From 58fb1a21fd4d478a56efe30fdad65567e26ce6da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20G=C3=B3is?= Date: Wed, 19 Apr 2023 19:40:29 +0100 Subject: [PATCH] feat: implement demo guide steps (#3569) https://linear.app/unleash/issue/2-924/implement-the-guide-steps While https://github.com/Unleash/unleash/pull/3528 implemented the basic guide logic with some example steps, this PR adds the guide steps as per the current demo, along with any necessary changes and features that were needed to accomplish this. Relates to [roadmap](https://github.com/orgs/Unleash/projects/10) item: #3537 https://user-images.githubusercontent.com/14320932/233133252-a3790f11-ec5d-4ee3-952d-88348212bd3c.mp4 --- frontend/src/component/common/Badge/Badge.tsx | 3 + frontend/src/component/demo/Demo.tsx | 165 +------ .../component/demo/DemoSteps/DemoSteps.tsx | 254 ++++++---- .../component/demo/DemoTopics/DemoTopics.tsx | 2 +- frontend/src/component/demo/demo-setup.ts | 114 +++++ frontend/src/component/demo/demo-topics.tsx | 451 ++++++++++++++++++ .../StrategyItem/StrategyItem.tsx | 5 +- .../VariantForm/VariantForm.tsx | 3 +- .../VariantOverrides/VariantOverrides.tsx | 2 + .../feature/FeatureView/FeatureView.tsx | 1 + .../FeatureToggleSwitch.tsx | 7 +- 11 files changed, 751 insertions(+), 256 deletions(-) create mode 100644 frontend/src/component/demo/demo-setup.ts create mode 100644 frontend/src/component/demo/demo-topics.tsx diff --git a/frontend/src/component/common/Badge/Badge.tsx b/frontend/src/component/common/Badge/Badge.tsx index 8d330362b2..a6a16a9da2 100644 --- a/frontend/src/component/common/Badge/Badge.tsx +++ b/frontend/src/component/common/Badge/Badge.tsx @@ -12,6 +12,7 @@ import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender' type Color = 'info' | 'success' | 'warning' | 'error' | 'secondary' | 'neutral'; interface IBadgeProps { + as?: React.ElementType; color?: Color; icon?: ReactElement; iconRight?: boolean; @@ -69,6 +70,7 @@ const BadgeIcon = (color: Color, icon: ReactElement, iconRight = false) => ( export const Badge: FC = forwardRef( ( { + as = 'div', color = 'neutral', icon, iconRight, @@ -80,6 +82,7 @@ export const Badge: FC = forwardRef( ref: ForwardedRef ) => ( - Import toggle configuration - - ), - content: ( - <> - - This is a cool feature that enables you to import - your toggle configuration. This is just an example - and not part of the final guide. - - - ), - disableBeacon: true, - }, - ], - }, - { - title: 'New feature toggle', - steps: [ - { - target: 'button[data-testid="NAVIGATE_TO_CREATE_FEATURE"]', - title: ( - - Add a new feature toggle - - ), - content: ( - <> - - You can use this button to add a new feature toggle. - This is just an example and not part of the final - guide. - - - ), - disableBeacon: true, - }, - ], - }, - { - title: 'Enable/disable a feature toggle', - steps: [ - { - target: '.MuiSwitch-sizeMedium', - title: ( - - Enable/disable a feature toggle - - ), - content: ( - <> - - The simplest way to use a feature toggle is to - enable or disable it for everyone (on/off). - - } - > - Look at the demo page when toggling! - - - ), - disableBeacon: true, - }, - ], - }, - { - title: 'Community', - steps: [ - { - target: 'a[href="https://twitter.com/getunleash"]', - title: ( - - Follow us on Twitter! - - ), - content: ( - <> - - Following us on Twitter is one of the easiest ways - of keeping up with us. This is just an example and - not part of the final guide. - - - ), - disableBeacon: true, - }, - { - target: 'a[href="https://www.linkedin.com/company/getunleash"]', - title: ( - - Follow us on LinkedIn! - - ), - content: ( - <> - - You can also follow us LinkedIn. This is just an - example and not part of the final guide. - - - ), - disableBeacon: true, - }, - { - target: 'a[href="https://github.com/Unleash/unleash"]', - title: ( - - Check out Unleash on GitHub! - - ), - content: ( - <> - - Unleash is open-source, check out the project on - GitHub. This is just an example and not part of the - final guide. - - - ), - disableBeacon: true, - }, - { - target: 'a[href="https://slack.unleash.run"]', - title: ( - Join us on Slack! - ), - content: ( - <> - - Join our community in Slack. This is just an example - and not part of the final guide. - - - ), - disableBeacon: true, - }, - ], - }, -]; - export const Demo = () => { const { uiConfig } = useUiConfig(); const [loaded, setLoaded] = useState(false); const [expanded, setExpanded] = useState(storedProgress.expanded ?? true); - const [run, setRun] = useState(storedProgress.run ?? false); + const [run, setRun] = useState(false); const [topic, setTopic] = useState(storedProgress.topic ?? 0); const [steps, setSteps] = useState(storedProgress.steps ?? [0]); useEffect(() => { setTimeout(() => { setLoaded(true); + if (storedProgress.run) { + setRun(true); + } }, 1000); }, []); diff --git a/frontend/src/component/demo/DemoSteps/DemoSteps.tsx b/frontend/src/component/demo/DemoSteps/DemoSteps.tsx index 7bef1012e6..8eabe169fe 100644 --- a/frontend/src/component/demo/DemoSteps/DemoSteps.tsx +++ b/frontend/src/component/demo/DemoSteps/DemoSteps.tsx @@ -1,14 +1,13 @@ import Joyride, { ACTIONS, CallBackProps, - EVENTS, - STATUS, TooltipRenderProps, } from 'react-joyride'; import { Button, Typography, styled, useTheme } from '@mui/material'; -import { ITutorialTopic } from '../Demo'; -import { useEffect } from 'react'; +import { ITutorialTopic, ITutorialTopicStep } from '../demo-topics'; +import { useEffect, useState } from 'react'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { useLocation, useNavigate } from 'react-router-dom'; const StyledTooltip = styled('div')(({ theme }) => ({ backgroundColor: theme.palette.background.paper, @@ -66,34 +65,62 @@ export const DemoSteps = ({ topics, }: IDemoStepsProps) => { const theme = useTheme(); + const navigate = useNavigate(); + const location = useLocation(); + const [flow, setFlow] = useState<'next' | 'back'>('next'); + + const abortController = new AbortController(); const skip = () => { - setRun(false); + abortController.abort(); setTopic(-1); setExpanded(false); }; + const setStep = (topic: number, step: number) => { + setSteps(steps => { + const newSteps = [...steps]; + newSteps[topic] = step; + return newSteps; + }); + }; + const back = () => { + setFlow('back'); if (steps[topic] === 0) { - setRun(false); const newTopic = topic - 1; setTopic(newTopic); - setSteps(steps => { - const newSteps = [...steps]; - newSteps[newTopic] = topics[newTopic].steps.length - 1; - return newSteps; - }); + setStep(newTopic, topics[newTopic].steps.length - 1); } else { - setSteps(steps => { - const newSteps = [...steps]; - newSteps[topic] = steps[topic] - 1; - return newSteps; - }); + setStep(topic, steps[topic] - 1); } }; - const joyrideCallback = (data: CallBackProps) => { - const { action, index, status, type, step } = data; + const nextTopic = () => { + if (topic === topics.length - 1) { + setTopic(-1); + setExpanded(false); + } else { + const newTopic = topic + 1; + setTopic(newTopic); + setStep(newTopic, 0); + } + }; + + const next = (index = steps[topic]) => { + setFlow('next'); + setStep(topic, index + 1); + if (index === topics[topic].steps.length - 1) { + nextTopic(); + } + }; + + const joyrideCallback = ( + data: CallBackProps & { + step: ITutorialTopicStep; + } + ) => { + const { action, index, step } = data; if (action === ACTIONS.UPDATE) { const el = document.querySelector(step.target as string); @@ -101,54 +128,86 @@ export const DemoSteps = ({ el.scrollIntoView({ block: 'center', }); + if (!step.nextButton) { + const clickHandler = (e: Event) => { + abortController.abort(); + next(index); + if (step.preventDefault) { + e.preventDefault(); + } + }; + + if (step.anyClick) { + window.addEventListener('click', clickHandler, { + signal: abortController.signal, + }); + } else { + el.addEventListener('click', clickHandler, { + signal: abortController.signal, + }); + } + } } } - if ( - ([EVENTS.STEP_AFTER, EVENTS.TARGET_NOT_FOUND] as string[]).includes( - type - ) - ) { - const newStep = index + (action === ACTIONS.PREV ? -1 : 1); - setSteps(steps => { - const newSteps = [...steps]; - newSteps[topic] = newStep; - return newSteps; - }); - } else if ( - ([STATUS.FINISHED, STATUS.SKIPPED] as string[]).includes(status) - ) { - setRun(false); - if (topic === topics.length - 1) { - setTopic(-1); - setExpanded(false); + if (run && !document.querySelector(step.target as string)) { + if (step.optional && flow === 'next') { + next(); } else { - const newTopic = topic + 1; - setTopic(newTopic); - setSteps(steps => { - const newSteps = [...steps]; - newSteps[newTopic] = 0; - return newSteps; - }); + back(); } } }; + const onBack = (step: ITutorialTopicStep) => { + if (step.backCloseModal) { + ( + document.querySelector('.MuiModal-backdrop') as HTMLElement + )?.click(); + } + if (step.backCollapseExpanded) { + ( + document.querySelector( + '.Mui-expanded[role="button"]' + ) as HTMLElement + )?.click(); + } + back(); + }; + useEffect(() => { - setRun(true); + setRun(false); + if (topic === -1) return; + const currentTopic = topics[topic]; + const currentStep = steps[topic]; + const href = currentTopic.steps[currentStep]?.href; + if (href && location.pathname !== href) { + navigate(href); + } + currentTopic.setup?.(); + + setTimeout(() => { + setRun(true); + }, 200); }, [topic, steps]); if (topic === -1) return null; + const joyrideSteps = topics[topic].steps.map(step => ({ + ...step, + disableBeacon: true, + })); + return ( { - const { onClick } = primaryProps; - - return ( - - - {step.title} + }: TooltipRenderProps & { + step: ITutorialTopicStep; + }) => ( + + + + {topics[topic].title} + + } + /> + 1} + show={ + + (step {steps[topic] + 1} of{' '} + {topics[topic].steps.length}) + + } + /> + + {step.content} + + + 1} + condition={topic > 0 || steps[topic] > 0} show={ - onBack(step)} > - (step {steps[topic] + 1} of{' '} - {topics[topic].steps.length}) - + Back + } /> - - {step.content} - - - - 0 || steps[topic] > 0} - show={ - - } - /> - - - - - ); - }} + next(steps[topic])} + variant="contained" + > + {topic === topics.length - 1 && + steps[topic] === + topics[topic].steps.length - 1 + ? 'Finish' + : 'Next'} + + } + /> + + + + )} /> ); }; diff --git a/frontend/src/component/demo/DemoTopics/DemoTopics.tsx b/frontend/src/component/demo/DemoTopics/DemoTopics.tsx index 2105e08d71..27159dfaf7 100644 --- a/frontend/src/component/demo/DemoTopics/DemoTopics.tsx +++ b/frontend/src/component/demo/DemoTopics/DemoTopics.tsx @@ -9,7 +9,7 @@ import { styled, } from '@mui/material'; import { CheckCircle, CircleOutlined, ExpandMore } from '@mui/icons-material'; -import { ITutorialTopic } from '../Demo'; +import { ITutorialTopic } from '../demo-topics'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; const StyledAccordion = styled(Accordion)(({ theme }) => ({ diff --git a/frontend/src/component/demo/demo-setup.ts b/frontend/src/component/demo/demo-setup.ts new file mode 100644 index 0000000000..da9183b7d2 --- /dev/null +++ b/frontend/src/component/demo/demo-setup.ts @@ -0,0 +1,114 @@ +import { IFeatureToggle } from 'interfaces/featureToggle'; +import { formatApiPath } from 'utils/formatPath'; + +export const gradualRollout = async () => { + const projectId = 'default'; + const featureId = 'demoApp.step3'; + const environmentId = 'default'; + + const { environments }: IFeatureToggle = await fetch( + formatApiPath( + `api/admin/projects/${projectId}/features/${featureId}?variantEnvironments=true` + ) + ).then(res => res.json()); + + const strategies = + environments.find(({ name }) => name === environmentId)?.strategies || + []; + + if (!strategies.find(({ name }) => name === 'flexibleRollout')) { + await fetch( + formatApiPath( + `api/admin/projects/${projectId}/features/${featureId}/environments/${environmentId}/strategies` + ), + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name: 'flexibleRollout', + constraints: [], + parameters: { + rollout: '50', + stickiness: 'userId', + groupId: featureId, + }, + }), + } + ); + } +}; + +export const variants = async () => { + const projectId = 'default'; + const featureId = 'demoApp.step4'; + const environmentId = 'default'; + + const { variants }: IFeatureToggle = await fetch( + formatApiPath( + `api/admin/projects/${projectId}/features/${featureId}?variantEnvironments=true` + ) + ).then(res => res.json()); + + if (!variants.length) { + await fetch( + formatApiPath( + `api/admin/projects/${projectId}/features/${featureId}/environments/${environmentId}/variants` + ), + { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify([ + { + op: 'add', + path: '/0', + value: { + name: 'red', + weightType: 'variable', + weight: 333, + overrides: [], + stickiness: 'default', + payload: { + type: 'string', + value: 'red', + }, + }, + }, + { + op: 'add', + path: '/1', + value: { + name: 'green', + weightType: 'variable', + weight: 333, + overrides: [], + stickiness: 'default', + payload: { + type: 'string', + value: 'green', + }, + }, + }, + { + op: 'add', + path: '/2', + value: { + name: 'blue', + weightType: 'variable', + weight: 333, + overrides: [], + stickiness: 'default', + payload: { + type: 'string', + value: 'blue', + }, + }, + }, + ]), + } + ); + } +}; diff --git a/frontend/src/component/demo/demo-topics.tsx b/frontend/src/component/demo/demo-topics.tsx new file mode 100644 index 0000000000..974254f1d8 --- /dev/null +++ b/frontend/src/component/demo/demo-topics.tsx @@ -0,0 +1,451 @@ +import { Typography, TypographyProps } from '@mui/material'; +import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; +import { Badge } from 'component/common/Badge/Badge'; +import { Step } from 'react-joyride'; +import { gradualRollout, variants } from './demo-setup'; + +export interface ITutorialTopicStep extends Step { + href?: string; + nextButton?: boolean; + backCloseModal?: boolean; + backCollapseExpanded?: boolean; + preventDefault?: boolean; + anyClick?: boolean; + optional?: boolean; +} + +export interface ITutorialTopic { + title: string; + setup?: () => Promise; + steps: ITutorialTopicStep[]; +} + +const Description = (props: TypographyProps) => ( + +); + +export const TOPICS: ITutorialTopic[] = [ + { + title: 'Enable/disable a feature toggle', + steps: [ + { + href: '/projects/default', + target: 'body', + placement: 'center', + content: ( + <> + + + Feature toggles + {' '} + are the central concept of Unleash. + + + Feature toggles are organized within{' '} + + projects + + . + + + ), + nextButton: true, + }, + { + href: '/projects/default', + target: 'div[data-testid="TOGGLE-demoApp.step1-default"]', + content: ( + <> + + The simplest way to use a feature toggle is to + enable or disable it for everyone (on/off). + + } + > + Look at the demo page when toggling! + + + ), + nextButton: true, + }, + ], + }, + { + title: 'Enable for a specific user', + steps: [ + { + href: '/projects/default', + target: 'body', + placement: 'center', + content: ( + <> + + + Activation strategies + {' '} + give you more control over when a feature should be + enabled. + + + Let's try enabling a feature toggle only for a + specific user. + + + ), + nextButton: true, + }, + { + href: '/projects/default', + target: 'a[href="/projects/default/features/demoApp.step2"]', + content: ( + + First, let's open the feature toggle configuration for{' '} + demoApp.step2. + + ), + preventDefault: true, + }, + { + href: '/projects/default/features/demoApp.step2', + target: 'div[data-testid="FEATURE_ENVIRONMENT_ACCORDION_default"] button', + content: ( + + Add a new strategy to this environment by clicking this + button. + + ), + }, + { + target: 'a[href="/projects/default/features/demoApp.step2/strategies/create?environmentId=default&strategyName=userWithId"]', + content: ( + + Select the UserIDs strategy + type. + + ), + placement: 'right', + backCloseModal: true, + }, + { + target: '#input-add-items', + content: ( + <> + + Enter your userId. + + } + > + You can find your userId on the demo page. + + + ), + nextButton: true, + backCloseModal: true, + }, + { + target: 'button[data-testid="STRATEGY_FORM_SUBMIT_ID"]', + content: ( + <> + + Save your strategy to apply it. + + } + > + Look at the demo page after saving! + + + ), + }, + { + target: 'button[data-testid="DIALOGUE_CONFIRM_ID"]', + content: ( + <> + Confirm your changes. + } + > + Look at the demo page after saving! + + + ), + optional: true, + }, + ], + }, + { + title: 'Adjust gradual rollout', + setup: gradualRollout, + steps: [ + { + href: '/projects/default', + target: 'body', + placement: 'center', + content: ( + <> + + + Gradual rollout + {' '} + is one of the available{' '} + + activation strategies + + . + + + Let's try enabling a feature toggle only for a + certain percentage of users. + + + ), + nextButton: true, + }, + { + href: '/projects/default', + target: 'a[href="/projects/default/features/demoApp.step3"]', + content: ( + + First, let's open the feature toggle configuration for{' '} + demoApp.step3. + + ), + preventDefault: true, + }, + { + href: '/projects/default/features/demoApp.step3', + target: 'div[data-testid="FEATURE_ENVIRONMENT_ACCORDION_default"] .MuiAccordionSummary-expandIconWrapper', + content: ( + + Expand the environment card to see all the defined + strategies. + + ), + }, + { + target: 'div[data-testid="FEATURE_ENVIRONMENT_ACCORDION_default"].Mui-expanded a[data-testid="STRATEGY_EDIT-flexibleRollout"]', + content: ( + + Edit the existing gradual rollout strategy. + + ), + backCollapseExpanded: true, + }, + { + target: 'span[data-testid="ROLLOUT_SLIDER_ID"]', + content: ( + Change the rollout percentage. + ), + backCloseModal: true, + }, + { + target: 'button[data-testid="STRATEGY_FORM_SUBMIT_ID"]', + content: ( + <> + + Save your strategy to apply it. + + } + > + Look at the demo page after saving! + + + ), + }, + { + target: 'button[data-testid="DIALOGUE_CONFIRM_ID"]', + content: ( + <> + Confirm your changes. + } + > + Look at the demo page after saving! + + + ), + optional: true, + }, + ], + }, + { + title: 'Adjust variants', + setup: variants, + steps: [ + { + href: '/projects/default', + target: 'body', + placement: 'center', + content: ( + <> + + + Feature toggle variants + {' '} + allow you to define different values for a feature + toggle. They can be used for A/B testing or + segmenting your users. + + + Let's try adding a variant to a feature toggle, + along with an override so our user can see it. + + + ), + nextButton: true, + }, + { + href: '/projects/default', + target: 'a[href="/projects/default/features/demoApp.step4"]', + content: ( + + First, let's open the feature toggle configuration for{' '} + demoApp.step4. + + ), + preventDefault: true, + }, + { + href: '/projects/default/features/demoApp.step4', + target: 'button[data-testid="TAB-Variants"]', + content: Select the variants tab., + }, + { + target: 'button[data-testid="EDIT_VARIANTS_BUTTON"]', + content: Edit the existing variants., + }, + { + target: 'button[data-testid="MODAL_ADD_VARIANT_BUTTON"]', + content: ( + Add a new variant to the list. + ), + backCloseModal: true, + }, + { + target: 'div[data-testid="VARIANT"]:last-of-type div[data-testid="VARIANT_NAME_INPUT"]', + content: ( + <> + Enter a new variant name. + + We recommend choosing a{' '} + + color + + . + + + Example: aqua. + + + ), + backCloseModal: true, + nextButton: true, + }, + { + target: 'div[data-testid="VARIANT"]:last-of-type #variant-payload-value', + content: ( + + Enter the{' '} + + color + {' '} + you chose on the previous step as the payload. + + ), + nextButton: true, + }, + { + target: 'div[data-testid="VARIANT"]:last-of-type button[data-testid="VARIANT_ADD_OVERRIDE_BUTTON"]', + content: ( + + Let's also add an override for our user. + + ), + }, + { + target: 'div[data-testid="VARIANT"]:last-of-type #override-context-name', + content: Choose a context field., + anyClick: true, + backCloseModal: true, + }, + { + target: 'li[data-testid="SELECT_ITEM_ID-userId"]', + content: ( + + Select the userId context + field. + + ), + placement: 'right', + backCloseModal: true, + }, + { + target: 'div[data-testid="VARIANT"]:last-of-type div[data-testid="OVERRIDE_VALUES"]', + content: ( + <> + + Enter your userId. + + } + > + You can find your userId on the demo page. + + + ), + nextButton: true, + backCloseModal: true, + }, + { + target: 'button[data-testid="DIALOGUE_CONFIRM_ID"]', + content: ( + <> + + Save your variants to apply them. + + } + > + Look at the demo page after saving! + + + ), + }, + ], + }, +]; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyItem.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyItem.tsx index 5fe010fe42..d7dea23e63 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyItem.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyItem.tsx @@ -69,7 +69,10 @@ export const StrategyItem: FC = ({ projectId={projectId} component={Link} to={editStrategyPath} - tooltipProps={{ title: 'Edit strategy' }} + tooltipProps={{ + title: 'Edit strategy', + }} + data-testid={`STRATEGY_EDIT-${strategy.name}`} > diff --git a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/VariantForm/VariantForm.tsx b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/VariantForm/VariantForm.tsx index 8fe29b5519..5c253aa673 100644 --- a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/VariantForm/VariantForm.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/VariantForm/VariantForm.tsx @@ -304,7 +304,7 @@ export const VariantForm = ({ }, [variant.weight]); return ( - + Add override diff --git a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/VariantForm/VariantOverrides/VariantOverrides.tsx b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/VariantForm/VariantOverrides/VariantOverrides.tsx index c4cc63e053..63798efb8a 100644 --- a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/VariantForm/VariantOverrides/VariantOverrides.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/VariantForm/VariantOverrides/VariantOverrides.tsx @@ -93,6 +93,7 @@ export const OverrideConfig: VFC = ({ id="override-context-name" name="contextName" label="Context Field" + data-testid="context_field" value={override.contextName} options={contextNames} onChange={e => @@ -140,6 +141,7 @@ export const OverrideConfig: VFC = ({ placeholder="" values={override.values} updateValues={updateValues(index)} + data-testid="OVERRIDE_VALUES" /> } /> diff --git a/frontend/src/component/feature/FeatureView/FeatureView.tsx b/frontend/src/component/feature/FeatureView/FeatureView.tsx index 8861d00e03..4d1fd577a5 100644 --- a/frontend/src/component/feature/FeatureView/FeatureView.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureView.tsx @@ -225,6 +225,7 @@ export const FeatureView = () => { label={tab.title} value={tab.path} onClick={() => navigate(tab.path)} + data-testid={`TAB-${tab.title}`} /> ))} diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch.tsx index 2585683312..157d7f5039 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch.tsx +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch.tsx @@ -5,7 +5,7 @@ import { UPDATE_FEATURE_ENVIRONMENT } from 'component/providers/AccessProvider/p import { useOptimisticUpdate } from './hooks/useOptimisticUpdate'; import { flexRow } from 'themes/themeStyles'; -const StyledBoxContainer = styled(Box)(() => ({ +const StyledBoxContainer = styled(Box)<{ 'data-testid': string }>(() => ({ mx: 'auto', ...flexRow, })); @@ -40,9 +40,12 @@ export const FeatureToggleSwitch: VFC = ({ ); }; + const key = `${featureName}-${environmentName}`; + return (