Skip to content

Commit ae74dfb

Browse files
Merge pull request #121 from microsoft/main
Merge main to release/v2
2 parents 301d618 + 80abf6b commit ae74dfb

File tree

35 files changed

+1139
-110
lines changed

35 files changed

+1139
-110
lines changed

.gitattributes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# If there are abnormal line endings in any file, run "git add --renormalize <file_name>",
2+
# review the changes, and commit them to fix the line endings.
3+
* text=auto

.github/workflows/ci.yml

Lines changed: 70 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,70 @@
1-
name: FeatureManagement-JavaScript CI
2-
3-
on:
4-
push:
5-
branches:
6-
- main
7-
- preview
8-
- release/*
9-
pull_request:
10-
branches:
11-
- main
12-
- preview
13-
- release/*
14-
15-
jobs:
16-
build:
17-
runs-on: ubuntu-latest
18-
19-
strategy:
20-
matrix:
21-
node-version: [18.x, 20.x]
22-
23-
defaults:
24-
run:
25-
working-directory: src/feature-management
26-
27-
steps:
28-
- uses: actions/checkout@v3
29-
- name: Use Node.js ${{ matrix.node-version }}
30-
uses: actions/setup-node@v3
31-
with:
32-
node-version: ${{ matrix.node-version }}
33-
cache: 'npm'
34-
cache-dependency-path: src/feature-management/package-lock.json
35-
36-
- name: Install dependencies
37-
run: npm ci
38-
working-directory: src/feature-management
39-
40-
- name: Run lint check for feature-management
41-
run: npm run lint
42-
working-directory: src/feature-management
43-
44-
- name: Build feature-management
45-
run: npm run build
46-
working-directory: src/feature-management
47-
48-
- name: Run tests
49-
run: npm run test
50-
working-directory: src/feature-management
51-
52-
- name: Run browser tests
53-
run: npm run test-browser
54-
working-directory: src/feature-management
55-
56-
- name: Build feature-management-applicationinsights-browser
57-
run: npm run build
58-
working-directory: src/feature-management-applicationinsights-browser
59-
60-
- name: Run lint check for feature-management-applicationinsights-browser
61-
run: npm run lint
62-
working-directory: src/feature-management-applicationinsights-browser
63-
64-
- name: Build feature-management-applicationinsights-node
65-
run: npm run build
66-
working-directory: src/feature-management-applicationinsights-node
67-
68-
- name: Run lint check for feature-management-applicationinsights-node
69-
run: npm run lint
70-
working-directory: src/feature-management-applicationinsights-node
1+
name: FeatureManagement-JavaScript CI
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- preview
8+
- release/*
9+
pull_request:
10+
branches:
11+
- main
12+
- preview
13+
- release/*
14+
15+
jobs:
16+
build:
17+
runs-on: ubuntu-latest
18+
19+
strategy:
20+
matrix:
21+
node-version: [18.x, 20.x]
22+
23+
defaults:
24+
run:
25+
working-directory: src/feature-management
26+
27+
steps:
28+
- uses: actions/checkout@v3
29+
- name: Use Node.js ${{ matrix.node-version }}
30+
uses: actions/setup-node@v3
31+
with:
32+
node-version: ${{ matrix.node-version }}
33+
cache: 'npm'
34+
cache-dependency-path: src/feature-management/package-lock.json
35+
36+
- name: Install dependencies
37+
run: npm ci
38+
working-directory: src/feature-management
39+
40+
- name: Run lint check for feature-management
41+
run: npm run lint
42+
working-directory: src/feature-management
43+
44+
- name: Build feature-management
45+
run: npm run build
46+
working-directory: src/feature-management
47+
48+
- name: Run tests
49+
run: npm run test
50+
working-directory: src/feature-management
51+
52+
- name: Run browser tests
53+
run: npm run test-browser
54+
working-directory: src/feature-management
55+
56+
- name: Build feature-management-applicationinsights-browser
57+
run: npm run build
58+
working-directory: src/feature-management-applicationinsights-browser
59+
60+
- name: Run lint check for feature-management-applicationinsights-browser
61+
run: npm run lint
62+
working-directory: src/feature-management-applicationinsights-browser
63+
64+
- name: Build feature-management-applicationinsights-node
65+
run: npm run build
66+
working-directory: src/feature-management-applicationinsights-node
67+
68+
- name: Run lint check for feature-management-applicationinsights-node
69+
run: npm run lint
70+
working-directory: src/feature-management-applicationinsights-node

SUPPORT.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
# Support
2-
3-
## How to file issues and get help
4-
5-
This project uses GitHub Issues to track bugs and feature requests. Please search the existing
6-
issues before filing new issues to avoid duplicates. For new issues, file your bug or
7-
feature request as a new Issue.
8-
9-
## Microsoft Support Policy
10-
11-
Support for this project is limited to the resources listed above.
1+
# Support
2+
3+
## How to file issues and get help
4+
5+
This project uses GitHub Issues to track bugs and feature requests. Please search the existing
6+
issues before filing new issues to avoid duplicates. For new issues, file your bug or
7+
feature request as a new Issue.
8+
9+
## Microsoft Support Policy
10+
11+
Support for this project is limited to the resources listed above.

examples/express-app/README.md

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,7 @@ The examples are compatible with [LTS versions of Node.js](https://github.com/no
88

99
## Setup & Run
1010

11-
1. Go to `src/feature-management` under the root folder and run:
12-
13-
```bash
14-
npm run install
15-
npm run build
16-
```
17-
18-
1. Go back to `examples/express-app` and install the dependencies using `npm`:
11+
1. Install the dependencies using `npm`:
1912

2013
```bash
2114
npm install
@@ -41,6 +34,9 @@ The targeting mechanism uses the `exampleTargetingContextAccessor` to extract th
4134
const exampleTargetingContextAccessor = {
4235
getTargetingContext: () => {
4336
const req = requestAccessor.getStore();
37+
if (req === undefined) {
38+
return undefined;
39+
}
4440
// read user and groups from request query data
4541
const { userId, groups } = req.query;
4642
// return aa ITargetingContext with the appropriate user info

examples/express-app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"start": "node server.mjs"
44
},
55
"dependencies": {
6-
"@microsoft/feature-management": "../../src/feature-management",
6+
"@microsoft/feature-management": "2.1.0",
77
"express": "^4.21.2"
88
}
99
}

examples/express-app/server.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ const requestAccessor = new AsyncLocalStorage();
1515
const exampleTargetingContextAccessor = {
1616
getTargetingContext: () => {
1717
const req = requestAccessor.getStore();
18+
if (req === undefined) {
19+
return undefined;
20+
}
1821
// read user and groups from request query data
1922
const { userId, groups } = req.query;
2023
// return an ITargetingContext with the appropriate user info
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# You can define environment variables in .env file and load them with 'dotenv' package.
2+
# This is a template of related environment variables in examples.
3+
# To use this file directly, please rename it to .env
4+
APPCONFIG_CONNECTION_STRING=<app-configuration-connection-string>
5+
APPLICATIONINSIGHTS_CONNECTION_STRING=<application-insights-connection-string>
6+
USE_APP_CONFIG=true

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

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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+
`createTargetingTelemetryProcessor` method creates a built-in Application Insights telemetry processor which gets targeting context from the targeting context accessor and attaches the targeting id to telemetry.
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 sent to Application Insights includes the targeting id 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+
```
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Quote of the Day</title>
8+
</head>
9+
<body>
10+
<div id="root"></div>
11+
<script type="module" src="/src/index.jsx"></script>
12+
</body>
13+
</html>

0 commit comments

Comments
 (0)