Skip to content

Commit 0939e7a

Browse files
committed
Merge branch 'main' into release/preview/v1
2 parents a034dc9 + e8a5c7c commit 0939e7a

File tree

6 files changed

+47
-26
lines changed

6 files changed

+47
-26
lines changed

examples/refresh.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const settings = await load(connectionString, {
2424
}],
2525
trimKeyPrefixes: ["app.settings."],
2626
refreshOptions: {
27+
enabled: true,
2728
watchedSettings: [{ key: "app.settings.sentinel" }],
2829
refreshIntervalInMs: 10 * 1000 // Default value is 30 seconds, shorted for this sample
2930
}
@@ -41,4 +42,4 @@ while (true) {
4142

4243
// Waiting before the next refresh
4344
await sleepInMs(5000);
44-
}
45+
}

src/AzureAppConfiguration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export type AzureAppConfiguration = {
1212
/**
1313
* API to register callback listeners, which will be called only when a refresh operation successfully updates key-values.
1414
*
15-
* @param listener - Callback funtion to be registered.
15+
* @param listener - Callback function to be registered.
1616
* @param thisArg - Optional. Value to use as `this` when executing callback.
1717
*/
1818
onRefresh(listener: () => any, thisArg?: any): Disposable;

src/AzureAppConfigurationImpl.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT license.
33

4-
import { AppConfigurationClient, ConfigurationSetting, ConfigurationSettingId, GetConfigurationSettingOptions, GetConfigurationSettingResponse, ListConfigurationSettingsOptions } from "@azure/app-configuration";
4+
import { AppConfigurationClient, ConfigurationSetting, ConfigurationSettingId, GetConfigurationSettingOptions, GetConfigurationSettingResponse, ListConfigurationSettingsOptions, isFeatureFlag } from "@azure/app-configuration";
55
import { RestError } from "@azure/core-rest-pipeline";
66
import { AzureAppConfiguration, ConfigurationObjectConstructionOptions } from "./AzureAppConfiguration";
77
import { AzureAppConfigurationOptions } from "./AzureAppConfigurationOptions";
@@ -150,7 +150,9 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
150150
const settings = this.#client.listConfigurationSettings(listOptions);
151151

152152
for await (const setting of settings) {
153-
loadedSettings.push(setting);
153+
if (!isFeatureFlag(setting)) { // exclude feature flags
154+
loadedSettings.push(setting);
155+
}
154156
}
155157
}
156158
return loadedSettings;
@@ -368,17 +370,17 @@ function getValidSelectors(selectors?: SettingSelector[]) {
368370
return [{ keyFilter: KeyFilter.Any, labelFilter: LabelFilter.Null }];
369371
}
370372

371-
// below code dedupes selectors by keyFilter and labelFilter, the latter selector wins
372-
const dedupedSelectors: SettingSelector[] = [];
373+
// below code deduplicates selectors by keyFilter and labelFilter, the latter selector wins
374+
const uniqueSelectors: SettingSelector[] = [];
373375
for (const selector of selectors) {
374-
const existingSelectorIndex = dedupedSelectors.findIndex(s => s.keyFilter === selector.keyFilter && s.labelFilter === selector.labelFilter);
376+
const existingSelectorIndex = uniqueSelectors.findIndex(s => s.keyFilter === selector.keyFilter && s.labelFilter === selector.labelFilter);
375377
if (existingSelectorIndex >= 0) {
376-
dedupedSelectors.splice(existingSelectorIndex, 1);
378+
uniqueSelectors.splice(existingSelectorIndex, 1);
377379
}
378-
dedupedSelectors.push(selector);
380+
uniqueSelectors.push(selector);
379381
}
380382

