Skip to content

Commit

Permalink
Add option for detecting changes to flag overrides (#101)
Browse files Browse the repository at this point in the history
* Add the ability to detect changes to the flag override map

* Move createFlagOverridesFromMap to common-js to reduce redundancy

* Bump version
  • Loading branch information
adams85 authored Jan 15, 2024
1 parent 1ec2efe commit 68e2306
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 29 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "configcat-common",
"version": "9.1.0",
"version": "9.2.0",
"description": "ConfigCat is a configuration as a service that lets you manage your features and configurations without actually deploying new code.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
Expand Down
25 changes: 18 additions & 7 deletions src/FlagOverrides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,31 @@ export interface IOverrideDataSource {
}

export class MapOverrideDataSource implements IOverrideDataSource {
private readonly map: { [name: string]: Setting } = {};
private static getCurrentSettings(map: { [name: string]: NonNullable<SettingValue> }) {
return Object.fromEntries(Object.entries(map)
.map(([key, value]) => [key, Setting.fromValue(value)]));
}

private readonly initialSettings: { [name: string]: Setting };
private readonly map?: { [name: string]: NonNullable<SettingValue> };

private readonly ["constructor"]!: typeof MapOverrideDataSource;

constructor(map: { [name: string]: NonNullable<SettingValue> }) {
this.map = Object.fromEntries(Object.entries(map).map(([key, value]) => {
return [key, Setting.fromValue(value)];
}));
constructor(map: { [name: string]: NonNullable<SettingValue> }, watchChanges?: boolean) {
this.initialSettings = this.constructor.getCurrentSettings(map);
if (watchChanges) {
this.map = map;
}
}

getOverrides(): Promise<{ [name: string]: Setting }> {
return Promise.resolve(this.map);
return Promise.resolve(this.getOverridesSync());
}

getOverridesSync(): { [name: string]: Setting } {
return this.map;
return this.map
? this.constructor.getCurrentSettings(this.map)
: this.initialSettings;
}
}

Expand Down
25 changes: 19 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import type { IAutoPollOptions, ILazyLoadingOptions, IManualPollOptions, Options
import { PollingMode } from "./ConfigCatClientOptions";
import type { IConfigCatLogger } from "./ConfigCatLogger";
import { ConfigCatConsoleLogger, LogLevel } from "./ConfigCatLogger";
import { FlagOverrides, MapOverrideDataSource, OverrideBehaviour } from "./FlagOverrides";
import { setupPolyfills } from "./Polyfills";
import type { SettingValue } from "./ProjectConfig";

setupPolyfills();

Expand Down Expand Up @@ -37,6 +39,19 @@ export function createConsoleLogger(logLevel: LogLevel): IConfigCatLogger {
return new ConfigCatConsoleLogger(logLevel);
}

/**
* Creates an instance of `FlagOverrides` that uses a map data source.
* @param map The map that contains the overrides.
* @param behaviour The override behaviour.
* Specifies whether the local values should override the remote values
* or local values should only be used when a remote value doesn't exist
* or the local values should be used only.
* @param watchChanges If set to `true`, the input map will be tracked for changes.
*/
export function createFlagOverridesFromMap(map: { [name: string]: NonNullable<SettingValue> }, behaviour: OverrideBehaviour, watchChanges?: boolean): FlagOverrides {
return new FlagOverrides(new MapOverrideDataSource(map, watchChanges), behaviour);
}

/* Public types for platform-specific SDKs */

// List types here which are required to implement the platform-specific SDKs but shouldn't be exposed to end users.
Expand All @@ -51,14 +66,10 @@ export type { OptionsBase } from "./ConfigCatClientOptions";

export type { IConfigCache } from "./ConfigCatCache";

export { ExternalConfigCache } from "./ConfigCatCache";
export { InMemoryConfigCache, ExternalConfigCache } from "./ConfigCatCache";

export type { IEventProvider, IEventEmitter } from "./EventEmitter";

export type { IOverrideDataSource } from "./FlagOverrides";

export { FlagOverrides, MapOverrideDataSource } from "./FlagOverrides";

/* Public types for end users */

// List types here which are part of the public API of platform-specific SDKs, thus, should be exposed to end users.
Expand Down Expand Up @@ -101,7 +112,9 @@ export type { UserAttributeValue } from "./User";

export { User } from "./User";

export { OverrideBehaviour } from "./FlagOverrides";
export type { FlagOverrides };

export { OverrideBehaviour };

export { ClientCacheState, RefreshResult } from "./ConfigServiceBase";

Expand Down
3 changes: 2 additions & 1 deletion test/ConfigV2EvaluationTests.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { assert } from "chai";
import "mocha";
import { FlagOverrides, IManualPollOptions, MapOverrideDataSource, OverrideBehaviour, SettingValue, User, UserAttributeValue } from "../src";
import { IManualPollOptions, OverrideBehaviour, SettingValue, User, UserAttributeValue } from "../src";
import { LogLevel, LoggerWrapper } from "../src/ConfigCatLogger";
import { FlagOverrides, MapOverrideDataSource } from "../src/FlagOverrides";
import { RolloutEvaluator, evaluate, isAllowedValue } from "../src/RolloutEvaluator";
import { errorToString } from "../src/Utils";
import { CdnConfigLocation, LocalFileConfigLocation } from "./helpers/ConfigLocation";
Expand Down
116 changes: 104 additions & 12 deletions test/OverrideTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,117 @@ describe("Local Overrides", () => {
sdkType: "common",
sdkVersion: "1.0.0"
};

const overrideMap = {
enabledFeature: true,
disabledFeature: false,
intSetting: 5,
doubleSetting: 3.14,
stringSetting: "test"
};

const options: AutoPollOptions = new AutoPollOptions("localhost", "common", "1.0.0", {
flagOverrides: {
dataSource: new MapOverrideDataSource({
enabledFeature: true,
disabledFeature: false,
intSetting: 5,
doubleSetting: 3.14,
stringSetting: "test"
}),
dataSource: new MapOverrideDataSource(overrideMap),
behaviour: OverrideBehaviour.LocalOnly
}
}, null);
const client: IConfigCatClient = new ConfigCatClient(options, configCatKernel);

assert.equal(await client.getValueAsync("enabledFeature", false), true);
assert.equal(await client.getValueAsync("disabledFeature", true), false);
assert.equal(await client.getValueAsync("intSetting", 0), 5);
assert.equal(await client.getValueAsync("doubleSetting", 0), 3.14);
assert.equal(await client.getValueAsync("stringSetting", ""), "test");
assert.equal(await client.getValueAsync("enabledFeature", null), true);
assert.equal(await client.getValueAsync("disabledFeature", null), false);
assert.equal(await client.getValueAsync("intSetting", null), 5);
assert.equal(await client.getValueAsync("doubleSetting", null), 3.14);
assert.equal(await client.getValueAsync("stringSetting", null), "test");

overrideMap.disabledFeature = true;
overrideMap.intSetting = -5;

assert.equal(await client.getValueAsync("enabledFeature", null), true);
assert.equal(await client.getValueAsync("disabledFeature", null), false);
assert.equal(await client.getValueAsync("intSetting", null), 5);
assert.equal(await client.getValueAsync("doubleSetting", null), 3.14);
assert.equal(await client.getValueAsync("stringSetting", null), "test");
});

it("Values from map - LocalOnly - watch changes - async", async () => {
const configCatKernel: FakeConfigCatKernel = {
configFetcher: new FakeConfigFetcherBase("{\"f\": { \"fakeKey\": { \"v\": false, \"p\": [], \"r\": [] } } }"),
sdkType: "common",
sdkVersion: "1.0.0"
};

const overrideMap = {
enabledFeature: true,
disabledFeature: false,
intSetting: 5,
doubleSetting: 3.14,
stringSetting: "test"
};

const options: AutoPollOptions = new AutoPollOptions("localhost", "common", "1.0.0", {
flagOverrides: {
dataSource: new MapOverrideDataSource(overrideMap, true),
behaviour: OverrideBehaviour.LocalOnly
}
}, null);
const client: IConfigCatClient = new ConfigCatClient(options, configCatKernel);

assert.equal(await client.getValueAsync("enabledFeature", null), true);
assert.equal(await client.getValueAsync("disabledFeature", null), false);
assert.equal(await client.getValueAsync("intSetting", null), 5);
assert.equal(await client.getValueAsync("doubleSetting", null), 3.14);
assert.equal(await client.getValueAsync("stringSetting", null), "test");

overrideMap.disabledFeature = true;
overrideMap.intSetting = -5;

assert.equal(await client.getValueAsync("enabledFeature", null), true);
assert.equal(await client.getValueAsync("disabledFeature", null), true);
assert.equal(await client.getValueAsync("intSetting", null), -5);
assert.equal(await client.getValueAsync("doubleSetting", null), 3.14);
assert.equal(await client.getValueAsync("stringSetting", null), "test");
});

it("Values from map - LocalOnly - watch changes - sync", async () => {
const configCatKernel: FakeConfigCatKernel = {
configFetcher: new FakeConfigFetcherBase("{\"f\": { \"fakeKey\": { \"v\": false, \"p\": [], \"r\": [] } } }"),
sdkType: "common",
sdkVersion: "1.0.0"
};

const overrideMap = {
enabledFeature: true,
disabledFeature: false,
intSetting: 5,
doubleSetting: 3.14,
stringSetting: "test"
};

const options: AutoPollOptions = new AutoPollOptions("localhost", "common", "1.0.0", {
flagOverrides: {
dataSource: new MapOverrideDataSource(overrideMap, true),
behaviour: OverrideBehaviour.LocalOnly
}
}, null);
const client: IConfigCatClient = new ConfigCatClient(options, configCatKernel);

let snapshot = client.snapshot();
assert.equal(await snapshot.getValue("enabledFeature", null), true);
assert.equal(await snapshot.getValue("disabledFeature", null), false);
assert.equal(await snapshot.getValue("intSetting", null), 5);
assert.equal(await snapshot.getValue("doubleSetting", null), 3.14);
assert.equal(await snapshot.getValue("stringSetting", null), "test");

overrideMap.disabledFeature = true;
overrideMap.intSetting = -5;

snapshot = client.snapshot();
assert.equal(await snapshot.getValue("enabledFeature", null), true);
assert.equal(await snapshot.getValue("disabledFeature", null), true);
assert.equal(await snapshot.getValue("intSetting", null), -5);
assert.equal(await snapshot.getValue("doubleSetting", null), 3.14);
assert.equal(await snapshot.getValue("stringSetting", null), "test");
});

it("Values from map - LocalOverRemote", async () => {
Expand Down

0 comments on commit 68e2306

Please sign in to comment.