Skip to content

Commit 7d45d1c

Browse files
wip
1 parent 5135f8e commit 7d45d1c

File tree

5 files changed

+106
-7
lines changed

5 files changed

+106
-7
lines changed

src/AzureAppConfigurationImpl.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import { IKeyValueAdapter } from "./IKeyValueAdapter";
99
import { JsonKeyValueAdapter } from "./JsonKeyValueAdapter";
1010
import { DEFAULT_REFRESH_INTERVAL_IN_MS, MIN_REFRESH_INTERVAL_IN_MS } from "./RefreshOptions";
1111
import { Disposable } from "./common/disposable";
12-
import { FEATURE_FLAGS_KEY_NAME, FEATURE_MANAGEMENT_KEY_NAME } from "./featureManagement/constants";
12+
import { FEATURE_FLAGS_KEY_NAME, FEATURE_MANAGEMENT_KEY_NAME, CONDITIONS_KEY_NAME, CLIENT_FILTERS_KEY_NAME } from "./featureManagement/constants";
1313
import { AzureKeyVaultKeyValueAdapter } from "./keyvault/AzureKeyVaultKeyValueAdapter";
1414
import { RefreshTimer } from "./refresh/RefreshTimer";
1515
import { getConfigurationSettingWithTrace, listConfigurationSettingsWithTrace, requestTracingEnabled } from "./requestTracing/utils";
16+
import { FeatureFlagTracing } from "./requestTracing/FeatureFlagTracing";
1617
import { KeyFilter, LabelFilter, SettingSelector } from "./types";
1718

1819
type PagedSettingSelector = SettingSelector & {
@@ -38,6 +39,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
3839
#client: AppConfigurationClient;
3940
#options: AzureAppConfigurationOptions | undefined;
4041
#isInitialLoadCompleted: boolean = false;
42+
#featureFlagTracing: FeatureFlagTracing = new FeatureFlagTracing();
4143

4244
// Refresh
4345
#refreshInterval: number = DEFAULT_REFRESH_INTERVAL_IN_MS;
@@ -173,7 +175,8 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
173175
return {
174176
requestTracingEnabled: this.#requestTracingEnabled,
175177
initialLoadCompleted: this.#isInitialLoadCompleted,
176-
appConfigOptions: this.#options
178+
appConfigOptions: this.#options,
179+
featureFlagTracingOptions: this.#featureFlagTracing
177180
};
178181
}
179182

@@ -255,8 +258,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
255258
}
256259

257260
async #loadFeatureFlags() {
258-
// Temporary map to store feature flags, key is the key of the setting, value is the raw value of the setting
259-
const featureFlagsMap = new Map<string, any>();
261+
const featureFlagSettings: ConfigurationSetting[] = [];
260262
for (const selector of this.#featureFlagSelectors) {
261263
const listOptions: ListConfigurationSettingsOptions = {
262264
keyFilter: `${featureFlagPrefix}${selector.keyFilter}`,
@@ -273,15 +275,17 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
273275
pageEtags.push(page.etag ?? "");
274276
for (const setting of page.items) {
275277
if (isFeatureFlag(setting)) {
276-
featureFlagsMap.set(setting.key, setting.value);
278+
featureFlagSettings.push(setting);
277279
}
278280
}
279281
}
280282
selector.pageEtags = pageEtags;
281283
}
282284

283285
// parse feature flags
284-
const featureFlags = Array.from(featureFlagsMap.values()).map(rawFlag => JSON.parse(rawFlag));
286+
const featureFlags = await Promise.all(
287+
featureFlagSettings.map(setting => this.#parseFeatureFlag(setting))
288+
);
285289

286290
// feature_management is a reserved key, and feature_flags is an array of feature flags
287291
this.#configMap.set(FEATURE_MANAGEMENT_KEY_NAME, { [FEATURE_FLAGS_KEY_NAME]: featureFlags });
@@ -532,6 +536,18 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
532536
}
533537
return response;
534538
}
539+
540+
async #parseFeatureFlag(setting: ConfigurationSetting<string>): Promise<any> {
541+
const rawFlag = setting.value;
542+
if (rawFlag === undefined) {
543+
throw new Error("The value of configuration setting cannot be undefined.");
544+
}
545+
const featureFlag = JSON.parse(rawFlag);
546+
547+
548+
549+
return featureFlag;
550+
}
535551
}
536552

