Skip to content

Commit c4052e8

Browse files
Merge branch 'main' of https://github.com/Azure/AppConfiguration-JavaScriptProvider into merge-main-to-preview
1 parent af942d1 commit c4052e8

File tree

13 files changed

+1200
-90
lines changed

13 files changed

+1200
-90
lines changed

examples/console-app/package-lock.json

Lines changed: 955 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/web-app/package-lock.json

Lines changed: 25 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package-lock.json

Lines changed: 24 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
"playwright": "^1.55.0"
6767
},
6868
"dependencies": {
69-
"@azure/app-configuration": "^1.9.2",
69+
"@azure/app-configuration": "^1.10.0",
7070
"@azure/core-rest-pipeline": "^1.6.0",
7171
"@azure/identity": "^4.2.1",
7272
"@azure/keyvault-secrets": "^4.7.0",

src/appConfigurationImpl.ts

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import {
1111
featureFlagPrefix,
1212
isFeatureFlag,
1313
isSecretReference,
14+
isSnapshotReference,
15+
parseSnapshotReference,
16+
SnapshotReferenceValue,
1417
GetSnapshotOptions,
1518
ListConfigurationSettingsForSnapshotOptions,
1619
GetSnapshotResponse,
@@ -66,7 +69,7 @@ import { AIConfigurationTracingOptions } from "./requestTracing/aiConfigurationT
6669
import { KeyFilter, LabelFilter, SettingWatcher, SettingSelector, PagedSettingsWatcher, WatchedSetting } from "./types.js";
6770
import { ConfigurationClientManager } from "./configurationClientManager.js";
6871
import { getFixedBackoffDuration, getExponentialBackoffDuration } from "./common/backoffUtils.js";
69-
import { InvalidOperationError, ArgumentError, isFailoverableError, isInputError } from "./common/errors.js";
72+
import { InvalidOperationError, ArgumentError, isFailoverableError, isInputError, SnapshotReferenceError } from "./common/errors.js";
7073
import { ErrorMessages } from "./common/errorMessages.js";
7174
import { X_MS_DATE_HEADER } from "./afd/constants.js";
7275

@@ -92,6 +95,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
9295
#featureFlagTracing: FeatureFlagTracingOptions | undefined;
9396
#fmVersion: string | undefined;
9497
#aiConfigurationTracing: AIConfigurationTracingOptions | undefined;
98+
#useSnapshotReference: boolean = false;
9599

96100
// Refresh
97101
#refreshInProgress: boolean = false;
@@ -229,7 +233,8 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
229233
featureFlagTracing: this.#featureFlagTracing,
230234
fmVersion: this.#fmVersion,
231235
aiConfigurationTracing: this.#aiConfigurationTracing,
232-
isAfdUsed: this.#isAfdUsed
236+
isAfdUsed: this.#isAfdUsed,
237+
useSnapshotReference: this.#useSnapshotReference
233238
};
234239
}
235240

@@ -521,17 +526,29 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
521526
selector.pageWatchers = pageWatchers;
522527
settings = items;
523528
} else { // snapshot selector
524-
const snapshot = await this.#getSnapshot(selector.snapshotName);
525-
if (snapshot === undefined) {
526-
throw new InvalidOperationError(`Could not find snapshot with name ${selector.snapshotName}.`);
527-
}
528-
if (snapshot.compositionType != KnownSnapshotComposition.Key) {
529-
throw new InvalidOperationError(`Composition type for the selected snapshot with name ${selector.snapshotName} must be 'key'.`);
530-
}
531-
settings = await this.#listConfigurationSettingsForSnapshot(selector.snapshotName);
529+
settings = await this.#loadConfigurationSettingsFromSnapshot(selector.snapshotName);
532530
}
533531

534532
for (const setting of settings) {
533+
if (isSnapshotReference(setting) && !loadFeatureFlag) {
534+
this.#useSnapshotReference = true;
535+
536+
const snapshotRef: ConfigurationSetting<SnapshotReferenceValue> = parseSnapshotReference(setting);
537+
const snapshotName = snapshotRef.value.snapshotName;
538+
if (!snapshotName) {
539+
throw new SnapshotReferenceError(`Invalid format for Snapshot reference setting '${setting.key}'.`);
540+
}
541+
const settingsFromSnapshot = await this.#loadConfigurationSettingsFromSnapshot(snapshotName);
542+
543+
for (const snapshotSetting of settingsFromSnapshot) {
544+
if (!isFeatureFlag(snapshotSetting)) {
545+
// Feature flags inside snapshot are ignored. This is consistent the behavior that key value selectors ignore feature flags.
546+
loadedSettings.set(snapshotSetting.key, snapshotSetting);
547+
}
548+
}
549+
continue;
550+
}
551+
535552
if (loadFeatureFlag === isFeatureFlag(setting)) {
536553
loadedSettings.set(setting.key, setting);
537554
}
@@ -592,6 +609,18 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
592609
}
593610
}
594611

