Skip to content

Commit 1577bef

Browse files
Merge branch 'preview' of https://github.com/Azure/AppConfiguration-JavaScriptProvider into zhiyuanliang/afd-support
2 parents b2b76ed + a31cc03 commit 1577bef

File tree

2 files changed

+99
-77
lines changed

2 files changed

+99
-77
lines changed

src/appConfigurationImpl.ts

Lines changed: 98 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
isFeatureFlag,
1313
isSecretReference,
1414
GetSnapshotOptions,
15+
ListConfigurationSettingsForSnapshotOptions,
1516
GetSnapshotResponse,
1617
KnownSnapshotComposition,
1718
ListConfigurationSettingPage
@@ -502,84 +503,64 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
502503
*/
503504
async #loadConfigurationSettings(loadFeatureFlag: boolean = false): Promise<ConfigurationSetting[]> {
504505
const selectors: PagedSettingsWatcher[] = loadFeatureFlag ? this.#ffSelectors : this.#kvSelectors;
505-
const funcToExecute = async (client) => {
506-
// Use a Map to deduplicate configuration settings by key. When multiple selectors return settings with the same key,
507-
// the configuration setting loaded by the later selector in the iteration order will override the one from the earlier selector.
508-
const loadedSettings: Map<string, ConfigurationSetting> = new Map<string, ConfigurationSetting>();
509-
// deep copy selectors to avoid modification if current client fails
510-
const selectorsToUpdate: PagedSettingsWatcher[] = JSON.parse(
511-
JSON.stringify(selectors)
512-
);
513506

514-
for (const selector of selectorsToUpdate) {
515-
if (selector.snapshotName === undefined) {
516-
const listOptions: ListConfigurationSettingsOptions = {
517-
keyFilter: selector.keyFilter,
518-
labelFilter: selector.labelFilter,
519-
tagsFilter: selector.tagFilters
520-
};
521-
const pageWatchers: SettingWatcher[] = [];
522-
const pageIterator = listConfigurationSettingsWithTrace(
523-
this.#requestTraceOptions,
524-
client,
525-
listOptions
526-
).byPage();
527-
528-
for await (const page of pageIterator) {
529-
pageWatchers.push({etag: page.etag, timestamp: this.#getResponseTimestamp(page)});
530-
for (const setting of page.items) {
531-
if (loadFeatureFlag === isFeatureFlag(setting)) {
532-
loadedSettings.set(setting.key, setting);
533-
}
507+
// Use a Map to deduplicate configuration settings by key. When multiple selectors return settings with the same key,
508+
// the configuration setting loaded by the later selector in the iteration order will override the one from the earlier selector.
509+
const loadedSettings: Map<string, ConfigurationSetting> = new Map<string, ConfigurationSetting>();
510+
// deep copy selectors to avoid modification if current client fails
511+
const selectorsToUpdate: PagedSettingsWatcher[] = JSON.parse(
512+
JSON.stringify(selectors)
513+
);
514+
515+
for (const selector of selectorsToUpdate) {
516+
let settings: ConfigurationSetting[] = [];
517+
if (selector.snapshotName === undefined) {
518+
const listOptions: ListConfigurationSettingsOptions = {
519+
keyFilter: selector.keyFilter,
520+
labelFilter: selector.labelFilter,
521+
tagsFilter: selector.tagFilters
522+
};
523+
const { items, pageWatchers } = await this.#listConfigurationSettings(listOptions);
524+
525+
for (const pageWatcher of pageWatchers) {
526+
if (loadFeatureFlag) {
527+
this.#isFfStale = pageWatcher.timestamp < this.#lastFfChangeDetectedTime;
528+
if (this.#isFfStale) {
529+
return [];
534530
}
535-
const timestamp = this.#getResponseTimestamp(page);
536-
// all pages must be later than last change detected to be considered up-to-date
537-
if (loadFeatureFlag) {
538-
this.#isFfStale = timestamp < this.#lastFfChangeDetectedTime;
539-
if (this.#isFfStale) {
540-
return [];
541-
}
542-
} else {
543-
this.#isKvStale = timestamp < this.#lastKvChangeDetectedTime;
544-
if (this.#isKvStale) {
545-
return [];
546-
}
547-
}
548-
}
549-
selector.pageWatchers = pageWatchers;
550-
} else { // snapshot selector
551-
const snapshot = await this.#getSnapshot(selector.snapshotName);
552-
if (snapshot === undefined) {
553-
throw new InvalidOperationError(`Could not find snapshot with name ${selector.snapshotName}.`);
554-
}
555-
if (snapshot.compositionType != KnownSnapshotComposition.Key) {
556-
throw new InvalidOperationError(`Composition type for the selected snapshot with name ${selector.snapshotName} must be 'key'.`);
557-
}
558-
const pageIterator = listConfigurationSettingsForSnapshotWithTrace(
559-
this.#requestTraceOptions,
560-
client,
561-
selector.snapshotName
562-
).byPage();
563-
564-
for await (const page of pageIterator) {
565-
for (const setting of page.items) {
566-
if (loadFeatureFlag === isFeatureFlag(setting)) {
567-
loadedSettings.set(setting.key, setting);
568-
}
531+
} else {
532+
this.#isKvStale = pageWatcher.timestamp < this.#lastKvChangeDetectedTime;
533+
if (this.#isKvStale) {
534+
return [];
569535
}
570536
}
571537
}
538+
selector.pageWatchers = pageWatchers;
539+
settings = items;
540+
} else { // snapshot selector
541+
const snapshot = await this.#getSnapshot(selector.snapshotName);
542+
if (snapshot === undefined) {
543+
throw new InvalidOperationError(`Could not find snapshot with name ${selector.snapshotName}.`);
544+
}
545+
if (snapshot.compositionType != KnownSnapshotComposition.Key) {
546+
throw new InvalidOperationError(`Composition type for the selected snapshot with name ${selector.snapshotName} must be 'key'.`);
547+
}
548+
settings = await this.#listConfigurationSettingsForSnapshot(selector.snapshotName);
572549
}
573550

574-
if (loadFeatureFlag) {
575-
this.#ffSelectors = selectorsToUpdate;
576-
} else {
577-
this.#kvSelectors = selectorsToUpdate;
551+
for (const setting of settings) {
552+
if (loadFeatureFlag === isFeatureFlag(setting)) {
553+
loadedSettings.set(setting.key, setting);
554+
}
578555
}
579-
return Array.from(loadedSettings.values());
580-
};
556+
}
581557

582-
return await this.#executeWithFailoverPolicy(funcToExecute) as ConfigurationSetting[];
558+
if (loadFeatureFlag) {
559+
this.#ffSelectors = selectorsToUpdate;
560+
} else {
561+
this.#kvSelectors = selectorsToUpdate;
562+
}
563+
return Array.from(loadedSettings.values());
583564
}
584565

585566
/**
@@ -653,6 +634,10 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
653634
const loadFeatureFlag = true;
654635
const featureFlagSettings: ConfigurationSetting[] = await this.#loadConfigurationSettings(loadFeatureFlag);
655636

637+
if (featureFlagSettings.length === 0) {
638+
return;
639+
}
640+
656641
if (this.#requestTracingEnabled && this.#featureFlagTracing !== undefined) {
657642
// Reset old feature flag tracing in order to track the information present in the current response from server.
658643
this.#featureFlagTracing.reset();
@@ -832,13 +817,13 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
832817
/**
833818
* Gets a configuration setting by key and label. If the setting is not found, return the error instead of throwing it.
834819
*/
835-
async #getConfigurationSetting(configurationSettingId: ConfigurationSettingId, customOptions?: GetConfigurationSettingOptions): Promise<GetConfigurationSettingResponse | RestError> {
820+
async #getConfigurationSetting(configurationSettingId: ConfigurationSettingId, getOptions?: GetConfigurationSettingOptions): Promise<GetConfigurationSettingResponse | RestError> {
836821
const funcToExecute = async (client) => {
837822
return getConfigurationSettingWithTrace(
838823
this.#requestTraceOptions,
839824
client,
840825
configurationSettingId,
841-
customOptions
826+
getOptions
842827
);
843828
};
844829

@@ -856,13 +841,33 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
856841
return response;
857842
}
858843

859-
async #getSnapshot(snapshotName: string, customOptions?: GetSnapshotOptions): Promise<GetSnapshotResponse | undefined> {
844+
async #listConfigurationSettings(listOptions: ListConfigurationSettingsOptions): Promise<{ items: ConfigurationSetting[]; pageWatchers: SettingWatcher[] }> {
845+
const funcToExecute = async (client) => {
846+
const pageWatchers: SettingWatcher[] = [];
847+
const pageIterator = listConfigurationSettingsWithTrace(
848+
this.#requestTraceOptions,
849+
client,
850+
listOptions
851+
).byPage();
852+
853+
const items: ConfigurationSetting[] = [];
854+
for await (const page of pageIterator) {
855+
pageWatchers.push({ etag: page.etag, timestamp: this.#getResponseTimestamp(page) });
856+
items.push(...page.items);
857+
}
858+
return { items, pageWatchers };
859+
};
860+
861+
return await this.#executeWithFailoverPolicy(funcToExecute);
862+
}
863+
864+
async #getSnapshot(snapshotName: string, getOptions?: GetSnapshotOptions): Promise<GetSnapshotResponse | undefined> {
860865
const funcToExecute = async (client) => {
861866
return getSnapshotWithTrace(
862867
this.#requestTraceOptions,
863868
client,
864869
snapshotName,
865-
customOptions
870+
getOptions
866871
);
867872
};
868873

@@ -879,6 +884,25 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
879884
return response;
880885
}
881886

887+
async #listConfigurationSettingsForSnapshot(snapshotName: string, listOptions?: ListConfigurationSettingsForSnapshotOptions): Promise<ConfigurationSetting[]> {
888+
const funcToExecute = async (client) => {
889+
const pageIterator = listConfigurationSettingsForSnapshotWithTrace(
890+
this.#requestTraceOptions,
891+
client,
892+
snapshotName,
893+
listOptions
894+
).byPage();
895+
896+
const items: ConfigurationSetting[] = [];
897+
for await (const page of pageIterator) {
898+
items.push(...page.items);
899+
}
900+
return items;
901+
};
902+
903+
return await this.#executeWithFailoverPolicy(funcToExecute);
904+
}
905+
882906
// Only operations related to Azure App Configuration should be executed with failover policy.
883907
async #executeWithFailoverPolicy(funcToExecute: (client: AppConfigurationClient) => Promise<any>): Promise<any> {
884908
let clientWrappers = await this.#clientManager.getClients();
@@ -954,7 +978,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
954978
#setAIConfigurationTracing(setting: ConfigurationSetting<string>): void {
955979
if (this.#requestTracingEnabled && this.#aiConfigurationTracing !== undefined) {
956980
const contentType = parseContentType(setting.contentType);
957-
// content type: "application/json; profile=\"https://azconfig.io/mime-profiles/ai\"""
981+
// content type: "application/json; profile=\"https://azconfig.io/mime-profiles/ai\""
958982
if (isJsonContentType(contentType) &&
959983
!isFeatureFlagContentType(contentType) &&
960984
!isSecretReferenceContentType(contentType)) {

test/featureFlag.test.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -468,9 +468,7 @@ describe("feature flags", function () {
468468
}
469469
});
470470

471-
featureFlags = settingsWithNonExistentTag.get<any>("feature_management").feature_flags;
472-
expect(featureFlags).not.undefined;
473-
expect((featureFlags as []).length).equals(0);
471+
expect(settingsWithNonExistentTag.get("feature_management")).to.be.undefined;
474472
});
475473

476474
it("should load feature flags from snapshot", async () => {

0 commit comments

Comments
 (0)