Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option for detecting changes to flag overrides #101

Merged
merged 4 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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