537553
function getValidSelectors(selectors: SettingSelector[]): SettingSelector[] {

src/featureManagement/constants.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22
// Licensed under the MIT license.
33

44
export const FEATURE_MANAGEMENT_KEY_NAME = "feature_management";
5-
export const FEATURE_FLAGS_KEY_NAME = "feature_flags";
5+
export const FEATURE_FLAGS_KEY_NAME = "feature_flags";
6+
export const CONDITIONS_KEY_NAME = "conditions";
7+
export const CLIENT_FILTERS_KEY_NAME = "client_filters";
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
4+
import {
5+
CUSTOM_FILTER_KEY,
6+
TIME_WINDOW_FILTER_KEY,
7+
TARGETING_FILTER_KEY,
8+
DELIMITER
9+
} from "./constants";
10+
11+
/**
12+
* Tracing for tracking feature flag usage.
13+
*/
14+
export class FeatureFlagTracing {
15+
#timeWindowFilterNames: string[] = ["TimeWindow", "Microsoft.TimeWindow", "TimeWindowFilter", "Microsoft.TimeWindowFilter"];
16+
#targetingFilterNames: string[] = ["Targeting", "Microsoft.Targeting", "TargetingFilter", "Microsoft.TargetingFilter"];
17+
18+
/**
19+
* Built-in feature filter usage.
20+
*/
21+
usesCustomFilter: boolean = false;
22+
usesTimeWindowFilter: boolean = false;
23+
usesTargetingFilter: boolean = false;
24+
25+
resetFeatureFlagTracing(): void {
26+
this.usesCustomFilter = false;
27+
this.usesTimeWindowFilter = false;
28+
this.usesTargetingFilter = false;
29+
}
30+
31+
updateFeatureFilterTracing(filterName: string): void {
32+
if (this.#timeWindowFilterNames.some(name => name.toLowerCase() === filterName.toLowerCase())) {
33+
this.usesTimeWindowFilter = true;
34+
} else if (this.#targetingFilterNames.some(name => name.toLowerCase() === filterName.toLowerCase())) {
35+
this.usesTargetingFilter = true;
36+
} else {
37+
this.usesCustomFilter = true;
38+
}
39+
}
40+
41+
usesAnyFeatureFilter(): boolean {
42+
return this.usesCustomFilter || this.usesTimeWindowFilter || this.usesTargetingFilter;
43+
}
44+
45+
createFeatureFiltersString(): string {
46+
if (!this.usesAnyFeatureFilter()) {
47+
return "";
48+
}
49+
50+
let result: string = "";
51+
52+
if (this.usesCustomFilter) {
53+
result += CUSTOM_FILTER_KEY
54+
}
55+
56+
if (this.usesTimeWindowFilter) {
57+
if (result !== "") {
58+
result += DELIMITER;
59+
}
60+
result += TIME_WINDOW_FILTER_KEY;
61+
}
62+
63+
if (this.usesTargetingFilter) {
64+
if (result !== "") {
65+
result += DELIMITER;
66+
}
67+
result += TARGETING_FILTER_KEY;
68+
}
69+
70+
return result;
71+
}
72+
}

src/requestTracing/constants.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,10 @@ export enum RequestType {
4646

4747
// Tag names
4848
export const KEY_VAULT_CONFIGURED_TAG = "UsesKeyVault";
49+
50+
// Feature Flag Usage Tracing
51+
export const CUSTOM_FILTER_KEY = "CSTM";
52+
export const TIME_WINDOW_FILTER_KEY = "TIME";
53+
export const TARGETING_FILTER_KEY = "TRGT";
54+
55+
export const DELIMITER = "+";

src/requestTracing/utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import { AppConfigurationClient, ConfigurationSettingId, GetConfigurationSettingOptions, ListConfigurationSettingsOptions } from "@azure/app-configuration";
55
import { AzureAppConfigurationOptions } from "../AzureAppConfigurationOptions";
6+
import { FeatureFlagTracing } from "./FeatureFlagTracing";
67
import {
78
AZURE_FUNCTION_ENV_VAR,
89
AZURE_WEB_APP_ENV_VAR,
@@ -28,6 +29,7 @@ export function listConfigurationSettingsWithTrace(
2829
requestTracingEnabled: boolean;
2930
initialLoadCompleted: boolean;
3031
appConfigOptions: AzureAppConfigurationOptions | undefined;
32+
featureFlagTracingOptions: FeatureFlagTracing | undefined;
3133
},
3234
client: AppConfigurationClient,
3335
listOptions: ListConfigurationSettingsOptions

0 commit comments

Comments
 (0)