From 7f043c7cabb1b0260b358c8c2fcf13628e2f3a8c Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Fri, 29 Mar 2024 19:28:49 +0100 Subject: [PATCH] feat: variant dependencies ui (#6739) --- .../Changes/Change/DependencyChange.tsx | 5 +- .../changeRequest/changeRequest.types.ts | 6 +- .../AddDependencyDialogue.test.tsx | 2 +- .../Dependencies/AddDependencyDialogue.tsx | 278 ++++++------------ .../Dependencies/FeatureStatusOptions.tsx | 29 ++ .../Dependencies/LazyParentOptions.tsx | 33 +++ .../Dependencies/ParentVariantOptions.tsx | 48 +++ .../feature/Dependencies/constants.ts | 12 + .../Dependencies/useManageDependency.ts | 121 ++++++++ .../DependencyRow.tsx | 33 ++- .../FeatureOverviewSidePanelDetails.test.tsx | 25 +- .../VariantsTooltip.tsx | 25 ++ .../useFeatureDependencyOptions.ts | 29 ++ .../useParentOptions/useParentOptions.ts | 26 -- frontend/src/interfaces/featureToggle.ts | 3 +- 15 files changed, 437 insertions(+), 238 deletions(-) create mode 100644 frontend/src/component/feature/Dependencies/FeatureStatusOptions.tsx create mode 100644 frontend/src/component/feature/Dependencies/LazyParentOptions.tsx create mode 100644 frontend/src/component/feature/Dependencies/ParentVariantOptions.tsx create mode 100644 frontend/src/component/feature/Dependencies/constants.ts create mode 100644 frontend/src/component/feature/Dependencies/useManageDependency.ts create mode 100644 frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/VariantsTooltip.tsx create mode 100644 frontend/src/hooks/api/getters/useFeatureDependencyOptions/useFeatureDependencyOptions.ts delete mode 100644 frontend/src/hooks/api/getters/useParentOptions/useParentOptions.ts diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/DependencyChange.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/DependencyChange.tsx index a71e3e68999..eb3485c259b 100644 --- a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/DependencyChange.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/DependencyChange.tsx @@ -42,8 +42,9 @@ export const DependencyChange: VFC<{ > {change.payload.feature} - {change.payload.enabled === false - ? ' (disabled)' + {!change.payload.enabled ? ' (disabled)' : null} + {change.payload.variants?.length + ? `(${change.payload.variants?.join(', ')})` : null} {actions} diff --git a/frontend/src/component/changeRequest/changeRequest.types.ts b/frontend/src/component/changeRequest/changeRequest.types.ts index 1de55729380..61ccaeeecd6 100644 --- a/frontend/src/component/changeRequest/changeRequest.types.ts +++ b/frontend/src/component/changeRequest/changeRequest.types.ts @@ -223,7 +223,11 @@ type ChangeRequestVariantPatch = { type ChangeRequestEnabled = { enabled: boolean }; -type ChangeRequestAddDependency = { feature: string; enabled: boolean }; +type ChangeRequestAddDependency = { + feature: string; + enabled: boolean; + variants?: string[]; +}; export type ChangeRequestAddStrategy = Pick< IFeatureStrategy, diff --git a/frontend/src/component/feature/Dependencies/AddDependencyDialogue.test.tsx b/frontend/src/component/feature/Dependencies/AddDependencyDialogue.test.tsx index e35106a6fe1..57874369860 100644 --- a/frontend/src/component/feature/Dependencies/AddDependencyDialogue.test.tsx +++ b/frontend/src/component/feature/Dependencies/AddDependencyDialogue.test.tsx @@ -91,7 +91,7 @@ test('Edit dependency', async () => { { closed = true; diff --git a/frontend/src/component/feature/Dependencies/AddDependencyDialogue.tsx b/frontend/src/component/feature/Dependencies/AddDependencyDialogue.tsx index bd418453cf3..7d2a24c3427 100644 --- a/frontend/src/component/feature/Dependencies/AddDependencyDialogue.tsx +++ b/frontend/src/component/feature/Dependencies/AddDependencyDialogue.tsx @@ -1,205 +1,60 @@ -import { type FC, useState } from 'react'; -import { Box, styled, Typography } from '@mui/material'; +import { useEffect, useState } from 'react'; +import { Box, Typography } from '@mui/material'; import { Dialogue } from 'component/common/Dialogue/Dialogue'; -import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect'; -import { useDependentFeaturesApi } from 'hooks/api/actions/useDependentFeaturesApi/useDependentFeaturesApi'; -import { useParentOptions } from 'hooks/api/getters/useParentOptions/useParentOptions'; -import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { useHighestPermissionChangeRequestEnvironment } from 'hooks/useHighestPermissionChangeRequestEnvironment'; import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; -import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; -import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests'; -import useToast from 'hooks/useToast'; -import { formatUnknownError } from 'utils/formatUnknownError'; -import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; import { DependenciesUpgradeAlert } from './DependenciesUpgradeAlert'; import { useUiFlag } from 'hooks/useUiFlag'; +import type { IDependency } from '../../../interfaces/featureToggle'; +import { ParentVariantOptions } from './ParentVariantOptions'; +import { type ParentValue, REMOVE_DEPENDENCY_OPTION } from './constants'; +import { FeatureStatusOptions } from './FeatureStatusOptions'; +import { useManageDependency } from './useManageDependency'; +import { LazyParentOptions } from './LazyParentOptions'; interface IAddDependencyDialogueProps { project: string; featureId: string; - parentFeatureId?: string; - parentFeatureValue?: ParentValue; + parentDependency?: IDependency; showDependencyDialogue: boolean; onClose: () => void; } -const StyledSelect = styled(GeneralSelect)(({ theme }) => ({ - marginTop: theme.spacing(2), - marginBottom: theme.spacing(1.5), -})); - -const REMOVE_DEPENDENCY_OPTION = { - key: 'none (remove dependency)', - label: 'none (remove dependency)', -}; - -// Project can have 100s of parents. We want to read them only when the modal for dependencies opens. -const LazyOptions: FC<{ - project: string; - featureId: string; - parent: string; - onSelect: (parent: string) => void; -}> = ({ project, featureId, parent, onSelect }) => { - const { parentOptions } = useParentOptions(project, featureId); - - const options = parentOptions - ? [ - REMOVE_DEPENDENCY_OPTION, - ...parentOptions.map((parent) => ({ - key: parent, - label: parent, - })), - ] - : [REMOVE_DEPENDENCY_OPTION]; - return ( - - ); -}; - -const FeatureValueOptions: FC<{ - parentValue: ParentValue; - onSelect: (parent: string) => void; -}> = ({ onSelect, parentValue }) => { - return ( - - ); -}; - -type ParentValue = { status: 'enabled' } | { status: 'disabled' }; - -const useManageDependency = ( - project: string, - featureId: string, - parent: string, - parentValue: ParentValue, - onClose: () => void, -) => { - const { trackEvent } = usePlausibleTracker(); - const { addChange } = useChangeRequestApi(); - const { refetch: refetchChangeRequests } = - usePendingChangeRequests(project); - const { setToastData, setToastApiError } = useToast(); - const { refetchFeature } = useFeature(project, featureId); - const environment = useHighestPermissionChangeRequestEnvironment(project)(); - const { isChangeRequestConfiguredInAnyEnv } = - useChangeRequestsEnabled(project); - const { addDependency, removeDependencies } = - useDependentFeaturesApi(project); - - const handleAddChange = async ( - actionType: 'addDependency' | 'deleteDependency', - ) => { - if (!environment) { - console.error('No change request environment'); - return; - } - if (actionType === 'addDependency') { - await addChange(project, environment, [ - { - action: actionType, - feature: featureId, - payload: { - feature: parent, - enabled: parentValue.status !== 'disabled', - }, - }, - ]); - trackEvent('dependent_features', { - props: { - eventType: 'dependency added', - }, - }); - } - if (actionType === 'deleteDependency') { - await addChange(project, environment, [ - { action: actionType, feature: featureId, payload: undefined }, - ]); - } - void refetchChangeRequests(); - setToastData({ - text: - actionType === 'addDependency' - ? `${featureId} will depend on ${parent}` - : `${featureId} dependency will be removed`, - type: 'success', - title: 'Change added to a draft', - }); - }; - - return async () => { - try { - if (isChangeRequestConfiguredInAnyEnv()) { - const actionType = - parent === REMOVE_DEPENDENCY_OPTION.key - ? 'deleteDependency' - : 'addDependency'; - await handleAddChange(actionType); - trackEvent('dependent_features', { - props: { - eventType: - actionType === 'addDependency' - ? 'add dependency added to change request' - : 'delete dependency added to change request', - }, - }); - } else if (parent === REMOVE_DEPENDENCY_OPTION.key) { - await removeDependencies(featureId); - trackEvent('dependent_features', { - props: { - eventType: 'dependency removed', - }, - }); - setToastData({ title: 'Dependency removed', type: 'success' }); - } else { - await addDependency(featureId, { - feature: parent, - enabled: parentValue.status !== 'disabled', - }); - trackEvent('dependent_features', { - props: { - eventType: 'dependency added', - }, - }); - setToastData({ title: 'Dependency added', type: 'success' }); - } - } catch (error) { - setToastApiError(formatUnknownError(error)); - } - void refetchFeature(); - onClose(); - }; -}; - export const AddDependencyDialogue = ({ project, featureId, - parentFeatureId, - parentFeatureValue, + parentDependency, showDependencyDialogue, onClose, }: IAddDependencyDialogueProps) => { const [parent, setParent] = useState( - parentFeatureId || REMOVE_DEPENDENCY_OPTION.key, + parentDependency?.feature || REMOVE_DEPENDENCY_OPTION.key, ); + + const getInitialParentValue = (): ParentValue => { + if (!parentDependency) return { status: 'enabled' }; + if (parentDependency.variants?.length) + return { + status: 'enabled_with_variants', + variants: parentDependency.variants, + }; + if (parentDependency.enabled === false) return { status: 'disabled' }; + return { status: 'enabled' }; + }; const [parentValue, setParentValue] = useState( - parentFeatureValue || { status: 'enabled' }, + getInitialParentValue, ); - const handleClick = useManageDependency( + + const resetState = () => { + setParent(parentDependency?.feature || REMOVE_DEPENDENCY_OPTION.key); + setParentValue(getInitialParentValue()); + }; + + useEffect(() => { + resetState(); + }, [JSON.stringify(parentDependency)]); + + const manageDependency = useManageDependency( project, featureId, parent, @@ -210,13 +65,38 @@ export const AddDependencyDialogue = ({ useChangeRequestsEnabled(project); const variantDependenciesEnabled = useUiFlag('variantDependencies'); + const showStatus = + parent !== REMOVE_DEPENDENCY_OPTION.key && variantDependenciesEnabled; + const showVariants = + parent !== REMOVE_DEPENDENCY_OPTION.key && + variantDependenciesEnabled && + parentValue.status === 'enabled_with_variants'; + + const selectStatus = (value: string) => { + if (value === 'enabled' || value === 'disabled') { + setParentValue({ status: value }); + } + if (value === 'enabled_with_variants') { + setParentValue({ + status: value, + variants: [], + }); + } + }; + + const selectVariants = (variants: string[]) => { + setParentValue({ + status: 'enabled_with_variants', + variants, + }); + }; return ( What feature status do you want to depend on? - - setParentValue({ - status: - value === 'disabled' - ? 'disabled' - : 'enabled', - }) - } + onSelect={selectStatus} /> } /> + + + + What variant do you want to depend + on? + + + + ) + } + /> ); diff --git a/frontend/src/component/feature/Dependencies/FeatureStatusOptions.tsx b/frontend/src/component/feature/Dependencies/FeatureStatusOptions.tsx new file mode 100644 index 00000000000..fe68e80c4c4 --- /dev/null +++ b/frontend/src/component/feature/Dependencies/FeatureStatusOptions.tsx @@ -0,0 +1,29 @@ +import type { FC } from 'react'; +import type { ParentValue } from './constants'; +import { styled } from '@mui/material'; +import GeneralSelect from '../../common/GeneralSelect/GeneralSelect'; + +export const StyledSelect = styled(GeneralSelect)(({ theme }) => ({ + marginTop: theme.spacing(2), + marginBottom: theme.spacing(1.5), +})); +export const FeatureStatusOptions: FC<{ + parentValue: ParentValue; + onSelect: (parent: string) => void; +}> = ({ onSelect, parentValue }) => { + return ( + + ); +}; diff --git a/frontend/src/component/feature/Dependencies/LazyParentOptions.tsx b/frontend/src/component/feature/Dependencies/LazyParentOptions.tsx new file mode 100644 index 00000000000..d12bb03a89a --- /dev/null +++ b/frontend/src/component/feature/Dependencies/LazyParentOptions.tsx @@ -0,0 +1,33 @@ +import type { FC } from 'react'; +import { useParentOptions } from 'hooks/api/getters/useFeatureDependencyOptions/useFeatureDependencyOptions'; +import { REMOVE_DEPENDENCY_OPTION } from './constants'; +import { StyledSelect } from './FeatureStatusOptions'; + +// Project can have 100s of parents. We want to read them only when the modal for dependencies opens. +export const LazyParentOptions: FC<{ + project: string; + featureId: string; + parent: string; + onSelect: (parent: string) => void; +}> = ({ project, featureId, parent, onSelect }) => { + const { parentOptions } = useParentOptions(project, featureId); + + const options = parentOptions + ? [ + REMOVE_DEPENDENCY_OPTION, + ...parentOptions.map((parent) => ({ + key: parent, + label: parent, + })), + ] + : [REMOVE_DEPENDENCY_OPTION]; + + return ( + + ); +}; diff --git a/frontend/src/component/feature/Dependencies/ParentVariantOptions.tsx b/frontend/src/component/feature/Dependencies/ParentVariantOptions.tsx new file mode 100644 index 00000000000..8f61d0a5ea6 --- /dev/null +++ b/frontend/src/component/feature/Dependencies/ParentVariantOptions.tsx @@ -0,0 +1,48 @@ +import { Autocomplete, Checkbox, styled, TextField } from '@mui/material'; +import type { FC } from 'react'; +import { useParentVariantOptions } from 'hooks/api/getters/useFeatureDependencyOptions/useFeatureDependencyOptions'; +import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'; +import CheckBoxIcon from '@mui/icons-material/CheckBox'; + +const StyledAutocomplete = styled(Autocomplete)(({ theme }) => ({ + marginTop: theme.spacing(2), + marginBottom: theme.spacing(1.5), +})); + +export const ParentVariantOptions: FC<{ + project: string; + parent: string; + selectedValues: string[]; + onSelect: (values: string[]) => void; +}> = ({ project, parent, onSelect, selectedValues }) => { + const { parentVariantOptions } = useParentVariantOptions(project, parent); + const icon = ; + const checkedIcon = ; + return ( + ( +
  • + + {option} +
  • + )} + renderInput={(params) => ( + + )} + fullWidth + value={selectedValues} + onChange={(_, selectedValues) => { + onSelect(selectedValues as string[]); + }} + /> + ); +}; diff --git a/frontend/src/component/feature/Dependencies/constants.ts b/frontend/src/component/feature/Dependencies/constants.ts new file mode 100644 index 00000000000..9b277cc2c8f --- /dev/null +++ b/frontend/src/component/feature/Dependencies/constants.ts @@ -0,0 +1,12 @@ +export type ParentValue = + | { status: 'enabled' } + | { status: 'disabled' } + | { + status: 'enabled_with_variants'; + variants: string[]; + }; + +export const REMOVE_DEPENDENCY_OPTION = { + key: 'none (remove dependency)', + label: 'none (remove dependency)', +}; diff --git a/frontend/src/component/feature/Dependencies/useManageDependency.ts b/frontend/src/component/feature/Dependencies/useManageDependency.ts new file mode 100644 index 00000000000..246b7f53423 --- /dev/null +++ b/frontend/src/component/feature/Dependencies/useManageDependency.ts @@ -0,0 +1,121 @@ +import { type ParentValue, REMOVE_DEPENDENCY_OPTION } from './constants'; +import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; +import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; +import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests'; +import useToast from 'hooks/useToast'; +import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; +import { useHighestPermissionChangeRequestEnvironment } from 'hooks/useHighestPermissionChangeRequestEnvironment'; +import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; +import { useDependentFeaturesApi } from 'hooks/api/actions/useDependentFeaturesApi/useDependentFeaturesApi'; +import { formatUnknownError } from '../../../utils/formatUnknownError'; + +export const useManageDependency = ( + project: string, + featureId: string, + parent: string, + parentValue: ParentValue, + onClose: () => void, +) => { + const { trackEvent } = usePlausibleTracker(); + const { addChange } = useChangeRequestApi(); + const { refetch: refetchChangeRequests } = + usePendingChangeRequests(project); + const { setToastData, setToastApiError } = useToast(); + const { refetchFeature } = useFeature(project, featureId); + const environment = useHighestPermissionChangeRequestEnvironment(project)(); + const { isChangeRequestConfiguredInAnyEnv } = + useChangeRequestsEnabled(project); + const { addDependency, removeDependencies } = + useDependentFeaturesApi(project); + + const handleAddChange = async ( + actionType: 'addDependency' | 'deleteDependency', + ) => { + if (!environment) { + console.error('No change request environment'); + return; + } + if (actionType === 'addDependency') { + await addChange(project, environment, [ + { + action: actionType, + feature: featureId, + payload: { + feature: parent, + enabled: parentValue.status !== 'disabled', + variants: + parentValue.status === 'enabled_with_variants' + ? parentValue.variants + : [], + }, + }, + ]); + trackEvent('dependent_features', { + props: { + eventType: 'dependency added', + }, + }); + } + if (actionType === 'deleteDependency') { + await addChange(project, environment, [ + { action: actionType, feature: featureId, payload: undefined }, + ]); + } + void refetchChangeRequests(); + setToastData({ + text: + actionType === 'addDependency' + ? `${featureId} will depend on ${parent}` + : `${featureId} dependency will be removed`, + type: 'success', + title: 'Change added to a draft', + }); + }; + + return async () => { + try { + if (isChangeRequestConfiguredInAnyEnv()) { + const actionType = + parent === REMOVE_DEPENDENCY_OPTION.key + ? 'deleteDependency' + : 'addDependency'; + await handleAddChange(actionType); + trackEvent('dependent_features', { + props: { + eventType: + actionType === 'addDependency' + ? 'add dependency added to change request' + : 'delete dependency added to change request', + }, + }); + } else if (parent === REMOVE_DEPENDENCY_OPTION.key) { + await removeDependencies(featureId); + trackEvent('dependent_features', { + props: { + eventType: 'dependency removed', + }, + }); + setToastData({ title: 'Dependency removed', type: 'success' }); + } else { + await addDependency(featureId, { + feature: parent, + enabled: parentValue.status !== 'disabled', + variants: + parentValue.status === 'enabled_with_variants' + ? parentValue.variants + : [], + }); + trackEvent('dependent_features', { + props: { + eventType: 'dependency added', + }, + }); + setToastData({ title: 'Dependency added', type: 'success' }); + } + } catch (error) { + setToastApiError(formatUnknownError(error)); + } + void refetchFeature(); + onClose(); + }; +}; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/DependencyRow.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/DependencyRow.tsx index 615931e1971..1bb62ab6cfe 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/DependencyRow.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/DependencyRow.tsx @@ -17,6 +17,7 @@ import { useHighestPermissionChangeRequestEnvironment } from 'hooks/useHighestPe import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; import { formatUnknownError } from 'utils/formatUnknownError'; import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; +import { VariantsTooltip } from './VariantsTooltip'; const useDeleteDependency = (project: string, featureId: string) => { const { trackEvent } = usePlausibleTracker(); @@ -152,11 +153,25 @@ export const DependencyRow: FC<{ feature: IFeatureToggle }> = ({ feature }) => { Dependency value: - - {feature.dependencies[0]?.enabled - ? 'enabled' - : 'disabled'} - + disabled + + + } + /> + + + Dependency value: + } @@ -182,13 +197,7 @@ export const DependencyRow: FC<{ feature: IFeatureToggle }> = ({ feature }) => { setShowDependencyDialogue(false)} showDependencyDialogue={showDependencyDialogue} /> diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.test.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.test.tsx index b36d5d06cd3..ad573fa02dd 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.test.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.test.tsx @@ -246,7 +246,6 @@ test('delete dependency with change request', async () => { }); test('edit dependency', async () => { - setupChangeRequestApi(); render( { await screen.findByText('Add parent feature dependency'); }); + +test('show variant dependencies', async () => { + render( + , + ); + + await screen.findByText('2 variants'); +}); diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/VariantsTooltip.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/VariantsTooltip.tsx new file mode 100644 index 00000000000..510062d03e0 --- /dev/null +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/VariantsTooltip.tsx @@ -0,0 +1,25 @@ +import { TooltipLink } from 'component/common/TooltipLink/TooltipLink'; +import type { FC } from 'react'; + +export const VariantsTooltip: FC<{ + variants: string[]; +}> = ({ variants }) => { + if (variants.length === 1 && variants[0].length < 20) { + return {variants[0]}; + } + return ( + + {variants.map((child) => ( +
    {child}
    + ))} + + } + > + {variants.length === 1 + ? '1 variant' + : `${variants.length} variants`} +
    + ); +}; diff --git a/frontend/src/hooks/api/getters/useFeatureDependencyOptions/useFeatureDependencyOptions.ts b/frontend/src/hooks/api/getters/useFeatureDependencyOptions/useFeatureDependencyOptions.ts new file mode 100644 index 00000000000..f645732169f --- /dev/null +++ b/frontend/src/hooks/api/getters/useFeatureDependencyOptions/useFeatureDependencyOptions.ts @@ -0,0 +1,29 @@ +import { formatApiPath } from 'utils/formatPath'; +import { fetcher, useApiGetter } from '../useApiGetter/useApiGetter'; + +const parentOptionsPath = (projectId: string, childFeatureId: string) => + `/api/admin/projects/${projectId}/features/${childFeatureId}/parents`; + +export const useParentOptions = (projectId: string, childFeatureId: string) => { + const path = formatApiPath(parentOptionsPath(projectId, childFeatureId)); + const { data, refetch, loading, error } = useApiGetter(path, () => + fetcher(path, 'Parent Options'), + ); + + return { parentOptions: data, loading, error }; +}; + +const parentVariantsPath = (projectId: string, parentFeatureId: string) => + `/api/admin/projects/${projectId}/features/${parentFeatureId}/parent-variants`; + +export const useParentVariantOptions = ( + projectId: string, + parentFeatureId: string, +) => { + const path = formatApiPath(parentVariantsPath(projectId, parentFeatureId)); + const { data, refetch, loading, error } = useApiGetter(path, () => + fetcher(path, 'Parent Variant Options'), + ); + + return { parentVariantOptions: data || [], loading, error }; +}; diff --git a/frontend/src/hooks/api/getters/useParentOptions/useParentOptions.ts b/frontend/src/hooks/api/getters/useParentOptions/useParentOptions.ts deleted file mode 100644 index 4dfca6b91a5..00000000000 --- a/frontend/src/hooks/api/getters/useParentOptions/useParentOptions.ts +++ /dev/null @@ -1,26 +0,0 @@ -import useSWR, { type SWRConfiguration } from 'swr'; -import { formatApiPath } from 'utils/formatPath'; -import handleErrorResponses from '../httpErrorResponseHandler'; - -export const useParentOptions = ( - project: string, - childFeatureId: string, - options: SWRConfiguration = {}, -) => { - const path = formatApiPath( - `/api/admin/projects/${project}/features/${childFeatureId}/parents`, - ); - const { data, error, mutate } = useSWR(path, fetcher, options); - - return { - parentOptions: data, - error, - loading: !error && !data, - }; -}; - -const fetcher = async (path: string): Promise => { - const res = await fetch(path).then(handleErrorResponses('Parent Options')); - const data = await res.json(); - return data; -}; diff --git a/frontend/src/interfaces/featureToggle.ts b/frontend/src/interfaces/featureToggle.ts index 0f3628d71dc..f05dfb2a1d5 100644 --- a/frontend/src/interfaces/featureToggle.ts +++ b/frontend/src/interfaces/featureToggle.ts @@ -54,7 +54,8 @@ export interface IFeatureToggle { export interface IDependency { feature: string; - enabled: boolean; + enabled?: boolean; + variants?: string[]; } export interface IFeatureEnvironment {