mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-21 13:47:39 +02:00
feat: Demo for strategy variants (#4457)
This commit is contained in:
parent
8ee031e978
commit
df41146f50
@ -21,7 +21,7 @@ export const StrategyVariantsPreferredAlert = () => {
|
|||||||
const DocsSdkSupportLink = () => {
|
const DocsSdkSupportLink = () => {
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
href="https://docs.getunleash.io/reference/feature-strategy-variants#client-sdk-support"
|
href="https://docs.getunleash.io/reference/strategy-variants#client-sdk-support"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
@ -33,7 +33,7 @@ const DocsSdkSupportLink = () => {
|
|||||||
const DocsLink = () => {
|
const DocsLink = () => {
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
href="https://docs.getunleash.io/reference/feature-strategy-variants"
|
href="https://docs.getunleash.io/reference/strategy-variants"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
|
@ -28,6 +28,7 @@ const ensureUserIdContextExists = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const specificUser = async () => {
|
export const specificUser = async () => {
|
||||||
|
await deleteOldStrategies('demoApp.step2');
|
||||||
await ensureUserIdContextExists();
|
await ensureUserIdContextExists();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -67,11 +68,48 @@ export const gradualRollout = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const variants = async () => {
|
const deleteStrategy = (featureId: string, strategyId: string) =>
|
||||||
const featureId = 'demoApp.step4';
|
fetch(
|
||||||
|
formatApiPath(
|
||||||
|
`api/admin/projects/${PROJECT}/features/${featureId}/environments/${ENVIRONMENT}/strategies/${strategyId}`
|
||||||
|
),
|
||||||
|
{ method: 'DELETE' }
|
||||||
|
);
|
||||||
|
|
||||||
await ensureUserIdContextExists();
|
const deleteOldStrategies = async (featureId: string) => {
|
||||||
|
const results = await fetch(
|
||||||
|
formatApiPath(
|
||||||
|
`api/admin/projects/${PROJECT}/features/${featureId}/environments/${ENVIRONMENT}/strategies`
|
||||||
|
)
|
||||||
|
).then(res => res.json());
|
||||||
|
|
||||||
|
const tooGenericStrategies = results.filter(
|
||||||
|
(strategy: { constraints: Array<unknown>; segments: Array<unknown> }) =>
|
||||||
|
strategy.constraints.length === 0 && strategy.segments.length === 0
|
||||||
|
);
|
||||||
|
const constrainedStrategies = results.filter(
|
||||||
|
(strategy: { constraints: Array<unknown>; segments: Array<unknown> }) =>
|
||||||
|
strategy.constraints.length > 0 || strategy.segments.length > 0
|
||||||
|
);
|
||||||
|
await Promise.all(
|
||||||
|
tooGenericStrategies.map((result: { id: string }) =>
|
||||||
|
deleteStrategy(featureId, result.id)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const strategyLimit = 30;
|
||||||
|
if (constrainedStrategies.length > strategyLimit) {
|
||||||
|
await Promise.all(
|
||||||
|
constrainedStrategies
|
||||||
|
.slice(0, strategyLimit - 1)
|
||||||
|
.map((result: { id: string }) =>
|
||||||
|
deleteStrategy(featureId, result.id)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ensureDefaultVariants = async (featureId: string) => {
|
||||||
const { variants }: IFeatureToggle = await fetch(
|
const { variants }: IFeatureToggle = await fetch(
|
||||||
formatApiPath(
|
formatApiPath(
|
||||||
`api/admin/projects/${PROJECT}/features/${featureId}?variantEnvironments=true`
|
`api/admin/projects/${PROJECT}/features/${featureId}?variantEnvironments=true`
|
||||||
@ -139,3 +177,9 @@ export const variants = async () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const variants = async () => {
|
||||||
|
await deleteOldStrategies('demoApp.step4');
|
||||||
|
await ensureDefaultVariants('demoApp.step4');
|
||||||
|
await ensureUserIdContextExists();
|
||||||
|
};
|
||||||
|
@ -461,24 +461,147 @@ export const TOPICS: ITutorialTopic[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: `/projects/${PROJECT}/features/demoApp.step4`,
|
href: `/projects/${PROJECT}/features/demoApp.step4`,
|
||||||
target: 'button[data-testid="TAB-Variants"]',
|
target: `div[data-testid="FEATURE_ENVIRONMENT_ACCORDION_${ENVIRONMENT}"] button`,
|
||||||
content: <Description>Select the "Variants" tab.</Description>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
target: 'button[data-testid="EDIT_VARIANTS_BUTTON"]',
|
|
||||||
content: (
|
content: (
|
||||||
<Description>
|
<Description>
|
||||||
Edit the existing variants by using this button.
|
Add a new strategy to this environment by using this
|
||||||
|
button.
|
||||||
</Description>
|
</Description>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: 'button[data-testid="MODAL_ADD_VARIANT_BUTTON"]',
|
target: 'button[data-testid="ADD_CONSTRAINT_BUTTON"]',
|
||||||
|
content: (
|
||||||
|
<>
|
||||||
|
<Description>
|
||||||
|
<a
|
||||||
|
href="https://docs.getunleash.io/reference/strategy-constraints"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Strategy constraints
|
||||||
|
</a>{' '}
|
||||||
|
are conditions that must be satisfied for an{' '}
|
||||||
|
<a
|
||||||
|
href="https://docs.getunleash.io/reference/activation-strategies"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
activation strategy
|
||||||
|
</a>{' '}
|
||||||
|
to be evaluated for a feature toggle.
|
||||||
|
</Description>
|
||||||
|
<Description sx={{ mt: 1 }}>
|
||||||
|
Add a constraint by using this button.
|
||||||
|
</Description>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
backCloseModal: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: '#context-field-select',
|
||||||
|
content: (
|
||||||
|
<>
|
||||||
|
<Description>
|
||||||
|
<a
|
||||||
|
href="https://docs.getunleash.io/reference/unleash-context"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Unleash context
|
||||||
|
</a>{' '}
|
||||||
|
contains information relating to the current feature
|
||||||
|
toggle request.
|
||||||
|
</Description>
|
||||||
|
<Description sx={{ mt: 1 }}>
|
||||||
|
Select the context field by using this dropdown.
|
||||||
|
</Description>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
backCloseModal: true,
|
||||||
|
anyClick: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: 'li[data-testid="SELECT_ITEM_ID-userId"]',
|
||||||
content: (
|
content: (
|
||||||
<Description>
|
<Description>
|
||||||
Add a new variant to the list by using this button.
|
Select the <Badge as="span">userId</Badge> context
|
||||||
|
field.
|
||||||
</Description>
|
</Description>
|
||||||
),
|
),
|
||||||
|
placement: 'right',
|
||||||
|
backCloseModal: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: 'div[data-testid="CONSTRAINT_VALUES_INPUT"]',
|
||||||
|
content: (
|
||||||
|
<>
|
||||||
|
<Description>
|
||||||
|
Enter your <Badge as="span">userId</Badge>
|
||||||
|
</Description>
|
||||||
|
<Badge
|
||||||
|
sx={{ mt: 2, mb: 1, width: '100%' }}
|
||||||
|
icon={<InfoOutlinedIcon />}
|
||||||
|
>
|
||||||
|
You can find your userId on the demo page.
|
||||||
|
</Badge>
|
||||||
|
<StyledImg
|
||||||
|
src={formatAssetPath(demoUserId)}
|
||||||
|
alt="You can find your userId on the demo page."
|
||||||
|
/>
|
||||||
|
<Description sx={{ mt: 1 }}>
|
||||||
|
When you're done, use the "Next" button in the
|
||||||
|
dialog.
|
||||||
|
</Description>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
placement: 'right',
|
||||||
|
nextButton: true,
|
||||||
|
focus: 'input',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: 'button[data-testid="CONSTRAINT_VALUES_ADD_BUTTON"]',
|
||||||
|
content: (
|
||||||
|
<Description>
|
||||||
|
Add the constraint value by using this button.
|
||||||
|
</Description>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: 'button[data-testid="CONSTRAINT_SAVE_BUTTON"]',
|
||||||
|
content: (
|
||||||
|
<Description>
|
||||||
|
Save the constraint by using this button.
|
||||||
|
</Description>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: 'button[data-testid="ADD_STRATEGY_VARIANT_BUTTON"]',
|
||||||
|
content: (
|
||||||
|
<>
|
||||||
|
<Description>
|
||||||
|
<a
|
||||||
|
href="https://docs.getunleash.io/reference/strategy-variants"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Strategy variants
|
||||||
|
</a>{' '}
|
||||||
|
allow to attach one or more values to your{' '}
|
||||||
|
<a
|
||||||
|
href="https://docs.getunleash.io/reference/activation-strategies"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
activation strategy
|
||||||
|
</a>{' '}
|
||||||
|
.
|
||||||
|
</Description>
|
||||||
|
<Description sx={{ mt: 1 }}>
|
||||||
|
Add a strategy variant by using this button.
|
||||||
|
</Description>
|
||||||
|
</>
|
||||||
|
),
|
||||||
backCloseModal: true,
|
backCloseModal: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -486,7 +609,8 @@ export const TOPICS: ITutorialTopic[] = [
|
|||||||
content: (
|
content: (
|
||||||
<>
|
<>
|
||||||
<Description>
|
<Description>
|
||||||
Enter a unique name for your variant.
|
Enter a name for your variant e.g.{' '}
|
||||||
|
<Badge as="span">color</Badge>
|
||||||
</Description>
|
</Description>
|
||||||
<Description sx={{ mt: 1 }}>
|
<Description sx={{ mt: 1 }}>
|
||||||
When you're done, use the "Next" button in the
|
When you're done, use the "Next" button in the
|
||||||
@ -494,9 +618,9 @@ export const TOPICS: ITutorialTopic[] = [
|
|||||||
</Description>
|
</Description>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
backCloseModal: true,
|
|
||||||
nextButton: true,
|
nextButton: true,
|
||||||
focus: 'input',
|
focus: 'input',
|
||||||
|
backCloseModal: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: 'div[data-testid="VARIANT"]:last-of-type #variant-payload-value',
|
target: 'div[data-testid="VARIANT"]:last-of-type #variant-payload-value',
|
||||||
@ -530,77 +654,14 @@ export const TOPICS: ITutorialTopic[] = [
|
|||||||
focus: true,
|
focus: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: 'div[data-testid="VARIANT"]:last-of-type button[data-testid="VARIANT_ADD_OVERRIDE_BUTTON"]',
|
target: 'button[data-testid="STRATEGY_FORM_SUBMIT_ID"]',
|
||||||
content: (
|
|
||||||
<>
|
|
||||||
<Description>
|
|
||||||
By adding an override, we can specify that your user
|
|
||||||
will always get this variant.
|
|
||||||
</Description>
|
|
||||||
<Description sx={{ mt: 1 }}>
|
|
||||||
Let's add an override for your user by using this
|
|
||||||
button.
|
|
||||||
</Description>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
target: 'div[data-testid="VARIANT"]:last-of-type #override-context-name',
|
|
||||||
content: (
|
content: (
|
||||||
<Description>
|
<Description>
|
||||||
Select the context field by using this dropdown.
|
Save and apply your strategy by using this button.
|
||||||
</Description>
|
</Description>
|
||||||
),
|
),
|
||||||
anyClick: true,
|
|
||||||
backCloseModal: true,
|
backCloseModal: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
target: 'li[data-testid="SELECT_ITEM_ID-userId"]',
|
|
||||||
content: (
|
|
||||||
<Description>
|
|
||||||
Select the <Badge as="span">userId</Badge> context
|
|
||||||
field.
|
|
||||||
</Description>
|
|
||||||
),
|
|
||||||
placement: 'right',
|
|
||||||
backCloseModal: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
target: 'div[data-testid="VARIANT"]:last-of-type div[data-testid="OVERRIDE_VALUES"]',
|
|
||||||
content: (
|
|
||||||
<>
|
|
||||||
<Description>
|
|
||||||
Enter your <Badge as="span">userId</Badge>
|
|
||||||
</Description>
|
|
||||||
<Badge
|
|
||||||
sx={{ mt: 2, mb: 1, width: '100%' }}
|
|
||||||
icon={<InfoOutlinedIcon />}
|
|
||||||
>
|
|
||||||
You can find your userId on the demo page.
|
|
||||||
</Badge>
|
|
||||||
<StyledImg
|
|
||||||
src={formatAssetPath(demoUserId)}
|
|
||||||
alt="You can find your userId on the demo page."
|
|
||||||
/>
|
|
||||||
<Description sx={{ mt: 1 }}>
|
|
||||||
When you're done, use the "Next" button in the
|
|
||||||
dialog.
|
|
||||||
</Description>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
placement: 'right',
|
|
||||||
nextButton: true,
|
|
||||||
backCloseModal: true,
|
|
||||||
focus: 'input',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
target: 'button[data-testid="DIALOGUE_CONFIRM_ID"]',
|
|
||||||
content: (
|
|
||||||
<Description>
|
|
||||||
Save your variants by using this button.
|
|
||||||
</Description>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
href: `/projects/${PROJECT}?sort=name`,
|
href: `/projects/${PROJECT}?sort=name`,
|
||||||
target: `div[data-testid="TOGGLE-demoApp.step4-${ENVIRONMENT}"]`,
|
target: `div[data-testid="TOGGLE-demoApp.step4-${ENVIRONMENT}"]`,
|
||||||
|
@ -2,7 +2,7 @@ import { Alert, Button, Divider, Link, styled } from '@mui/material';
|
|||||||
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||||
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { FormEvent, useEffect, useMemo, useState } from 'react';
|
import { FormEvent, useEffect, useMemo, useState, memo } from 'react';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { IFeatureEnvironment, IFeatureVariant } from 'interfaces/featureToggle';
|
import { IFeatureEnvironment, IFeatureVariant } from 'interfaces/featureToggle';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
@ -133,6 +133,8 @@ interface IEnvironmentVariantModalProps {
|
|||||||
onConfirm: (updatedVariants: IFeatureVariant[]) => void;
|
onConfirm: (updatedVariants: IFeatureVariant[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MemoizedVariantForm = memo(VariantForm);
|
||||||
|
|
||||||
export const EnvironmentVariantsModal = ({
|
export const EnvironmentVariantsModal = ({
|
||||||
environment,
|
environment,
|
||||||
open,
|
open,
|
||||||
@ -373,7 +375,7 @@ export const EnvironmentVariantsModal = ({
|
|||||||
/>
|
/>
|
||||||
<StyledVariantForms>
|
<StyledVariantForms>
|
||||||
{variantsEdit.map(variant => (
|
{variantsEdit.map(variant => (
|
||||||
<VariantForm
|
<MemoizedVariantForm
|
||||||
key={variant.id}
|
key={variant.id}
|
||||||
variant={variant}
|
variant={variant}
|
||||||
variants={variantsEdit}
|
variants={variantsEdit}
|
||||||
|
@ -111,7 +111,7 @@ export const StrategyVariants: FC<{
|
|||||||
</span>
|
</span>
|
||||||
<Link
|
<Link
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://docs.getunleash.io/reference/feature-strategy-variants"
|
href="https://docs.getunleash.io/reference/strategy-variants"
|
||||||
>
|
>
|
||||||
Learn more
|
Learn more
|
||||||
</Link>
|
</Link>
|
||||||
@ -120,7 +120,6 @@ export const StrategyVariants: FC<{
|
|||||||
/>
|
/>
|
||||||
</Typography>
|
</Typography>
|
||||||
<StyledVariantForms>
|
<StyledVariantForms>
|
||||||
<VariantInfoAlert mode="strategy" />
|
|
||||||
<StrategyVariantsUpgradeAlert />
|
<StrategyVariantsUpgradeAlert />
|
||||||
{variantsEdit.map((variant, i) => (
|
{variantsEdit.map((variant, i) => (
|
||||||
<VariantForm
|
<VariantForm
|
||||||
@ -155,6 +154,7 @@ export const StrategyVariants: FC<{
|
|||||||
permission={UPDATE_FEATURE_ENVIRONMENT_VARIANTS}
|
permission={UPDATE_FEATURE_ENVIRONMENT_VARIANTS}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
environmentId={environment}
|
environmentId={environment}
|
||||||
|
data-testid="ADD_STRATEGY_VARIANT_BUTTON"
|
||||||
>
|
>
|
||||||
Add variant
|
Add variant
|
||||||
</PermissionButton>
|
</PermissionButton>
|
||||||
|
Loading…
Reference in New Issue
Block a user