Skip to content

Commit 4934bf4

Browse files
update readme
1 parent e9fc6a7 commit 4934bf4

File tree

7 files changed

+156
-12
lines changed

7 files changed

+156
-12
lines changed

examples/quote-of-the-day/.env.temlate

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
# This is a template of related environment variables in examples.
33
# To use this file directly, please rename it to .env
44
APPCONFIG_CONNECTION_STRING=<app-configuration-connection-string>
5-
APPLICATIONINSIGHTS_CONNECTION_STRING=<application-insights-connection-string>
5+
APPLICATIONINSIGHTS_CONNECTION_STRING=<application-insights-connection-string>
6+
USE_APP_CONFIG=true

examples/quote-of-the-day/README.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,142 @@ npm run build
1414

1515
```cmd
1616
npm run start
17+
```
18+
19+
## Telemetry
20+
21+
The Quote of the Day example implements telemetry using Azure Application Insights to track feature flag evaluations. This helps monitor and analyze how feature flags are being used in your application.
22+
23+
### Application Insights Integration
24+
25+
The application uses the `@microsoft/feature-management-applicationinsights-node` package to integrate Feature Management with Application Insights:
26+
27+
```javascript
28+
const { createTelemetryPublisher } = require("@microsoft/feature-management-applicationinsights-node");
29+
30+
// When initializing Feature Management
31+
const publishTelemetry = createTelemetryPublisher(appInsightsClient);
32+
featureManager = new FeatureManager(featureFlagProvider, {
33+
onFeatureEvaluated: publishTelemetry,
34+
targetingContextAccessor: targetingContextAccessor
35+
});
36+
```
37+
38+
The `onFeatureEvaluated` option registers a callback that automatically sends telemetry events to Application Insights whenever a feature flag is evaluated.
39+
40+
### Targeting Context in Telemetry
41+
42+
The telemetry implementation also captures the targeting context, which includes user ID and groups, in the telemetry data:
43+
44+
```javascript
45+
// Initialize Application Insights with targeting context
46+
applicationInsights.defaultClient.addTelemetryProcessor(
47+
createTargetingTelemetryProcessor(targetingContextAccessor)
48+
);
49+
```
50+
51+
This ensures that every telemetry event sent to Application Insights includes the targeting identity information, allowing you to correlate feature flag usage with specific users or groups in your analytics.
52+
53+
### Experimentation and A/B Testing
54+
55+
Telemetry is particularly valuable for running experiments like A/B tests. Here's how you can use telemetry to track whether different variants of a feature influence user behavior.
56+
57+
In this example, a variant feature flag is used to track the like button click rate of a web application:
58+
59+
```json
60+
{
61+
"id": "Greeting",
62+
"enabled": true,
63+
"variants": [
64+
{
65+
"name": "Default"
66+
},
67+
{
68+
"name": "Simple",
69+
"configuration_value": "Hello!"
70+
},
71+
{
72+
"name": "Long",
73+
"configuration_value": "I hope this makes your day!"
74+
}
75+
],
76+
"allocation": {
77+
"percentile": [
78+
{
79+
"variant": "Default",
80+
"from": 0,
81+
"to": 50
82+
},
83+
{
84+
"variant": "Simple",
85+
"from": 50,
86+
"to": 75
87+
},
88+
{
89+
"variant": "Long",
90+
"from": 75,
91+
"to": 100
92+
}
93+
],
94+
"default_when_enabled": "Default",
95+
"default_when_disabled": "Default"
96+
},
97+
"telemetry": {
98+
"enabled": true
99+
}
100+
}
101+
```
102+
103+
## Targeting
104+
105+
The targeting mechanism uses the `exampleTargetingContextAccessor` to extract the targeting context from the request. This function retrieves the userId and groups from the query parameters of the request.
106+
107+
```javascript
108+
const targetingContextAccessor = {
109+
getTargetingContext: () => {
110+
const req = requestAccessor.getStore();
111+
if (req === undefined) {
112+
return undefined;
113+
}
114+
// read user and groups from request
115+
const userId = req.query.userId ?? req.body.userId;
116+
const groups = req.query.groups ?? req.body.groups;
117+
// return an ITargetingContext with the appropriate user info
118+
return { userId: userId, groups: groups ? groups.split(",") : [] };
119+
}
120+
};
121+
```
122+
123+
The `FeatureManager` is configured with this targeting context accessor:
124+
125+
```javascript
126+
const featureManager = new FeatureManager(
127+
featureProvider,
128+
{
129+
targetingContextAccessor: exampleTargetingContextAccessor
130+
}
131+
);
132+
```
133+
134+
This allows you to get ambient targeting context while doing feature flag evaluation and variant allocation.
135+
136+
### Request Accessor
137+
138+
The `requestAccessor` is an instance of `AsyncLocalStorage` from the `async_hooks` module. It is used to store the request object in asynchronous local storage, allowing it to be accessed throughout the lifetime of the request. This is particularly useful for accessing request-specific data in asynchronous operations. For more information, please go to https://nodejs.org/api/async_context.html
139+
140+
```javascript
141+
import { AsyncLocalStorage } from "async_hooks";
142+
const requestAccessor = new AsyncLocalStorage();
143+
```
144+
145+
Middleware is used to store the request object in the AsyncLocalStorage:
146+
147+
```javascript
148+
const requestStorageMiddleware = (req, res, next) => {
149+
requestAccessor.run(req, next);
150+
};
151+
152+
...
153+
154+
server.use(requestStorageMiddleware);
17155
```

