From 0ca8fa61739f0802c4d08eca631c78ead118d636 Mon Sep 17 00:00:00 2001 From: zhiyuanliang Date: Fri, 16 Aug 2024 14:53:13 +0800 Subject: [PATCH 1/5] add example --- examples/README.md | 9 +++++ examples/config.json | 62 ++++++++++++++++++++++++++++++++++ examples/featureFlagSample.mjs | 29 ++++++++++++++++ examples/package.json | 5 +++ 4 files changed, 105 insertions(+) create mode 100644 examples/README.md create mode 100644 examples/config.json create mode 100644 examples/featureFlagSample.mjs create mode 100644 examples/package.json diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..d561c5e --- /dev/null +++ b/examples/README.md @@ -0,0 +1,9 @@ +# Examples for Microsoft Feature Management for JavaScript + +These examples show how to use the Microsoft Feature Management in some common scenarios. + +## Prerequisites + +The examples are compatible with [LTS versions of Node.js](https://github.com/nodejs/release#release-schedule). + +The examples reference the local `@microsoft/feature-management` implemented in the `src` folder. To run the example programs, please run `npm run build` in the root folder first. \ No newline at end of file diff --git a/examples/config.json b/examples/config.json new file mode 100644 index 0000000..702abcd --- /dev/null +++ b/examples/config.json @@ -0,0 +1,62 @@ +{ + "feature_management": { + "feature_flags": [ + { + "id": "FeatureX", + "enabled": true + }, + { + "id": "FeatureY", + "enabled": false + }, + { + "id": "FeatureFlagWithTimeWindowFilter", + "enabled": true, + "conditions": { + "client_filters": [ + { + "name": "Microsoft.TimeWindow", + "parameters": { + "Start": "Thu, 15 Aug 2024 00:00:00 GMT", + "End": "Mon, 19 Aug 2024 00:00:00 GMT" + } + } + ] + } + }, + { + "id": "FeatureFlagWithTargetingFilter", + "enabled": true, + "conditions": { + "client_filters": [ + { + "name": "Microsoft.Targeting", + "parameters": { + "Audience": { + "Users": [ + "Jeff" + ], + "Groups": [ + { + "Name": "Admin", + "RolloutPercentage": 100 + } + ], + "DefaultRolloutPercentage": 40, + "Exclusion": { + "Users": [ + "Anne" + ], + "Groups": [ + "Guest" + ] + } + } + } + } + ] + } + } + ] + } +} \ No newline at end of file diff --git a/examples/featureFlagSample.mjs b/examples/featureFlagSample.mjs new file mode 100644 index 0000000..3de5e9e --- /dev/null +++ b/examples/featureFlagSample.mjs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import fs from 'node:fs/promises'; +import { ConfigurationObjectFeatureFlagProvider, FeatureManager } from "@microsoft/feature-management"; + +const config = JSON.parse(await fs.readFile("config.json")); +const featureProvider = new ConfigurationObjectFeatureFlagProvider(config); +const featureManager = new FeatureManager(featureProvider); + +console.log("FeatureX is:", await featureManager.isEnabled("FeatureX")); +console.log("FeatureY is:", await featureManager.isEnabled("FeatureY")); + +// Is true between 2024-8-15 ~ 2024-8-19 +console.log("Feature flag with Time WindoW Filter is:", await featureManager.isEnabled("FeatureFlagWithTimeWindowFilter")); + +// Targeted by Users +console.log("Feature flag with Targeting Filter is:", await featureManager.isEnabled("FeatureFlagWithTargetingFilter", {userId: "Jeff"})) +// Excluded by Users +console.log("Feature flag with Targeting Filter is:", await featureManager.isEnabled("FeatureFlagWithTargetingFilter", {userId: "Anne"})) +// Targeted by Groups Admin +console.log("Feature flag with Targeting Filter is:", await featureManager.isEnabled("FeatureFlagWithTargetingFilter", {userId: "Admin1", groups: ["Admin"]})) +// Excluded by Groups Guest +console.log("Feature flag with Targeting Filter is:", await featureManager.isEnabled("FeatureFlagWithTargetingFilter", {userId: "Guest1", groups: ["Guest"]})) + +// Targeted by default rollout percentage +console.log("Feature flag with Targeting Filter is:", await featureManager.isEnabled("FeatureFlagWithTargetingFilter", {userId: "Alicia"})) +console.log("Feature flag with Targeting Filter is:", await featureManager.isEnabled("FeatureFlagWithTargetingFilter", {userId: "Susan"})) +console.log("Feature flag with Targeting Filter is:", await featureManager.isEnabled("FeatureFlagWithTargetingFilter", {userId: "John"})) diff --git a/examples/package.json b/examples/package.json new file mode 100644 index 0000000..f40a0d1 --- /dev/null +++ b/examples/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@microsoft/feature-management": "../" + } +} From af3e64737460416f8f1866fd9c290e5fa82fe43e Mon Sep 17 00:00:00 2001 From: zhiyuanliang Date: Sat, 17 Aug 2024 16:56:29 +0800 Subject: [PATCH 2/5] add example for app config --- examples/.env.template | 4 ++ examples/README.md | 25 +++++++++++- examples/featureFlagSampleWithAppConfig.mjs | 25 ++++++++++++ examples/package.json | 5 ++- examples/server.mjs | 44 +++++++++++++++++++++ 5 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 examples/.env.template create mode 100644 examples/featureFlagSampleWithAppConfig.mjs create mode 100644 examples/server.mjs diff --git a/examples/.env.template b/examples/.env.template new file mode 100644 index 0000000..e9a4dab --- /dev/null +++ b/examples/.env.template @@ -0,0 +1,4 @@ +# 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= \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index d561c5e..4230f34 100644 --- a/examples/README.md +++ b/examples/README.md @@ -6,4 +6,27 @@ These examples show how to use the Microsoft Feature Management in some common s The examples are compatible with [LTS versions of Node.js](https://github.com/nodejs/release#release-schedule). -The examples reference the local `@microsoft/feature-management` implemented in the `src` folder. To run the example programs, please run `npm run build` in the root folder first. \ No newline at end of file +Some examples use `@azure/app-configuration-provider` to load feature flags from the [Azure App Configuration](https://learn.microsoft.com/azure/azure-app-configuration/overview). Azure App Configuration provides a service to centrally manage application settings and feature flags. The examples retrieve credentials to access your App Configuration store from environment variables. Edit the file `.env.template`, adding the access keys to your App Configuration store. and rename the file from `.env.template` to just `.env`. The examples will read this file automatically. + +## Setup & Run + +1. Build the feature management package in the root folder. + + ``` bash + npm install + npm run build + ``` + +The examples reference the local `@microsoft/feature-management` package implemented in the `src` folder. Before running the example programs, make sure that you have built it. + +1. Go to the `examples` folder. Install the dependencies using `npm`: + + ``` bash + npm install + ``` + +1. Run the examples: + + ``` bash + node featureFlagSample.mjs + ``` diff --git a/examples/featureFlagSampleWithAppConfig.mjs b/examples/featureFlagSampleWithAppConfig.mjs new file mode 100644 index 0000000..57598cf --- /dev/null +++ b/examples/featureFlagSampleWithAppConfig.mjs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import * as dotenv from "dotenv"; +dotenv.config() + +import { load } from "@azure/app-configuration-provider"; +const connectionString = process.env.APPCONFIG_CONNECTION_STRING; +const appConfig = await load(connectionString, { + featureFlagOptions: { + enabled: true, + selectors: [{ + keyFilter: "*" + }], + refresh: { + enabled: true + } + } +}); + +import { ConfigurationMapFeatureFlagProvider, FeatureManager } from "@microsoft/feature-management"; +const featureProvider = new ConfigurationMapFeatureFlagProvider(appConfig); +const featureManager = new FeatureManager(featureProvider); + +console.log("Feature Beta is:", await featureManager.isEnabled("Beta")); diff --git a/examples/package.json b/examples/package.json index f40a0d1..590585f 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,5 +1,8 @@ { "dependencies": { - "@microsoft/feature-management": "../" + "@azure/app-configuration-provider": "latest", + "@microsoft/feature-management": "../", + "dotenv": "^16.3.1", + "express": "^4.19.2" } } diff --git a/examples/server.mjs b/examples/server.mjs new file mode 100644 index 0000000..cfa55f1 --- /dev/null +++ b/examples/server.mjs @@ -0,0 +1,44 @@ +import express from 'express'; + +const app = express(); +const port = 3000; + +import * as dotenv from "dotenv"; +dotenv.config() + +import { load } from "@azure/app-configuration-provider"; +const connectionString = process.env.APPCONFIG_CONNECTION_STRING; +const appConfig = await load(connectionString, { + featureFlagOptions: { + enabled: true, + selectors: [{ + keyFilter: "*" + }], + refresh: { + enabled: true, + refreshIntervalInMs: 10_000 + } + } +}); + +import { ConfigurationMapFeatureFlagProvider, FeatureManager } from "@microsoft/feature-management"; +const featureProvider = new ConfigurationMapFeatureFlagProvider(appConfig); +const featureManager = new FeatureManager(featureProvider); + +app.get('/', (req, res) => { + res.send('Welcome to the original feature!'); +}); + +app.get('/beta', async (req, res) => { + appConfig.refresh(); + if (await featureManager.isEnabled("Beta")) { + res.send('Welcome to the new Beta feature!'); + } else { + res.status(404).send(); + } +}); + + +app.listen(port, () => { + console.log(`Server is running on http://localhost:${port}`); +}); \ No newline at end of file From 94553607263e357136b4d050e9938890f3c18274 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Mon, 26 Aug 2024 16:53:14 +0800 Subject: [PATCH 3/5] add web app example --- .gitignore | 2 +- README.md | 2 +- examples/.env.template | 4 - examples/{ => console-app}/README.md | 4 +- examples/{ => console-app}/config.json | 0 .../{ => console-app}/featureFlagSample.mjs | 0 .../featureFlagSampleWithAppConfig.mjs | 8 +- examples/{ => console-app}/package.json | 3 +- examples/express-react/README.md | 0 examples/express-react/client/.gitignore | 24 ++++++ examples/express-react/client/README.md | 8 ++ examples/express-react/client/index.html | 13 +++ examples/express-react/client/package.json | 23 +++++ examples/express-react/client/public/vite.svg | 1 + examples/express-react/client/src/App.css | 42 ++++++++++ examples/express-react/client/src/App.jsx | 83 +++++++++++++++++++ .../express-react/client/src/assets/react.svg | 1 + examples/express-react/client/src/index.css | 68 +++++++++++++++ examples/express-react/client/src/main.jsx | 10 +++ examples/express-react/client/vite.config.js | 7 ++ examples/express-react/package.json | 11 +++ examples/express-react/server/package.json | 15 ++++ .../server/server.js} | 34 +++++--- 23 files changed, 337 insertions(+), 26 deletions(-) delete mode 100644 examples/.env.template rename examples/{ => console-app}/README.md (71%) rename examples/{ => console-app}/config.json (100%) rename examples/{ => console-app}/featureFlagSample.mjs (100%) rename examples/{ => console-app}/featureFlagSampleWithAppConfig.mjs (85%) rename examples/{ => console-app}/package.json (60%) create mode 100644 examples/express-react/README.md create mode 100644 examples/express-react/client/.gitignore create mode 100644 examples/express-react/client/README.md create mode 100644 examples/express-react/client/index.html create mode 100644 examples/express-react/client/package.json create mode 100644 examples/express-react/client/public/vite.svg create mode 100644 examples/express-react/client/src/App.css create mode 100644 examples/express-react/client/src/App.jsx create mode 100644 examples/express-react/client/src/assets/react.svg create mode 100644 examples/express-react/client/src/index.css create mode 100644 examples/express-react/client/src/main.jsx create mode 100644 examples/express-react/client/vite.config.js create mode 100644 examples/express-react/package.json create mode 100644 examples/express-react/server/package.json rename examples/{server.mjs => express-react/server/server.js} (55%) diff --git a/.gitignore b/.gitignore index 5fd795e..250bd47 100644 --- a/.gitignore +++ b/.gitignore @@ -409,7 +409,7 @@ types/ *.tgz # examples -examples/package-lock.json +examples/**/**/package-lock.json # playwright test result test-results diff --git a/README.md b/README.md index 54aa97a..ffeff87 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ The App Configuration JavaScript provider provides feature flags in as a `Map` o A builtin `ConfigurationMapFeatureFlagProvider` helps to load feature flags in this case. ```js -const appConfig = load(connectionString, {featureFlagOptions}); // load feature flags from Azure App Configuration service +const appConfig = await load(connectionString, {featureFlagOptions}); // load feature flags from Azure App Configuration service const featureProvider = new ConfigurationMapFeatureFlagProvider(appConfig); const featureManager = new FeatureManager(featureProvider); const isAlphaEnabled = await featureManager.isEnabled("Alpha"); diff --git a/examples/.env.template b/examples/.env.template deleted file mode 100644 index e9a4dab..0000000 --- a/examples/.env.template +++ /dev/null @@ -1,4 +0,0 @@ -# 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= \ No newline at end of file diff --git a/examples/README.md b/examples/console-app/README.md similarity index 71% rename from examples/README.md rename to examples/console-app/README.md index 4230f34..9e38286 100644 --- a/examples/README.md +++ b/examples/console-app/README.md @@ -6,7 +6,7 @@ These examples show how to use the Microsoft Feature Management in some common s The examples are compatible with [LTS versions of Node.js](https://github.com/nodejs/release#release-schedule). -Some examples use `@azure/app-configuration-provider` to load feature flags from the [Azure App Configuration](https://learn.microsoft.com/azure/azure-app-configuration/overview). Azure App Configuration provides a service to centrally manage application settings and feature flags. The examples retrieve credentials to access your App Configuration store from environment variables. Edit the file `.env.template`, adding the access keys to your App Configuration store. and rename the file from `.env.template` to just `.env`. The examples will read this file automatically. +Some examples use `@azure/app-configuration-provider` to load feature flags from the [Azure App Configuration](https://learn.microsoft.com/azure/azure-app-configuration/overview). Azure App Configuration provides a service to centrally manage application settings and feature flags. ## Setup & Run @@ -19,7 +19,7 @@ Some examples use `@azure/app-configuration-provider` to load feature flags from The examples reference the local `@microsoft/feature-management` package implemented in the `src` folder. Before running the example programs, make sure that you have built it. -1. Go to the `examples` folder. Install the dependencies using `npm`: +1. Go to the folder of the example. Install the dependencies using `npm`: ``` bash npm install diff --git a/examples/config.json b/examples/console-app/config.json similarity index 100% rename from examples/config.json rename to examples/console-app/config.json diff --git a/examples/featureFlagSample.mjs b/examples/console-app/featureFlagSample.mjs similarity index 100% rename from examples/featureFlagSample.mjs rename to examples/console-app/featureFlagSample.mjs diff --git a/examples/featureFlagSampleWithAppConfig.mjs b/examples/console-app/featureFlagSampleWithAppConfig.mjs similarity index 85% rename from examples/featureFlagSampleWithAppConfig.mjs rename to examples/console-app/featureFlagSampleWithAppConfig.mjs index 57598cf..0255278 100644 --- a/examples/featureFlagSampleWithAppConfig.mjs +++ b/examples/console-app/featureFlagSampleWithAppConfig.mjs @@ -1,11 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import * as dotenv from "dotenv"; -dotenv.config() - import { load } from "@azure/app-configuration-provider"; -const connectionString = process.env.APPCONFIG_CONNECTION_STRING; +import { ConfigurationMapFeatureFlagProvider, FeatureManager } from "@microsoft/feature-management"; + +const connectionString = ""; const appConfig = await load(connectionString, { featureFlagOptions: { enabled: true, @@ -18,7 +17,6 @@ const appConfig = await load(connectionString, { } }); -import { ConfigurationMapFeatureFlagProvider, FeatureManager } from "@microsoft/feature-management"; const featureProvider = new ConfigurationMapFeatureFlagProvider(appConfig); const featureManager = new FeatureManager(featureProvider); diff --git a/examples/package.json b/examples/console-app/package.json similarity index 60% rename from examples/package.json rename to examples/console-app/package.json index 590585f..9f4f7cc 100644 --- a/examples/package.json +++ b/examples/console-app/package.json @@ -1,8 +1,7 @@ { "dependencies": { "@azure/app-configuration-provider": "latest", - "@microsoft/feature-management": "../", - "dotenv": "^16.3.1", + "@microsoft/feature-management": "../../", "express": "^4.19.2" } } diff --git a/examples/express-react/README.md b/examples/express-react/README.md new file mode 100644 index 0000000..e69de29 diff --git a/examples/express-react/client/.gitignore b/examples/express-react/client/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/examples/express-react/client/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/examples/express-react/client/README.md b/examples/express-react/client/README.md new file mode 100644 index 0000000..f768e33 --- /dev/null +++ b/examples/express-react/client/README.md @@ -0,0 +1,8 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh diff --git a/examples/express-react/client/index.html b/examples/express-react/client/index.html new file mode 100644 index 0000000..0c589ec --- /dev/null +++ b/examples/express-react/client/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + + +
+ + + diff --git a/examples/express-react/client/package.json b/examples/express-react/client/package.json new file mode 100644 index 0000000..34fef4e --- /dev/null +++ b/examples/express-react/client/package.json @@ -0,0 +1,23 @@ +{ + "name": "client", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "@microsoft/feature-management": "../../../" + }, + "devDependencies": { + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "globals": "^15.9.0", + "vite": "^5.4.1" + } +} diff --git a/examples/express-react/client/public/vite.svg b/examples/express-react/client/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/examples/express-react/client/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/express-react/client/src/App.css b/examples/express-react/client/src/App.css new file mode 100644 index 0000000..b9d355d --- /dev/null +++ b/examples/express-react/client/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/examples/express-react/client/src/App.jsx b/examples/express-react/client/src/App.jsx new file mode 100644 index 0000000..4537982 --- /dev/null +++ b/examples/express-react/client/src/App.jsx @@ -0,0 +1,83 @@ +import { useState, useEffect } from 'react' +import reactLogo from './assets/react.svg' +import viteLogo from '/vite.svg' +import './App.css' +import { ConfigurationObjectFeatureFlagProvider, FeatureManager } from "@microsoft/feature-management" + +function App() { + const [betaEnabled, setBetaEnabled] = useState(false); + const [targeted, setTargeted] = useState(false); + const [fontColor, setFontColor] = useState("black"); + + useEffect(() => { + const queryParams = new URLSearchParams(window.location.search); + const userid = queryParams.get("userid"); + const groups = queryParams.get("group") ? queryParams.get("group").split(",") : []; + + const fetchFeatureFlags = async () => { + try { + const response = await fetch("http://localhost:5000/config"); + const data = await response.json(); + + if (data.fontColor !== undefined) { + setFontColor(data.fontColor); + } + + const provider = new ConfigurationObjectFeatureFlagProvider(data); + const featureManager = new FeatureManager(provider); + + const isBetaEnabled = await featureManager.isEnabled("Beta"); + setBetaEnabled(isBetaEnabled); + + const isTargeted = await featureManager.isEnabled("Targeting", { userId: userid, groups: groups }); + setTargeted(isTargeted); + + } catch (error) { + console.error('Error:', error); + } + }; + + fetchFeatureFlags(); + }, []); + + return ( + <> +
+ {betaEnabled ? ( + + ) : ( +
+ + React logo + +
+ )} +
+ {targeted ? ( +
+ {betaEnabled ? ( +
+

Vite + React

+
+ ) : ( +
+

React

+
+ )} +
+ ) : ( +
+
+ )} + + ) +} + +export default App diff --git a/examples/express-react/client/src/assets/react.svg b/examples/express-react/client/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/examples/express-react/client/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/express-react/client/src/index.css b/examples/express-react/client/src/index.css new file mode 100644 index 0000000..6119ad9 --- /dev/null +++ b/examples/express-react/client/src/index.css @@ -0,0 +1,68 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/examples/express-react/client/src/main.jsx b/examples/express-react/client/src/main.jsx new file mode 100644 index 0000000..89f91e5 --- /dev/null +++ b/examples/express-react/client/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import App from './App.jsx' +import './index.css' + +createRoot(document.getElementById('root')).render( + + + , +) diff --git a/examples/express-react/client/vite.config.js b/examples/express-react/client/vite.config.js new file mode 100644 index 0000000..5a33944 --- /dev/null +++ b/examples/express-react/client/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/examples/express-react/package.json b/examples/express-react/package.json new file mode 100644 index 0000000..16e6c2e --- /dev/null +++ b/examples/express-react/package.json @@ -0,0 +1,11 @@ +{ + "name": "feature-management-react-example", + "scripts": { + "server-install": "cd server && npm install", + "client-install": "cd client && npm install", + "install": "npm run server-install && npm run client-install", + "build": "npm run install && cd client && npm run build", + "start-server": "cd server && npm run start", + "start-client": "cd client && npm run build && npm run preview" + } + } \ No newline at end of file diff --git a/examples/express-react/server/package.json b/examples/express-react/server/package.json new file mode 100644 index 0000000..7c401a9 --- /dev/null +++ b/examples/express-react/server/package.json @@ -0,0 +1,15 @@ +{ + "name": "server", + "main": "server.js", + "type": "module", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "@azure/app-configuration-provider": "latest", + "@microsoft/feature-management": "../../../", + "cors": "^2.8.5", + "express": "^4.19.2" + } +} + \ No newline at end of file diff --git a/examples/server.mjs b/examples/express-react/server/server.js similarity index 55% rename from examples/server.mjs rename to examples/express-react/server/server.js index cfa55f1..e1206bd 100644 --- a/examples/server.mjs +++ b/examples/express-react/server/server.js @@ -1,14 +1,26 @@ -import express from 'express'; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import express from "express"; +import cors from "cors"; +import { load } from "@azure/app-configuration-provider"; +import { ConfigurationMapFeatureFlagProvider, FeatureManager } from "@microsoft/feature-management"; const app = express(); -const port = 3000; +const port = 5000; -import * as dotenv from "dotenv"; -dotenv.config() +const corsOptions = { + origin: ["http://localhost:4173", "http://localhost:5173"] // vite will run the web app on port 4173 and 5173 +} +app.use(cors(corsOptions)) -import { load } from "@azure/app-configuration-provider"; -const connectionString = process.env.APPCONFIG_CONNECTION_STRING; +const connectionString = ""; const appConfig = await load(connectionString, { + refreshOptions: { + enabled: true, + refreshIntervalInMs: 10_000, + watchedSettings: [{ key: "fontColor" }] // Watch for changes to the key "sentinel" and refreshes the configuration when it changes + }, featureFlagOptions: { enabled: true, selectors: [{ @@ -21,14 +33,9 @@ const appConfig = await load(connectionString, { } }); -import { ConfigurationMapFeatureFlagProvider, FeatureManager } from "@microsoft/feature-management"; const featureProvider = new ConfigurationMapFeatureFlagProvider(appConfig); const featureManager = new FeatureManager(featureProvider); -app.get('/', (req, res) => { - res.send('Welcome to the original feature!'); -}); - app.get('/beta', async (req, res) => { appConfig.refresh(); if (await featureManager.isEnabled("Beta")) { @@ -38,6 +45,11 @@ app.get('/beta', async (req, res) => { } }); +app.get("/config", (req, res) => { + appConfig.refresh(); + res.json(appConfig.constructConfigurationObject()); +}) + app.listen(port, () => { console.log(`Server is running on http://localhost:${port}`); From 34a798de96f5a3044fb9a4c06c0f3b61352a00b3 Mon Sep 17 00:00:00 2001 From: zhiyuanliang Date: Thu, 29 Aug 2024 13:38:09 +0800 Subject: [PATCH 4/5] update --- examples/express-react/client/index.html | 2 +- .../client/{src/assets => public}/react.svg | 0 examples/express-react/client/src/App.css | 2 +- examples/express-react/client/src/App.jsx | 59 +++++++------------ examples/express-react/server/.env.template | 4 ++ examples/express-react/server/package.json | 2 +- examples/express-react/server/server.js | 25 +++----- 7 files changed, 36 insertions(+), 58 deletions(-) rename examples/express-react/client/{src/assets => public}/react.svg (100%) create mode 100644 examples/express-react/server/.env.template diff --git a/examples/express-react/client/index.html b/examples/express-react/client/index.html index 0c589ec..c2ce3dd 100644 --- a/examples/express-react/client/index.html +++ b/examples/express-react/client/index.html @@ -4,7 +4,7 @@ - Vite + React + Demo
diff --git a/examples/express-react/client/src/assets/react.svg b/examples/express-react/client/public/react.svg similarity index 100% rename from examples/express-react/client/src/assets/react.svg rename to examples/express-react/client/public/react.svg diff --git a/examples/express-react/client/src/App.css b/examples/express-react/client/src/App.css index b9d355d..a4e253d 100644 --- a/examples/express-react/client/src/App.css +++ b/examples/express-react/client/src/App.css @@ -28,7 +28,7 @@ } @media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { + a:nth-of-type(1) .logo { animation: logo-spin infinite 20s linear; } } diff --git a/examples/express-react/client/src/App.jsx b/examples/express-react/client/src/App.jsx index 4537982..21e8eff 100644 --- a/examples/express-react/client/src/App.jsx +++ b/examples/express-react/client/src/App.jsx @@ -1,26 +1,25 @@ import { useState, useEffect } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from '/vite.svg' +import reactLogo from '/react.svg' import './App.css' import { ConfigurationObjectFeatureFlagProvider, FeatureManager } from "@microsoft/feature-management" function App() { const [betaEnabled, setBetaEnabled] = useState(false); const [targeted, setTargeted] = useState(false); - const [fontColor, setFontColor] = useState("black"); + const [appSettings, setAppSettings] = useState(undefined); useEffect(() => { const queryParams = new URLSearchParams(window.location.search); const userid = queryParams.get("userid"); - const groups = queryParams.get("group") ? queryParams.get("group").split(",") : []; + const groups = queryParams.get("groups") ? queryParams.get("groups").split(",") : []; - const fetchFeatureFlags = async () => { + const fetchConfiguration = async () => { try { const response = await fetch("http://localhost:5000/config"); const data = await response.json(); - if (data.fontColor !== undefined) { - setFontColor(data.fontColor); + if (data.app?.settings !== undefined) { + setAppSettings(data.app.settings); } const provider = new ConfigurationObjectFeatureFlagProvider(data); @@ -37,45 +36,29 @@ function App() { } }; - fetchFeatureFlags(); + fetchConfiguration(); }, []); return ( <> -
- {betaEnabled ? ( - - ) : ( -
- - React logo - -
- )} -
- {targeted ? ( + {betaEnabled ?
- {betaEnabled ? ( -
-

Vite + React

-
- ) : ( -
-

React

-
- )} + + React logo +
- ) : ( + : null} + {appSettings ? +
+ Welcome to your React app! +
: null + } + {targeted ?
+

User is targeted.

- )} + : null + } ) } diff --git a/examples/express-react/server/.env.template b/examples/express-react/server/.env.template new file mode 100644 index 0000000..25e3e6b --- /dev/null +++ b/examples/express-react/server/.env.template @@ -0,0 +1,4 @@ +# 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= diff --git a/examples/express-react/server/package.json b/examples/express-react/server/package.json index 7c401a9..4231b88 100644 --- a/examples/express-react/server/package.json +++ b/examples/express-react/server/package.json @@ -7,8 +7,8 @@ }, "dependencies": { "@azure/app-configuration-provider": "latest", - "@microsoft/feature-management": "../../../", "cors": "^2.8.5", + "dotenv": "^16.3.1", "express": "^4.19.2" } } diff --git a/examples/express-react/server/server.js b/examples/express-react/server/server.js index e1206bd..9c7fd59 100644 --- a/examples/express-react/server/server.js +++ b/examples/express-react/server/server.js @@ -1,10 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { load } from "@azure/app-configuration-provider"; import express from "express"; import cors from "cors"; -import { load } from "@azure/app-configuration-provider"; -import { ConfigurationMapFeatureFlagProvider, FeatureManager } from "@microsoft/feature-management"; +import * as dotenv from "dotenv"; +dotenv.config() +const connectionString = process.env.APPCONFIG_CONNECTION_STRING; const app = express(); const port = 5000; @@ -14,12 +16,14 @@ const corsOptions = { } app.use(cors(corsOptions)) -const connectionString = ""; const appConfig = await load(connectionString, { + selectors: [{ + keyFilter: "app.*" + }], refreshOptions: { enabled: true, refreshIntervalInMs: 10_000, - watchedSettings: [{ key: "fontColor" }] // Watch for changes to the key "sentinel" and refreshes the configuration when it changes + watchedSettings: [{ key: "sentinel" }] }, featureFlagOptions: { enabled: true, @@ -33,24 +37,11 @@ const appConfig = await load(connectionString, { } }); -const featureProvider = new ConfigurationMapFeatureFlagProvider(appConfig); -const featureManager = new FeatureManager(featureProvider); - -app.get('/beta', async (req, res) => { - appConfig.refresh(); - if (await featureManager.isEnabled("Beta")) { - res.send('Welcome to the new Beta feature!'); - } else { - res.status(404).send(); - } -}); - app.get("/config", (req, res) => { appConfig.refresh(); res.json(appConfig.constructConfigurationObject()); }) - app.listen(port, () => { console.log(`Server is running on http://localhost:${port}`); }); \ No newline at end of file From 40fb36f4e3cf204c407f837c909c605dc9627ff8 Mon Sep 17 00:00:00 2001 From: zhiyuanliang Date: Thu, 26 Sep 2024 15:01:28 +0800 Subject: [PATCH 5/5] remove express-react example --- examples/console-app/README.md | 11 +-- examples/console-app/package.json | 3 +- examples/express-react/README.md | 0 examples/express-react/client/.gitignore | 24 ------- examples/express-react/client/README.md | 8 --- examples/express-react/client/index.html | 13 ---- examples/express-react/client/package.json | 23 ------- .../express-react/client/public/react.svg | 1 - examples/express-react/client/public/vite.svg | 1 - examples/express-react/client/src/App.css | 42 ------------ examples/express-react/client/src/App.jsx | 66 ------------------ examples/express-react/client/src/index.css | 68 ------------------- examples/express-react/client/src/main.jsx | 10 --- examples/express-react/client/vite.config.js | 7 -- examples/express-react/package.json | 11 --- examples/express-react/server/.env.template | 4 -- examples/express-react/server/package.json | 15 ---- examples/express-react/server/server.js | 47 ------------- 18 files changed, 2 insertions(+), 352 deletions(-) delete mode 100644 examples/express-react/README.md delete mode 100644 examples/express-react/client/.gitignore delete mode 100644 examples/express-react/client/README.md delete mode 100644 examples/express-react/client/index.html delete mode 100644 examples/express-react/client/package.json delete mode 100644 examples/express-react/client/public/react.svg delete mode 100644 examples/express-react/client/public/vite.svg delete mode 100644 examples/express-react/client/src/App.css delete mode 100644 examples/express-react/client/src/App.jsx delete mode 100644 examples/express-react/client/src/index.css delete mode 100644 examples/express-react/client/src/main.jsx delete mode 100644 examples/express-react/client/vite.config.js delete mode 100644 examples/express-react/package.json delete mode 100644 examples/express-react/server/.env.template delete mode 100644 examples/express-react/server/package.json delete mode 100644 examples/express-react/server/server.js diff --git a/examples/console-app/README.md b/examples/console-app/README.md index 9e38286..1aa576e 100644 --- a/examples/console-app/README.md +++ b/examples/console-app/README.md @@ -10,16 +10,7 @@ Some examples use `@azure/app-configuration-provider` to load feature flags from ## Setup & Run -1. Build the feature management package in the root folder. - - ``` bash - npm install - npm run build - ``` - -The examples reference the local `@microsoft/feature-management` package implemented in the `src` folder. Before running the example programs, make sure that you have built it. - -1. Go to the folder of the example. Install the dependencies using `npm`: +1. Install the dependencies using `npm`: ``` bash npm install diff --git a/examples/console-app/package.json b/examples/console-app/package.json index 9f4f7cc..c43ceb2 100644 --- a/examples/console-app/package.json +++ b/examples/console-app/package.json @@ -1,7 +1,6 @@ { "dependencies": { "@azure/app-configuration-provider": "latest", - "@microsoft/feature-management": "../../", - "express": "^4.19.2" + "@microsoft/feature-management": "latest" } } diff --git a/examples/express-react/README.md b/examples/express-react/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/examples/express-react/client/.gitignore b/examples/express-react/client/.gitignore deleted file mode 100644 index a547bf3..0000000 --- a/examples/express-react/client/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/examples/express-react/client/README.md b/examples/express-react/client/README.md deleted file mode 100644 index f768e33..0000000 --- a/examples/express-react/client/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# React + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh diff --git a/examples/express-react/client/index.html b/examples/express-react/client/index.html deleted file mode 100644 index c2ce3dd..0000000 --- a/examples/express-react/client/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - Demo - - -
- - - diff --git a/examples/express-react/client/package.json b/examples/express-react/client/package.json deleted file mode 100644 index 34fef4e..0000000 --- a/examples/express-react/client/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "client", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "react": "^18.3.1", - "react-dom": "^18.3.1", - "@microsoft/feature-management": "../../../" - }, - "devDependencies": { - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "globals": "^15.9.0", - "vite": "^5.4.1" - } -} diff --git a/examples/express-react/client/public/react.svg b/examples/express-react/client/public/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/examples/express-react/client/public/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/express-react/client/public/vite.svg b/examples/express-react/client/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/examples/express-react/client/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/express-react/client/src/App.css b/examples/express-react/client/src/App.css deleted file mode 100644 index a4e253d..0000000 --- a/examples/express-react/client/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(1) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/examples/express-react/client/src/App.jsx b/examples/express-react/client/src/App.jsx deleted file mode 100644 index 21e8eff..0000000 --- a/examples/express-react/client/src/App.jsx +++ /dev/null @@ -1,66 +0,0 @@ -import { useState, useEffect } from 'react' -import reactLogo from '/react.svg' -import './App.css' -import { ConfigurationObjectFeatureFlagProvider, FeatureManager } from "@microsoft/feature-management" - -function App() { - const [betaEnabled, setBetaEnabled] = useState(false); - const [targeted, setTargeted] = useState(false); - const [appSettings, setAppSettings] = useState(undefined); - - useEffect(() => { - const queryParams = new URLSearchParams(window.location.search); - const userid = queryParams.get("userid"); - const groups = queryParams.get("groups") ? queryParams.get("groups").split(",") : []; - - const fetchConfiguration = async () => { - try { - const response = await fetch("http://localhost:5000/config"); - const data = await response.json(); - - if (data.app?.settings !== undefined) { - setAppSettings(data.app.settings); - } - - const provider = new ConfigurationObjectFeatureFlagProvider(data); - const featureManager = new FeatureManager(provider); - - const isBetaEnabled = await featureManager.isEnabled("Beta"); - setBetaEnabled(isBetaEnabled); - - const isTargeted = await featureManager.isEnabled("Targeting", { userId: userid, groups: groups }); - setTargeted(isTargeted); - - } catch (error) { - console.error('Error:', error); - } - }; - - fetchConfiguration(); - }, []); - - return ( - <> - {betaEnabled ? -
- - React logo - -
- : null} - {appSettings ? -
- Welcome to your React app! -
: null - } - {targeted ? -
-

User is targeted.

-
- : null - } - - ) -} - -export default App diff --git a/examples/express-react/client/src/index.css b/examples/express-react/client/src/index.css deleted file mode 100644 index 6119ad9..0000000 --- a/examples/express-react/client/src/index.css +++ /dev/null @@ -1,68 +0,0 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/examples/express-react/client/src/main.jsx b/examples/express-react/client/src/main.jsx deleted file mode 100644 index 89f91e5..0000000 --- a/examples/express-react/client/src/main.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import App from './App.jsx' -import './index.css' - -createRoot(document.getElementById('root')).render( - - - , -) diff --git a/examples/express-react/client/vite.config.js b/examples/express-react/client/vite.config.js deleted file mode 100644 index 5a33944..0000000 --- a/examples/express-react/client/vite.config.js +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react()], -}) diff --git a/examples/express-react/package.json b/examples/express-react/package.json deleted file mode 100644 index 16e6c2e..0000000 --- a/examples/express-react/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "feature-management-react-example", - "scripts": { - "server-install": "cd server && npm install", - "client-install": "cd client && npm install", - "install": "npm run server-install && npm run client-install", - "build": "npm run install && cd client && npm run build", - "start-server": "cd server && npm run start", - "start-client": "cd client && npm run build && npm run preview" - } - } \ No newline at end of file diff --git a/examples/express-react/server/.env.template b/examples/express-react/server/.env.template deleted file mode 100644 index 25e3e6b..0000000 --- a/examples/express-react/server/.env.template +++ /dev/null @@ -1,4 +0,0 @@ -# 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= diff --git a/examples/express-react/server/package.json b/examples/express-react/server/package.json deleted file mode 100644 index 4231b88..0000000 --- a/examples/express-react/server/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "server", - "main": "server.js", - "type": "module", - "scripts": { - "start": "node server.js" - }, - "dependencies": { - "@azure/app-configuration-provider": "latest", - "cors": "^2.8.5", - "dotenv": "^16.3.1", - "express": "^4.19.2" - } -} - \ No newline at end of file diff --git a/examples/express-react/server/server.js b/examples/express-react/server/server.js deleted file mode 100644 index 9c7fd59..0000000 --- a/examples/express-react/server/server.js +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { load } from "@azure/app-configuration-provider"; -import express from "express"; -import cors from "cors"; -import * as dotenv from "dotenv"; -dotenv.config() -const connectionString = process.env.APPCONFIG_CONNECTION_STRING; - -const app = express(); -const port = 5000; - -const corsOptions = { - origin: ["http://localhost:4173", "http://localhost:5173"] // vite will run the web app on port 4173 and 5173 -} -app.use(cors(corsOptions)) - -const appConfig = await load(connectionString, { - selectors: [{ - keyFilter: "app.*" - }], - refreshOptions: { - enabled: true, - refreshIntervalInMs: 10_000, - watchedSettings: [{ key: "sentinel" }] - }, - featureFlagOptions: { - enabled: true, - selectors: [{ - keyFilter: "*" - }], - refresh: { - enabled: true, - refreshIntervalInMs: 10_000 - } - } -}); - -app.get("/config", (req, res) => { - appConfig.refresh(); - res.json(appConfig.constructConfigurationObject()); -}) - -app.listen(port, () => { - console.log(`Server is running on http://localhost:${port}`); -}); \ No newline at end of file