612+
async #loadConfigurationSettingsFromSnapshot(snapshotName: string): Promise<ConfigurationSetting[]> {
613+
const snapshot = await this.#getSnapshot(snapshotName);
614+
if (snapshot === undefined) {
615+
return []; // treat non-existing snapshot as empty
616+
}
617+
if (snapshot.compositionType != KnownSnapshotComposition.Key) {
618+
throw new InvalidOperationError(`Composition type for the selected snapshot with name ${snapshotName} must be 'key'.`);
619+
}
620+
const settings: ConfigurationSetting[] = await this.#listConfigurationSettingsForSnapshot(snapshotName);
621+
return settings;
622+
}
623+
595624
/**
596625
* Clears all existing key-values in the local configuration except feature flags.
597626
*/

src/common/errors.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ export class KeyVaultReferenceError extends Error {
3333
}
3434
}
3535

36+
export class SnapshotReferenceError extends Error {
37+
constructor(message: string, options?: ErrorOptions) {
38+
super(message, options);
39+
this.name = "SnapshotReferenceError";
40+
}
41+
}
42+
3643
export function isFailoverableError(error: any): boolean {
3744
if (!isRestError(error)) {
3845
return false;

src/requestTracing/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export const KEY_VAULT_CONFIGURED_TAG = "UsesKeyVault";
5252
export const KEY_VAULT_REFRESH_CONFIGURED_TAG = "RefreshesKeyVault";
5353
export const FAILOVER_REQUEST_TAG = "Failover";
5454
export const AFD_USED_TAG = "AFD";
55+
export const SNAPSHOT_REFERENCE_TAG = "SnapshotRef";
5556

5657
// Compact feature tags
5758
export const FEATURES_KEY = "Features";

src/requestTracing/utils.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ import {
4242
FM_VERSION_KEY,
4343
DELIMITER,
4444
AI_CONFIGURATION_TAG,
45-
AI_CHAT_COMPLETION_CONFIGURATION_TAG
45+
AI_CHAT_COMPLETION_CONFIGURATION_TAG,
46+
SNAPSHOT_REFERENCE_TAG
4647
} from "./constants.js";
4748

4849
export interface RequestTracingOptions {
@@ -55,6 +56,7 @@ export interface RequestTracingOptions {
5556
featureFlagTracing: FeatureFlagTracingOptions | undefined;
5657
fmVersion: string | undefined;
5758
aiConfigurationTracing: AIConfigurationTracingOptions | undefined;
59+
useSnapshotReference: boolean;
5860
}
5961

6062
// Utils
@@ -203,6 +205,9 @@ function createFeaturesString(requestTracingOptions: RequestTracingOptions): str
203205
if (requestTracingOptions.isAfdUsed) {
204206
tags.push(AFD_USED_TAG);
205207
}
208+
if (requestTracingOptions.useSnapshotReference) {
209+
tags.push(SNAPSHOT_REFERENCE_TAG);
210+
}
206211
return tags.join(DELIMITER);
207212
}
208213

test/featureFlag.test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -475,8 +475,14 @@ describe("feature flags", function () {
475475

476476
it("should load feature flags from snapshot", async () => {
477477
const snapshotName = "Test";
478-
mockAppConfigurationClientGetSnapshot(snapshotName, {compositionType: "key"});
479-
mockAppConfigurationClientListConfigurationSettingsForSnapshot(snapshotName, [[createMockedFeatureFlag("TestFeature", { enabled: true })]]);
478+
const snapshotResponses = new Map([
479+
[snapshotName, { compositionType: "key" }]
480+
]);
481+
const snapshotKVs = new Map([
482+
[snapshotName, [[createMockedFeatureFlag("TestFeature", { enabled: true })]]]
483+
]);
484+
mockAppConfigurationClientGetSnapshot(snapshotResponses);
485+
mockAppConfigurationClientListConfigurationSettingsForSnapshot(snapshotKVs);
480486
const connectionString = createMockedConnectionString();
481487
const settings = await load(connectionString, {
482488
featureFlagOptions: {

test/load.test.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -581,18 +581,22 @@ describe("load", function () {
581581

582582
it("should load key values from snapshot", async () => {
583583
const snapshotName = "Test";
584-
mockAppConfigurationClientGetSnapshot(snapshotName, {compositionType: "key"});
585-
mockAppConfigurationClientListConfigurationSettingsForSnapshot(snapshotName, [[{key: "TestKey", value: "TestValue"}].map(createMockedKeyValue)]);
584+
const snapshotResponses = new Map([
585+
[snapshotName, { compositionType: "key" }]
586+
]);
587+
const snapshotKVs = new Map([
588+
[snapshotName, [[{key: "TestKey", value: "TestValue"}].map(createMockedKeyValue)]]]
589+
);
590+
mockAppConfigurationClientGetSnapshot(snapshotResponses);
591+
mockAppConfigurationClientListConfigurationSettingsForSnapshot(snapshotKVs);
586592
const connectionString = createMockedConnectionString();
587593
const settings = await load(connectionString, {
588594
selectors: [{
589595
snapshotName: snapshotName
590596
}]
591597
});
592598
expect(settings).not.undefined;
593-
expect(settings).not.undefined;
594599
expect(settings.get("TestKey")).eq("TestValue");
595-
restoreMocks();
596600
});
597601
});
598602
/* eslint-enable @typescript-eslint/no-unused-expressions */

0 commit comments

Comments
 (0)