examples/quote-of-the-day/config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@
33

44
require("dotenv").config();
55

6+
const fs = require('fs/promises');
7+
const localFeatureFlags = JSON.parse(await fs.readFile("localFeatureFlags.json"));
8+
69
// Export configuration variables
710
module.exports = {
11+
localFeatureFlags,
812
appConfigConnectionString: process.env.APPCONFIG_CONNECTION_STRING,
913
appInsightsConnectionString: process.env.APPLICATIONINSIGHTS_CONNECTION_STRING,
1014
port: process.env.PORT || "8080"

examples/quote-of-the-day/featureManagement.js

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

44
const { load } = require("@azure/app-configuration-provider");
5-
const { FeatureManager, ConfigurationMapFeatureFlagProvider } = require("@microsoft/feature-management");
5+
const { FeatureManager, ConfigurationMapFeatureFlagProvider, ConfigurationObjectFeatureFlagProvider } = require("@microsoft/feature-management");
66
const { createTelemetryPublisher } = require("@microsoft/feature-management-applicationinsights-node");
77
const config = require("./config");
88

@@ -27,8 +27,11 @@ async function initializeFeatureManagement(appInsightsClient, targetingContextAc
2727
}
2828
}
2929
});
30-
3130
const featureFlagProvider = new ConfigurationMapFeatureFlagProvider(appConfig);
31+
32+
// You can also alternatively use local feature flag source.
33+
// const featureFlagProvider = new ConfigurationObjectFeatureFlagProvider(config.localFeatureFlags);
34+
3235
const publishTelemetry = createTelemetryPublisher(appInsightsClient);
3336
featureManager = new FeatureManager(featureFlagProvider, {
3437
onFeatureEvaluated: publishTelemetry,
@@ -39,7 +42,7 @@ async function initializeFeatureManagement(appInsightsClient, targetingContextAc
3942
}
4043

4144
// Middleware to refresh configuration before each request
42-
const configRefreshMiddleware = (req, res, next) => {
45+
const featureFlagRefreshMiddleware = (req, res, next) => {
4346
// The configuration refresh happens asynchronously to the processing of your app's incoming requests.
4447
// It will not block or slow down the incoming request that triggered the refresh.
4548
// The request that triggered the refresh may not get the updated configuration values, but later requests will get new configuration values.
@@ -49,5 +52,5 @@ const configRefreshMiddleware = (req, res, next) => {
4952

5053
module.exports = {
5154
initializeFeatureManagement,
52-
configRefreshMiddleware
55+
featureFlagRefreshMiddleware
5356
};

examples/quote-of-the-day/package.json

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@
22
"name": "quoteoftheday",
33
"scripts": {
44
"build-client": "cd client && npm install && npm run build",
5-
"build-fm": "cd ../../src/feature-management && npm install && npm run build",
6-
"build-fmai": "cd ../../src/feature-management-applicationinsights-node && npm install && npm run build",
7-
"build": "npm run build-fm && npm run build-fmai && npm install && npm run build-client",
5+
"build": "npm install && npm run build-client",
86
"start": "node server.js"
97
},
108
"dependencies": {
119
"@azure/app-configuration-provider": "latest",
12-
"@microsoft/feature-management": "../../src/feature-management",
13-
"@microsoft/feature-management-applicationinsights-node": "../../src/feature-management-applicationinsights-node",
10+
"@microsoft/feature-management": "2.1.0-preview.1",
11+
"@microsoft/feature-management-applicationinsights-node": "2.1.0-preview.1",
1412
"applicationinsights": "^2.9.6",
1513
"dotenv": "^16.5.0",
1614
"express": "^4.19.2"

examples/quote-of-the-day/server.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const config = require("./config");
66
const express = require("express");
77
const { targetingContextAccessor, requestStorageMiddleware } = require("./targetingContextAccessor");
88
const { initializeAppInsights } = require("./telemetry");
9-
const { initializeFeatureManagement, configRefreshMiddleware } = require("./featureManagement");
9+
const { initializeFeatureManagement, featureFlagRefreshMiddleware } = require("./featureManagement");
1010
const { initializeRoutes } = require("./routes");
1111

1212
// Initialize Express server
@@ -32,7 +32,7 @@ async function startApp() {
3232

3333
// Set up middleware
3434
server.use(requestStorageMiddleware);
35-
server.use(configRefreshMiddleware);
35+
server.use(featureFlagRefreshMiddleware);
3636
server.use(express.json());
3737
server.use(express.static("public"));
3838

0 commit comments

Comments
 (0)