Skip to content

Commit e9eb89e

Browse files
update
1 parent c5754b2 commit e9eb89e

File tree

2 files changed

+154
-63
lines changed

2 files changed

+154
-63
lines changed

articles/azure-app-configuration/feature-management-overview.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ Feature | .NET | Spring | Python | JavaScript
4141
------- | ---- | ------ | ------ | ----------
4242
Targeting Filter | [GA](./feature-management-dotnet-reference.md#targeting) | GA | [GA](./feature-management-python-reference.md#targeting) | [GA](./feature-management-javascript-reference.md#targeting)
4343
Targeting Exclusion | [GA](./feature-management-dotnet-reference.md#targeting-exclusion) | GA | [GA](./feature-management-python-reference.md#targeting-exclusion) | [GA](./feature-management-javascript-reference.md#targeting-exclusion)
44-
Ambient Targeting | [GA](./feature-management-dotnet-reference.md#targeting-in-a-web-application) | WIP | WIP | [Preview](./feature-management-javascript-reference.md#targeting-in-a-web-application)
44+
Ambient Targeting | [GA](./feature-management-dotnet-reference.md#targeting-in-a-web-application) | WIP | WIP | [GA](./feature-management-javascript-reference.md#targeting-in-a-web-application)
4545
Time Window Filter | [GA](./feature-management-dotnet-reference.md#microsofttimewindow) | GA | [GA](./feature-management-python-reference.md#microsofttimewindow) | [GA](./feature-management-javascript-reference.md#microsofttimewindow)
4646
Recurring Time Window | [GA](./feature-management-dotnet-reference.md#microsofttimewindow) | GA | WIP | WIP
4747
Custom Feature Filter | [GA](./feature-management-dotnet-reference.md#implementing-a-feature-filter) | GA | [GA](./feature-management-python-reference.md#implementing-a-feature-filter) | [GA](./feature-management-javascript-reference.md#implementing-a-feature-filter)

articles/azure-app-configuration/howto-targetingfilter-javascript.md

Lines changed: 153 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,17 @@ In this guide, you'll use the targeting filter to roll out a feature to targeted
1616

1717
## Prerequisites
1818

19-
- Create a [Node.js application with a feature flag](./quickstart-feature-flag-javascript.md).
19+
- An Azure account with an active subscription. [Create one for free](https://azure.microsoft.com/free/).
20+
- An App Configuration store. [Create a store](./quickstart-azure-app-configuration-create.md#create-an-app-configuration-store).
2021
- A feature flag with targeting filter. [Create the feature flag](./howto-targetingfilter.md).
22+
- [LTS versions of Node.js](https://github.com/nodejs/release#release-schedule).
2123

2224
## Create a web application with a feature flag
2325

2426
In this section, you create a web application that uses the *Beta* feature flag to control the access to the beta version of the page.
2527

28+
### Initial project setup
29+
2630
1. Create a folder called `targeting-filter-tutorial` and initialize the project.
2731

2832
```bash
@@ -39,16 +43,20 @@ In this section, you create a web application that uses the *Beta* feature flag
3943
npm install express
4044
```
4145

46+
### Add App Configuration provider
47+
48+
You will load the feature flag you created in the prerequisites from Azure App Configuration and create a `FeatureManager` to manage the feature flag.
49+
4250
1. Create a new file named *app.js* and add the following code.
4351

4452
```js
4553
const express = require("express");
46-
const app = express();
54+
const server = express();
55+
const port = "8080";
4756
4857
const appConfigEndpoint = process.env.AZURE_APPCONFIG_ENDPOINT;
4958
const { DefaultAzureCredential } = require("@azure/identity");
5059
const { load } = require("@azure/app-configuration-provider");
51-
const { FeatureManager, ConfigurationMapFeatureFlagProvider } = require("@microsoft/feature-management");
5260
5361
let appConfig;
5462
let featureManager;
@@ -65,45 +73,46 @@ In this section, you create a web application that uses the *Beta* feature flag
6573
6674
featureManager = new FeatureManager(new ConfigurationMapFeatureFlagProvider(appConfig));
6775
}
76+
```
77+
78+
### Use the feature flag
6879
69-
function buildContent(title, message) {
70-
return `
80+
1. Add the following code to the *app.js* file. You add a middleware to refresh configuration and configure the route handler. The server will serve different contents based on whether the **Beta** feature flag is enabled.
81+
82+
```js
83+
// Use a middleware to refresh the configuration before each request.
84+
server.use((req, res, next) => {
85+
appConfig.refresh();
86+
next();
87+
});
88+
89+
server.get("/", async (req, res) => {
90+
const isBetaEnabled = await featureManager.isEnabled("Beta");
91+
const [title, message] = isBetaEnabled
92+
? ["Beta Page", "This is a beta page."]
93+
: ["Home Page", "Welcome."];
94+
95+
res.send(
96+
`<!DOCTYPE html>
7197
<html>
7298
<head><title>${title}</title></head>
73-
<body style="display: flex; justify-content: center; align-items: center;">
74-
<h1 style="text-align: center; font-size: 5.0rem">${message}</h1>
99+
<body style="display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0;">
100+
<h1 style="text-align: center; font-size: 5rem;">${message}</h1>
75101
</body>
76-
</html>
77-
`;
78-
}
79-
80-
function startServer() {
81-
// Use a middleware to refresh the configuration before each request
82-
app.use((req, res, next) => {
83-
appConfig.refresh();
84-
next();
85-
});
86-
87-
app.get("/", async (req, res) => {
88-
const { userId, groups } = req.query;
89-
const isBetaEnabled = await featureManager.isEnabled("Beta", { userId: userId, groups: groups ? groups.split(",") : [] });
90-
91-
res.send(isBetaEnabled ?
92-
buildContent("Beta Page", "This is a beta page.") :
93-
buildContent("Home Page", "Welcome.")
94-
);
95-
});
102+
</html>`
103+
);
104+
});
105+
```
96106
97-
const port = "8080";
98-
app.listen(port, () => {
99-
console.log(`Server is running at http://localhost:${port}`);
100-
});
101-
}
107+
1. Complete the application. It will initialize the configuration and then start the express server.
102108
103-
// Initialize the configuration and start the server
109+
```js
104110
initializeConfig()
105111
.then(() => {
106-
startServer();
112+
// Start the express server.
113+
server.listen(port, () => {
114+
console.log(`Server is running at http://localhost:${port}`);
115+
});
107116
})
108117
.catch((error) => {
109118
console.error("Failed to load configuration:", error);
@@ -113,33 +122,36 @@ In this section, you create a web application that uses the *Beta* feature flag
113122
114123
## Enable targeting for the web application
115124
116-
A targeting context is required for feature evaluation with targeting. You can provide it as a parameter to the `featureManager.isEnabled` API explicitly. In the tutorial, we extract user information from query parameters of the request URL for simplicity.
125+
A targeting context is required for feature evaluation with targeting. To explicitly specify the targeting context for each feature evaluation, you can pass targeting context as a parameter to the `featureManager.isEnabled` call.
126+
127+
```js
128+
const isBetaEnabled = await featureManager.isEnabled("Beta", { userId: "UserA", groups: ["Group1"] });
129+
```
117130
118-
### Targeting Context Accessor
131+
In the web application, the targeting context can also be provided as an ambient context by implementing the [ITargetingContextAccessor](./feature-management-javascript-reference.md#itargetingcontextaccessor) interface. Ambient targeting context means the targeting information is automatically retrieved from the environment (like the current HTTP request) without explicitly passing it to each `featureManager.isEnabled()` call.
119132
120-
In the web application, the targeting context can also be provided as an ambient context by implementing the [ITargetingContextAccessor](./feature-management-javascript-reference.md#itargetingcontextaccessor) interface.
133+
We will use ambient targeting context as an example in this tutorial.
121134
122-
1. Add the following code to your application:
135+
1. Add the following code after where you declare the express server.
123136
124137
```js
125-
// ...
126-
// existing code
127-
const app = express();
138+
const express = require("express");
139+
const server = express();
128140
129141
const { AsyncLocalStorage } = require("async_hooks");
130142
const requestAccessor = new AsyncLocalStorage();
131-
// Use a middleware to store request context
132-
app.use((req, res, next) => {
133-
// Store the request in AsyncLocalStorage for this request chain
143+
// Use a middleware to store request context.
144+
server.use((req, res, next) => {
145+
// Store the request in AsyncLocalStorage for this request chain.
134146
requestAccessor.run(req, () => {
135147
next();
136148
});
137149
});
138150
139-
// Create a targeting context accessor that retrieves user data from the current request
151+
// Create a targeting context accessor that retrieves user data from the current request.
140152
const targetingContextAccessor = {
141153
getTargetingContext: () => {
142-
// Get the current request from AsyncLocalStorage
154+
// Get the current request from AsyncLocalStorage.
143155
const request = requestAccessor.getStore();
144156
if (!request) {
145157
return undefined;
@@ -151,35 +163,114 @@ In the web application, the targeting context can also be provided as an ambient
151163
};
152164
}
153165
};
154-
155-
// existing code
156-
// ...
166+
// Existing code ...
157167
```
158168
159-
1. When constructing the `FeatureManager`, pass the targeting cotnext accessor to the `FeatureManagerOptions`.
169+
For demonstration purposes, in this tutorial, you extract user information from the query string of the request URL. In a real-world application, user information is typically obtained through authentication mechanisms.
170+
171+
For more information, go to [Using AsyncLocalStorage for request context](./feature-management-javascript-reference.md#using-asynclocalstorage-for-request-context).
172+
173+
1. When constructing the `FeatureManager`, pass the targeting context accessor to the `FeatureManagerOptions`.
160174
161175
```js
162176
featureManager = new FeatureManager(featureFlagProvider, {
163177
targetingContextAccessor: targetingContextAccessor
164178
});
165179
```
166180
167-
1. Instead of explicitly passing the targeting context with each `isEnabled` call, the feature manager will retrieve the current user's targeting information from the targeting context accessor.
181+
Your complete application code should look like the following:
168182
169-
```js
170-
app.get("/", async (req, res) => {
171-
const beta = await featureManager.isEnabled("Beta");
172-
if (beta) {
173-
// existing code
174-
// ...
175-
} else {
176-
// existing code
177-
// ...
183+
```js
184+
const express = require("express");
185+
const server = express();
186+
const port = 8080;
187+
188+
const { AsyncLocalStorage } = require("async_hooks");
189+
const requestAccessor = new AsyncLocalStorage();
190+
// Use a middleware to store request context
191+
server.use((req, res, next) => {
192+
// Store the request in AsyncLocalStorage for this request chain
193+
requestAccessor.run(req, () => {
194+
next();
195+
});
196+
});
197+
198+
// Create a targeting context accessor that retrieves user data from the current request
199+
const targetingContextAccessor = {
200+
getTargetingContext: () => {
201+
// Get the current request from AsyncLocalStorage
202+
const request = requestAccessor.getStore();
203+
if (!request) {
204+
return undefined;
205+
}
206+
const { userId, groups } = request.query;
207+
return {
208+
userId: userId,
209+
groups: groups ? groups.split(",") : []
210+
};
211+
}
212+
};
213+
214+
const appConfigEndpoint = process.env.AZURE_APPCONFIG_ENDPOINT;
215+
const { DefaultAzureCredential } = require("@azure/identity");
216+
const { load } = require("@azure/app-configuration-provider");
217+
const { FeatureManager, ConfigurationMapFeatureFlagProvider } = require("@microsoft/feature-management");
218+
219+
let appConfig;
220+
let featureManager;
221+
222+
async function initializeConfig() {
223+
appConfig = await load(appConfigEndpoint, new DefaultAzureCredential(), {
224+
featureFlagOptions: {
225+
enabled: true,
226+
refresh: {
227+
enabled: true
178228
}
229+
}
230+
});
231+
232+
featureManager = new FeatureManager(new ConfigurationMapFeatureFlagProvider(appConfig), {
233+
targetingContextAccessor: targetingContextAccessor
234+
});
235+
}
236+
237+
// Use a middleware to refresh the configuration before each request
238+
server.use((req, res, next) => {
239+
appConfig.refresh();
240+
next();
241+
});
242+
243+
server.get("/", async (req, res) => {
244+
const isBetaEnabled = await featureManager.isEnabled("Beta");
245+
const [title, message] = isBetaEnabled
246+
? ["Beta Page", "This is a beta page."]
247+
: ["Home Page", "Welcome."];
248+
249+
res.send(
250+
`<!DOCTYPE html>
251+
<html>
252+
<head><title>${title}</title></head>
253+
<body style="display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0;">
254+
<h1 style="text-align: center; font-size: 5rem;">${message}</h1>
255+
</body>
256+
</html>`
257+
);
258+
});
259+
260+
// Initialize the configuration and start the server
261+
initializeConfig()
262+
.then(() => {
263+
// Start the express server.
264+
server.listen(port, () => {
265+
console.log(`Server is running at http://localhost:${port}`);
179266
});
180-
```
267+
})
268+
.catch((error) => {
269+
console.error("Failed to load configuration:", error);
270+
process.exit(1);
271+
});
272+
```
181273
182-
For more information, go to [Using AsyncLocalStorage for request context](./feature-management-javascript-reference.md#using-asynclocalstorage-for-request-context).
183274
184275
## Targeting filter in action
185276

0 commit comments

Comments
 (0)