381-
return dedupedSelectors.map(selectorCandidate => {
383+
return uniqueSelectors.map(selectorCandidate => {
382384
const selector = { ...selectorCandidate };
383385
if (!selector.keyFilter) {
384386
throw new Error("Key filter cannot be null or empty.");

src/JsonKeyValueAdapter.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT license.
33

4-
import { ConfigurationSetting, secretReferenceContentType } from "@azure/app-configuration";
4+
import { ConfigurationSetting, featureFlagContentType, secretReferenceContentType } from "@azure/app-configuration";
55
import { IKeyValueAdapter } from "./IKeyValueAdapter";
66

77
export class JsonKeyValueAdapter implements IKeyValueAdapter {
88
static readonly #ExcludedJsonContentTypes: string[] = [
9-
secretReferenceContentType
10-
// TODO: exclude application/vnd.microsoft.appconfig.ff+json after feature management is supported
9+
secretReferenceContentType,
10+
featureFlagContentType
1111
];
1212

1313
canProcess(setting: ConfigurationSetting): boolean {

test/load.test.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as chaiAsPromised from "chai-as-promised";
66
chai.use(chaiAsPromised);
77
const expect = chai.expect;
88
import { load } from "./exportedApi";
9-
import { mockAppConfigurationClientListConfigurationSettings, restoreMocks, createMockedConnectionString, createMockedEnpoint, createMockedTokenCredential, createMockedKeyValue } from "./utils/testHelper";
9+
import { mockAppConfigurationClientListConfigurationSettings, restoreMocks, createMockedConnectionString, createMockedEndpoint, createMockedTokenCredential, createMockedKeyValue } from "./utils/testHelper";
1010

1111
const mockedKVs = [{
1212
key: "app.settings.fontColor",
@@ -62,6 +62,17 @@ const mockedKVs = [{
6262
}, {
6363
key: "app5.settings",
6464
value: "placeholder"
65+
}, {
66+
key: ".appconfig.featureflag/Beta",
67+
value: JSON.stringify({
68+
"id": "Beta",
69+
"description": "",
70+
"enabled": true,
71+
"conditions": {
72+
"client_filters": []
73+
}
74+
}),
75+
contentType: "application/vnd.microsoft.appconfig.ff+json;charset=utf-8"
6576
}
6677
].map(createMockedKeyValue);
6778

@@ -84,7 +95,7 @@ describe("load", function () {
8495
});
8596

8697
it("should load data from config store with aad + endpoint URL", async () => {
87-
const endpoint = createMockedEnpoint();
98+
const endpoint = createMockedEndpoint();
8899
const credential = createMockedTokenCredential();
89100
const settings = await load(new URL(endpoint), credential);
90101
expect(settings).not.undefined;
@@ -93,7 +104,7 @@ describe("load", function () {
93104
});
94105

95106
it("should load data from config store with aad + endpoint string", async () => {
96-
const endpoint = createMockedEnpoint();
107+
const endpoint = createMockedEndpoint();
97108
const credential = createMockedTokenCredential();
98109
const settings = await load(endpoint, credential);
99110
expect(settings).not.undefined;
@@ -110,6 +121,13 @@ describe("load", function () {
110121
return expect(load("invalid-endpoint-url", credential)).eventually.rejectedWith("Invalid endpoint URL.");
111122
});
112123

124+
it("should not include feature flags directly in the settings", async () => {
125+
const connectionString = createMockedConnectionString();
126+
const settings = await load(connectionString);
127+
expect(settings).not.undefined;
128+
expect(settings.get(".appconfig.featureflag/Beta")).undefined;
129+
});
130+
113131
it("should filter by key and label, has(key) and get(key) should work", async () => {
114132
const connectionString = createMockedConnectionString();
115133
const settings = await load(connectionString, {
@@ -251,7 +269,7 @@ describe("load", function () {
251269
expect(settings.get("TestKey")).eq("TestValueForProd");
252270
});
253271

254-
it("should dedup exact same selectors but keeping the precedence", async () => {
272+
it("should deduplicate exact same selectors but keeping the precedence", async () => {
255273
const connectionString = createMockedConnectionString();
256274
const settings = await load(connectionString, {
257275
selectors: [{
@@ -317,7 +335,7 @@ describe("load", function () {
317335
* key: "app3.settings.fontColor" => value: "yellow"
318336
*
319337
* get() will return "placeholder" for "app3.settings" and "yellow" for "app3.settings.fontColor", as expected.
320-
* data.app3.settings will return "placeholder" as a whole JSON object, which is not guarenteed to be correct.
338+
* data.app3.settings will return "placeholder" as a whole JSON object, which is not guaranteed to be correct.
321339
*/
322340
it("Edge case 1: Hierarchical key-value pairs with overlapped key prefix.", async () => {
323341
const connectionString = createMockedConnectionString();
@@ -337,7 +355,7 @@ describe("load", function () {
337355
* key: "app5.settings.fontColor" => value: "yellow"
338356
* key: "app5.settings" => value: "placeholder"
339357
*
340-
* When ocnstructConfigurationObject() is called, it first constructs from key "app5.settings.fontColor" and then from key "app5.settings".
358+
* When constructConfigurationObject() is called, it first constructs from key "app5.settings.fontColor" and then from key "app5.settings".
341359
* An error will be thrown when constructing from key "app5.settings" because there is ambiguity between the two keys.
342360
*/
343361
it("Edge case 1: Hierarchical key-value pairs with overlapped key prefix.", async () => {

test/utils/testHelper.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const TEST_TENANT_ID = "00000000-0000-0000-0000-000000000000";
1515
const TEST_CLIENT_SECRET = "0000000000000000000000000000000000000000";
1616

1717
function mockAppConfigurationClientListConfigurationSettings(kvList: ConfigurationSetting[]) {
18-
function* testKvSetGnerator(kvs: any[]) {
18+
function* testKvSetGenerator(kvs: any[]) {
1919
yield* kvs;
2020
}
2121
sinon.stub(AppConfigurationClient.prototype, "listConfigurationSettings").callsFake((listOptions) => {
@@ -36,7 +36,7 @@ function mockAppConfigurationClientListConfigurationSettings(kvList: Configurati
3636
}
3737
return keyMatched && labelMatched;
3838
})
39-
return testKvSetGnerator(kvs) as any;
39+
return testKvSetGenerator(kvs) as any;
4040
});
4141
}
4242

@@ -79,9 +79,9 @@ function restoreMocks() {
7979
sinon.restore();
8080
}
8181

82-
const createMockedEnpoint = (name = "azure") => `https://${name}.azconfig.io`;
82+
const createMockedEndpoint = (name = "azure") => `https://${name}.azconfig.io`;
8383

84-
const createMockedConnectionString = (endpoint = createMockedEnpoint(), secret = "secret", id = "b1d9b31") => {
84+
const createMockedConnectionString = (endpoint = createMockedEndpoint(), secret = "secret", id = "b1d9b31") => {
8585
const toEncodeAsBytes = Buffer.from(secret);
8686
const returnValue = toEncodeAsBytes.toString("base64");
8787
return `Endpoint=${endpoint};Id=${id};Secret=${returnValue}`;
@@ -99,7 +99,7 @@ const createMockedKeyVaultReference = (key: string, vaultUri: string): Configura
9999
lastModified: new Date(),
100100
tags: {
101101
},
102-
etag: "SPJSMnJ2ph4BAjftWfdIctV2VIyQxtcIzRbh1oxTBkM",
102+
etag: uuid.v4(),
103103
isReadOnly: false,
104104
});
105105

@@ -109,7 +109,7 @@ const createMockedJsonKeyValue = (key: string, value: any): ConfigurationSetting
109109
contentType: "application/json",
110110
lastModified: new Date(),
111111
tags: {},
112-
etag: "GdmsLWq3mFjFodVEXUYRmvFr3l_qRiKAW_KdpFbxZKk",
112+
etag: uuid.v4(),
113113
isReadOnly: false
114114
});
115115

@@ -130,7 +130,7 @@ export {
130130
mockSecretClientGetSecret,
131131
restoreMocks,
132132

133-
createMockedEnpoint,
133+
createMockedEndpoint,
134134
createMockedConnectionString,
135135
createMockedTokenCredential,
136136
createMockedKeyVaultReference,

0 commit comments

Comments
 (0)