|
| 1 | +# Quote of the day - JavaScript |
| 2 | + |
| 3 | +These examples show how to use the Microsoft Feature Management in an express application. |
| 4 | + |
| 5 | +## Setup & Run |
| 6 | + |
| 7 | +1. Build the project. |
| 8 | + |
| 9 | +```cmd |
| 10 | +npm run build |
| 11 | +``` |
| 12 | + |
| 13 | +1. Start the application. |
| 14 | + |
| 15 | +```cmd |
| 16 | +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); |
| 155 | +``` |
0 commit comments