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
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# If there are abnormal line endings in any file, run "git add --renormalize <file_name>",
# review the changes, and commit them to fix the line endings.
* text=auto
140 changes: 70 additions & 70 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,70 +1,70 @@
name: FeatureManagement-JavaScript CI
on:
push:
branches:
- main
- preview
- release/*
pull_request:
branches:
- main
- preview
- release/*
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x]
defaults:
run:
working-directory: src/feature-management
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
cache-dependency-path: src/feature-management/package-lock.json
- name: Install dependencies
run: npm ci
working-directory: src/feature-management
- name: Run lint check for feature-management
run: npm run lint
working-directory: src/feature-management
- name: Build feature-management
run: npm run build
working-directory: src/feature-management
- name: Run tests
run: npm run test
working-directory: src/feature-management
- name: Run browser tests
run: npm run test-browser
working-directory: src/feature-management
- name: Build feature-management-applicationinsights-browser
run: npm run build
working-directory: src/feature-management-applicationinsights-browser
- name: Run lint check for feature-management-applicationinsights-browser
run: npm run lint
working-directory: src/feature-management-applicationinsights-browser
- name: Build feature-management-applicationinsights-node
run: npm run build
working-directory: src/feature-management-applicationinsights-node
- name: Run lint check for feature-management-applicationinsights-node
run: npm run lint
working-directory: src/feature-management-applicationinsights-node
name: FeatureManagement-JavaScript CI

on:
push:
branches:
- main
- preview
- release/*
pull_request:
branches:
- main
- preview
- release/*

jobs:
build:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [18.x, 20.x]

defaults:
run:
working-directory: src/feature-management

steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
cache-dependency-path: src/feature-management/package-lock.json

- name: Install dependencies
run: npm ci
working-directory: src/feature-management

- name: Run lint check for feature-management
run: npm run lint
working-directory: src/feature-management

- name: Build feature-management
run: npm run build
working-directory: src/feature-management

- name: Run tests
run: npm run test
working-directory: src/feature-management

- name: Run browser tests
run: npm run test-browser
working-directory: src/feature-management

- name: Build feature-management-applicationinsights-browser
run: npm run build
working-directory: src/feature-management-applicationinsights-browser

- name: Run lint check for feature-management-applicationinsights-browser
run: npm run lint
working-directory: src/feature-management-applicationinsights-browser

- name: Build feature-management-applicationinsights-node
run: npm run build
working-directory: src/feature-management-applicationinsights-node

- name: Run lint check for feature-management-applicationinsights-node
run: npm run lint
working-directory: src/feature-management-applicationinsights-node
22 changes: 11 additions & 11 deletions SUPPORT.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Support
## How to file issues and get help
This project uses GitHub Issues to track bugs and feature requests. Please search the existing
issues before filing new issues to avoid duplicates. For new issues, file your bug or
feature request as a new Issue.
## Microsoft Support Policy
Support for this project is limited to the resources listed above.
# Support

## How to file issues and get help

This project uses GitHub Issues to track bugs and feature requests. Please search the existing
issues before filing new issues to avoid duplicates. For new issues, file your bug or
feature request as a new Issue.

## Microsoft Support Policy

Support for this project is limited to the resources listed above.
12 changes: 4 additions & 8 deletions examples/express-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,7 @@ The examples are compatible with [LTS versions of Node.js](https://github.com/no

## Setup & Run

1. Go to `src/feature-management` under the root folder and run:

```bash
npm run install
npm run build
```

1. Go back to `examples/express-app` and install the dependencies using `npm`:
1. Install the dependencies using `npm`:

```bash
npm install
Expand All @@ -41,6 +34,9 @@ The targeting mechanism uses the `exampleTargetingContextAccessor` to extract th
const exampleTargetingContextAccessor = {
getTargetingContext: () => {
const req = requestAccessor.getStore();
if (req === undefined) {
return undefined;
}
// read user and groups from request query data
const { userId, groups } = req.query;
// return aa ITargetingContext with the appropriate user info
Expand Down
2 changes: 1 addition & 1 deletion examples/express-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"start": "node server.mjs"
},
"dependencies": {
"@microsoft/feature-management": "../../src/feature-management",
"@microsoft/feature-management": "2.1.0",
"express": "^4.21.2"
}
}
3 changes: 3 additions & 0 deletions examples/express-app/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ const requestAccessor = new AsyncLocalStorage();
const exampleTargetingContextAccessor = {
getTargetingContext: () => {
const req = requestAccessor.getStore();
if (req === undefined) {
return undefined;
}
// read user and groups from request query data
const { userId, groups } = req.query;
// return an ITargetingContext with the appropriate user info
Expand Down
6 changes: 6 additions & 0 deletions examples/quote-of-the-day/.env.temlate
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# You can define environment variables in .env file and load them with 'dotenv' package.
# This is a template of related environment variables in examples.
# To use this file directly, please rename it to .env
APPCONFIG_CONNECTION_STRING=<app-configuration-connection-string>
APPLICATIONINSIGHTS_CONNECTION_STRING=<application-insights-connection-string>
USE_APP_CONFIG=true
155 changes: 155 additions & 0 deletions examples/quote-of-the-day/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Quote of the day - JavaScript

These examples show how to use the Microsoft Feature Management in an express application.

## Setup & Run

1. Build the project.

```cmd
npm run build
```

1. Start the application.

```cmd
npm run start
```

## Telemetry

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.

### Application Insights Integration

The application uses the `@microsoft/feature-management-applicationinsights-node` package to integrate Feature Management with Application Insights:

```javascript
const { createTelemetryPublisher } = require("@microsoft/feature-management-applicationinsights-node");

// When initializing Feature Management
const publishTelemetry = createTelemetryPublisher(appInsightsClient);
featureManager = new FeatureManager(featureFlagProvider, {
onFeatureEvaluated: publishTelemetry,
targetingContextAccessor: targetingContextAccessor
});
```

The `onFeatureEvaluated` option registers a callback that automatically sends telemetry events to Application Insights whenever a feature flag is evaluated.

### Targeting Context in Telemetry

`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.

```javascript
// Initialize Application Insights with targeting context
applicationInsights.defaultClient.addTelemetryProcessor(
createTargetingTelemetryProcessor(targetingContextAccessor)
);
```

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.

### Experimentation and A/B Testing

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.

In this example, a variant feature flag is used to track the like button click rate of a web application:

```json
{
"id": "Greeting",
"enabled": true,
"variants": [
{
"name": "Default"
},
{
"name": "Simple",
"configuration_value": "Hello!"
},
{
"name": "Long",
"configuration_value": "I hope this makes your day!"
}
],
"allocation": {
"percentile": [
{
"variant": "Default",
"from": 0,
"to": 50
},
{
"variant": "Simple",
"from": 50,
"to": 75
},
{
"variant": "Long",
"from": 75,
"to": 100
}
],
"default_when_enabled": "Default",
"default_when_disabled": "Default"
},
"telemetry": {
"enabled": true
}
}
```

## Targeting

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.

```javascript
const targetingContextAccessor = {
getTargetingContext: () => {
const req = requestAccessor.getStore();
if (req === undefined) {
return undefined;
}
// read user and groups from request
const userId = req.query.userId ?? req.body.userId;
const groups = req.query.groups ?? req.body.groups;
// return an ITargetingContext with the appropriate user info
return { userId: userId, groups: groups ? groups.split(",") : [] };
}
};
```

The `FeatureManager` is configured with this targeting context accessor:

```javascript
const featureManager = new FeatureManager(
featureProvider,
{
targetingContextAccessor: exampleTargetingContextAccessor
}
);
```

This allows you to get ambient targeting context while doing feature flag evaluation and variant allocation.

### Request Accessor

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

```javascript
import { AsyncLocalStorage } from "async_hooks";
const requestAccessor = new AsyncLocalStorage();
```

Middleware is used to store the request object in the AsyncLocalStorage:

```javascript
const requestStorageMiddleware = (req, res, next) => {
requestAccessor.run(req, next);
};

...

server.use(requestStorageMiddleware);
```
13 changes: 13 additions & 0 deletions examples/quote-of-the-day/client/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Quote of the Day</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.jsx"></script>
</body>
</html>
Loading