Skip to content
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
49 changes: 49 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions sdk/appconfiguration/app-configuration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@
"@azure/eventgrid": "^5.0.0",
"@azure/identity": "catalog:internal",
"@azure/keyvault-secrets": "^4.2.0",
"@azure/app-configuration-provider": "latest",
"@microsoft/feature-management": "latest",
"@types/node": "catalog:",
"@vitest/browser": "catalog:testing",
"@vitest/coverage-istanbul": "catalog:testing",
Expand Down
172 changes: 59 additions & 113 deletions sdk/appconfiguration/app-configuration/samples-dev/featureFlag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,36 @@ import {
AppConfigurationClient,
ConfigurationSetting,
featureFlagContentType,
featureFlagPrefix,
FeatureFlagValue,
parseFeatureFlag,
} from "@azure/app-configuration";
import { DefaultAzureCredential } from "@azure/identity";

// Use configuration provider and feature management library to consume feature flags
import { load } from "@azure/app-configuration-provider";
import {
ConfigurationMapFeatureFlagProvider,
FeatureManager,
ITargetingContext,
} from "@microsoft/feature-management";

// Load the .env file if it exists
import * as dotenv from "dotenv";
dotenv.config();

export async function main() {
console.log(`Running featureFlag sample`);

const originalFeatureFlag: ConfigurationSetting<FeatureFlagValue> = {
key: `sample-feature-flag`,
const featureFlagName = "sample-feature-flag";
const sampleFeatureFlag: ConfigurationSetting<FeatureFlagValue> = {
key: `${featureFlagPrefix}${featureFlagName}`,
isReadOnly: false,
contentType: featureFlagContentType,
value: {
enabled: false,
description: "I'm a description",
conditions: {
clientFilters: [
{
// Time window filter - Use this filter to activate the feature for a time period
name: "Microsoft.TimeWindow",
parameters: {
Start: "Wed, 01 May 2021 13:59:59 GMT",
End: "Mon, 01 July 2022 00:00:00 GMT",
},
},
{
// Targeting filter - you can target users/groups of users using this filter
name: "Microsoft.Targeting",
Expand All @@ -52,9 +53,12 @@ export async function main() {
},
},
// {
// // Percentage filter - activates a feature based on a percentage, to enable the feature flag for 50% of requests
// name: "Microsoft.Percentage",
// parameters: { Value: 50 }
// // Time window filter - Use this filter to activate the feature for a time period
// name: "Microsoft.TimeWindow",
// parameters: {
// Start: new Date().toUTCString(),
// End: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toUTCString(), // 30 days from now
// },
// },
// { name: "FilterX" }, // Custom filter
],
Expand All @@ -67,77 +71,57 @@ export async function main() {
const credential = new DefaultAzureCredential();
const appConfigClient = new AppConfigurationClient(endpoint, credential);

await cleanupSampleValues([originalFeatureFlag.key], appConfigClient);
await cleanupSampleValues([sampleFeatureFlag.key], appConfigClient);

console.log(`Add a new featureFlag with key: ${originalFeatureFlag.key}`);
await appConfigClient.addConfigurationSetting(originalFeatureFlag);
console.log(`Add a new featureFlag with key: ${sampleFeatureFlag.key}`);
await appConfigClient.addConfigurationSetting(sampleFeatureFlag);

console.log(`Get the added configurationSetting with key: ${originalFeatureFlag.key}`);
const getResponse = await appConfigClient.getConfigurationSetting({
key: originalFeatureFlag.key,
console.log(`Use configuration provider to load feature flags and enable dynamic refresh`);
const appConfigProvider = await load(endpoint, credential, {
featureFlagOptions: {
enabled: true,
refresh: {
enabled: true,
// refreshIntervalInMs: 30_000, // Optional. Default: 30 seconds
},
},
});

// You can use the `isFeatureFlag` global method to check if the content type is featureFlagContentType ("application/vnd.microsoft.appconfig.ff+json;charset=utf-8")
const newFeatureFlag = parseFeatureFlag(getResponse); // Converts the configurationsetting into featureflag
// Modify the props
for (const clientFilter of newFeatureFlag.value.conditions.clientFilters) {
clientFilter.parameters = clientFilter.parameters || {};
console.log(
`\n...clientFilter - "${clientFilter.name}"...\nparams => ${JSON.stringify(
clientFilter.parameters,
null,
1,
)}\n`,
);
switch (clientFilter.name) {
// Tweak the client filters of the feature flag
case "Microsoft.Targeting":
// Adds a new user to the group
if (isTargetingClientFilter(clientFilter)) {
clientFilter.parameters.Audience.Users =
clientFilter.parameters.Audience.Users.concat("[email protected]");
}
break;
case "Microsoft.TimeWindow":
// Changes the start time
if (isTimeWindowClientFilter(clientFilter)) {
clientFilter.parameters.Start = "Wed, 01 June 2021 13:59:59 GMT";
}
break;
// case "Microsoft.Percentage":
// // Changes the percentage value from 50 to 75 - to enable the feature flag for 75% of requests
// clientFilter.parameters.Value = 75;
// break;
default:
// Change the filter name for all other client filters
// clientFilter.name = "FilterY";
break;
}
}
console.log(`Use feature management library to consume feature flags`);
const featureManager = new FeatureManager(
new ConfigurationMapFeatureFlagProvider(appConfigProvider),
);

let isEnabled = await featureManager.isEnabled(featureFlagName);
console.log(`Is featureFlag enabled? ${isEnabled}`);

const targetingContext: ITargetingContext = { userId: "[email protected]" };
isEnabled = await featureManager.isEnabled(featureFlagName, targetingContext);
console.log(`Is featureFlag enabled for [email protected]? ${isEnabled}`);

console.log(`========> Update the featureFlag <======== `);

// Update the feature flag to be enabled
sampleFeatureFlag.value.enabled = true;

// Updating the config setting
await appConfigClient.setConfigurationSetting(newFeatureFlag);
await appConfigClient.setConfigurationSetting(sampleFeatureFlag);

// Get the config setting again
console.log(`Get the updated config setting with key: ${newFeatureFlag.key}`);
const getResponseAfterUpdate = await appConfigClient.getConfigurationSetting({
key: newFeatureFlag.key,
});
while (!isEnabled) {
console.log("Waiting for feature flag to be refreshed...");
// Waiting for refresh interval to elapse
await new Promise((resolve) => setTimeout(resolve, 10_000));
await appConfigProvider.refresh();

// The feature flag will not be enabled for everyone as targeting filter is configured
isEnabled = await featureManager.isEnabled(featureFlagName);
console.log(`Is featureFlag enabled? ${isEnabled}`);

// You can use the `isFeatureFlag` global method to check if the content type is featureFlagContentType ("application/vnd.microsoft.appconfig.ff+json;charset=utf-8")
const featureFlagAfterUpdate = parseFeatureFlag(getResponseAfterUpdate); // Converts the configurationsetting into featureflag
const conditions = featureFlagAfterUpdate.value.conditions;
for (const clientFilter of conditions.clientFilters) {
console.log(
`\n...clientFilter - "${clientFilter.name}"...\nparams => ${JSON.stringify(
clientFilter.parameters,
null,
1,
)}\n`,
);
isEnabled = await featureManager.isEnabled(featureFlagName, targetingContext);
console.log(`Is featureFlag enabled for [email protected]? ${isEnabled}`);
}
await cleanupSampleValues([originalFeatureFlag.key], appConfigClient);

await cleanupSampleValues([sampleFeatureFlag.key], appConfigClient);
}

async function cleanupSampleValues(keys: string[], client: AppConfigurationClient) {
Expand All @@ -150,44 +134,6 @@ async function cleanupSampleValues(keys: string[], client: AppConfigurationClien
}
}

/**
* typeguard - for targeting client filter
*/
function isTargetingClientFilter(clientFilter: any): clientFilter is {
parameters: {
Audience: {
Groups: Array<{ Name: string; RolloutPercentage: number }>;
Users: Array<string>;
DefaultRolloutPercentage: number;
};
};
} {
return (
clientFilter.name === "Microsoft.Targeting" &&
clientFilter.parameters &&
clientFilter.parameters["Audience"] &&
Array.isArray(clientFilter.parameters["Audience"]["Groups"]) &&
Array.isArray(clientFilter.parameters["Audience"]["Users"]) &&
typeof clientFilter.parameters["Audience"]["DefaultRolloutPercentage"] === "number"
);
}

/**
* typeguard - for timewindow client filter
*/
export function isTimeWindowClientFilter(
clientFilter: any,
): clientFilter is { parameters: { Start: string; End: string } } {
return (
clientFilter.name === "Microsoft.TimeWindow" &&
clientFilter.parameters &&
clientFilter.parameters["Start"] &&
clientFilter.parameters["End"] &&
typeof clientFilter.parameters["Start"] === "string" &&
typeof clientFilter.parameters["End"] === "string"
);
}

main().catch((err) => {
console.error("Failed to run sample:", err);
process.exit(1);
Expand Down
Loading