Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"@types/node": "^12.20.7",
"@types/react": "^17.0.3",
"@types/react-dom": "^17.0.3",
"crypto-js": "^4.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,40 @@ const featureFlag2 = {
}
};

const featureFlag3 = {
id: "react-app-feature-3",
description: "",
enabled: true,
conditions: {
client_filters: [
{
name: "Microsoft.Targeting",
parameters: {
Audience :
{
Users : ["abc"],
Groups : [{
Name : "microsoft.com",
RolloutPercentage : 50
}],
DefaultRolloutPercentage : 50
}
}
}
]
}
};

export default function Page(): JSX.Element {
const feature1Name = "react-app-feature-1";
const feature2Name = "react-app-feature-2";
const feature3Name = "react-app-feature-3";
const [feature1, setFeature1] = useState<{ enabled: boolean }>({ enabled: false });
const [feature2, setFeature2] = useState<{ enabled: boolean }>({ enabled: false });
const [feature3, setFeature3] = useState<{ enabled: boolean }>({ enabled: false });

const getFeatureFlags = async (keys: string[]): Promise<void> => {
const [setting1, setting2] = await Promise.all(
const [setting1, setting2, setting3] = await Promise.all(
keys.map((key: string) =>
client.getConfigurationSetting({
key: featureFlagPrefix + key
Expand All @@ -68,18 +94,77 @@ export default function Page(): JSX.Element {
const withinRange =
now - Date.parse(clientFilter.parameters.start) > 0 &&
Date.parse(clientFilter.parameters.end) - now > 0;
setFeature2({ enabled: withinRange });
setFeature2({ enabled: setting2.enabled && withinRange });
}
}
if (isFeatureFlag(setting3)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs updates based on the latest API design.

// console.log(`${setting3.key} is enabled : ${setting3.enabled}`, setting3);
const clientFilter = setting3.conditions.clientFilters?.[0];
if (isFeatureFlagClientFilter("targeting", clientFilter)) {
//Targeting Logic
let targetingFlag = false;
const usersList= clientFilter.parameters.audience.users;
const groupsList = clientFilter.parameters.audience.groups;
const defaultRolloutPercentage = clientFilter.parameters.audience.defaultRolloutPercentage;
const userEmail = "[email protected]"; //Replace this userEmail with logged in user's email via auth email fetch logic
//User Alias
const userAlias = userEmail.split("@")[0];
//Forming User Groups
const userDomain = userEmail.split("@")[1];
const userGroups = [];
userGroups.push(userDomain);

//Check if user is targeted directly
if(usersList.includes(userAlias)) targetingFlag = true;

// Check if the user is in a group that is being targeted
if(!targetingFlag){

for( var userGroup of userGroups )
{
const groupRollout = groupsList.map((group) => { if(group.name === userGroup) return group.rolloutPercentage; })
if( groupRollout.length !== 0)
{
const audienceContextId = `${userAlias}\n${featureFlag3.id}\n${userGroup}`;
targetingFlag = isTargeted(audienceContextId ,groupRollout[0]!);
}
}
}

if(!targetingFlag){
// Check if the user is being targeted by a default rollout percentage
const defaultContextId = `${userAlias}\n${featureFlag3.id}`;
targetingFlag = isTargeted(defaultContextId, defaultRolloutPercentage);
}

setFeature3({ enabled: setting3.enabled && targetingFlag});
}
}
};

const isTargeted = (contextId: string, percentage: number) =>
{
const CryptoJS = require("crypto-js");
const hash = CryptoJS.SHA256(contextId);
const hexhash = hash.toString(CryptoJS.enc.Hex).slice(0,8);
const data = hexhash.match(/../g);
const buf = new ArrayBuffer(4);
const view = new DataView(buf);
data.forEach(function (b: string, i: number) {
view.setUint8(i, parseInt(b, 16));
});
const contextMarker = view.getUint32(0, true);
const contextPercentage = ((contextMarker/4294967295.0)*100);
return (contextPercentage < percentage);
}

useEffect(() => {
const endpoint = getEnvironmentVariable("REACT_APP_APPCONFIG_ENDPOINT");
const clientId = getEnvironmentVariable("REACT_APP_AZURE_CLIENT_ID");
const tenantId = getEnvironmentVariable("REACT_APP_AZURE_TENANT_ID");
const credential = new InteractiveBrowserCredential({ clientId, tenantId });
client = new AppConfigurationClient(endpoint, credential);
getFeatureFlags([feature1Name, feature2Name]);
getFeatureFlags([feature1Name, feature2Name, feature3Name]);
}, []);

return (
Expand Down Expand Up @@ -111,6 +196,14 @@ export default function Page(): JSX.Element {
Beta Feature (Feature 2 - Time Window)
</a>
</li>
<li className="nav-item">
<a
className={(feature3.enabled ? "" : "cursor-disabled").concat(" nav-link")}
href="#"
>
Beta Feature (Feature 3 - Targeting)
</a>
</li>
</ul>
</div>
</nav>
Expand Down Expand Up @@ -163,6 +256,27 @@ export default function Page(): JSX.Element {
</div>
</div>
</div>
<div className="alert alert-light">
<h4 className="alert-heading">Feature Flag 3</h4>
<div className="container">
<div className="row">
<div className="col">
<pre>{JSON.stringify(featureFlag3, null, 2)}</pre>
</div>
<div className="col">
<div className="row">
<h4>== Description ==</h4>
</div>
<div className="row">
<p>
"Another action (Feature 3 - Targeting)" button in the dropdown will be
enabled based on the targeting parameters from the client filter.
</p>
</div>
</div>
</div>
</div>
</div>
<a
className="nav-link"
href="https://github.com/Azure/azure-sdk-for-js/blob/master/sdk/appconfiguration/app-configuration/samples/v1/typescript"
Expand Down