From 3f983d061ab982b06cf0eb8a9773401462bd14c5 Mon Sep 17 00:00:00 2001 From: Jaanus Sellin Date: Thu, 9 May 2024 16:55:09 +0300 Subject: [PATCH] feat: mark completed ui selector (#7025) ![image](https://github.com/Unleash/unleash/assets/964450/c6baa90b-5abb-4ba4-a3d9-36af6b426ed6) --- .../LegalValueLabel/LegalValueLabel.tsx | 4 +- .../FeatureLifecycle/FeatureLifecycle.tsx | 10 +- .../MarkCompletedDialogue.tsx | 125 ++++++++++++++++++ .../FeatureLifecycle/SingleVariantOptions.tsx | 47 +++++++ .../FeatureOverviewMetaData.tsx | 15 ++- .../useFeatureLifecycleApi.ts | 9 +- 6 files changed, 198 insertions(+), 12 deletions(-) create mode 100644 frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/MarkCompletedDialogue.tsx create mode 100644 frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/SingleVariantOptions.tsx diff --git a/frontend/src/component/common/NewConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/LegalValueLabel/LegalValueLabel.tsx b/frontend/src/component/common/NewConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/LegalValueLabel/LegalValueLabel.tsx index c3508fe349..c121066401 100644 --- a/frontend/src/component/common/NewConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/LegalValueLabel/LegalValueLabel.tsx +++ b/frontend/src/component/common/NewConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/LegalValueLabel/LegalValueLabel.tsx @@ -12,17 +12,19 @@ interface ILegalValueTextProps { legal: ILegalValue; control: React.ReactElement; filter?: string; + value?: string; } export const LegalValueLabel = ({ legal, control, filter, + value, }: ILegalValueTextProps) => { return ( = ({ feature, onComplete, onUncomplete, onArchive }) => { const currentStage = populateCurrentStage(feature); - const { markFeatureCompleted, markFeatureUncompleted, loading } = - useFeatureLifecycleApi(); - - const onCompleteHandler = async () => { - await markFeatureCompleted(feature.name, feature.project); - onComplete(); - }; + const { markFeatureUncompleted, loading } = useFeatureLifecycleApi(); const onUncompleteHandler = async () => { await markFeatureUncompleted(feature.name, feature.project); @@ -41,7 +35,7 @@ export const FeatureLifecycle: FC<{ diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/MarkCompletedDialogue.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/MarkCompletedDialogue.tsx new file mode 100644 index 0000000000..b9076753f2 --- /dev/null +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/MarkCompletedDialogue.tsx @@ -0,0 +1,125 @@ +import { Box, Radio, RadioGroup, Typography } from '@mui/material'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; +import { LegalValueLabel } from 'component/common/NewConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/LegalValueLabel/LegalValueLabel'; +import { useState } from 'react'; +import useFeatureLifecycleApi from 'hooks/api/actions/useFeatureLifecycleApi/useFeatureLifecycleApi'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { SingleVariantOptions } from './SingleVariantOptions'; + +interface IMarkCompletedDialogueProps { + isOpen: boolean; + setIsOpen: (open: boolean) => void; + onComplete: () => void; + projectId: string; + featureId: string; +} + +type Status = 'kept' | 'discarded' | 'kept-with-variant'; + +export const MarkCompletedDialogue = ({ + projectId, + featureId, + isOpen, + setIsOpen, + onComplete, +}: IMarkCompletedDialogueProps) => { + const { markFeatureCompleted } = useFeatureLifecycleApi(); + const [status, setStatus] = useState('kept'); + const [variant, setVariant] = useState(undefined); + const onClick = async () => { + const sentStatus = status === 'kept-with-variant' ? 'kept' : status; + await markFeatureCompleted(featureId, projectId, { + status: sentStatus, + statusValue: variant, + }); + setIsOpen(false); + onComplete(); + }; + + return ( + { + setIsOpen(false); + }} + disabledPrimaryButton={ + status === 'kept-with-variant' && variant === null + } + onClick={onClick} + primaryButtonText={'Mark completed'} + secondaryButtonText='Cancel' + > + + + Marking the feature toggle as complete does not affect any + configuration, but it moves the feature toggle into it’s + next life cycle stage and is an indication that you have + learned what you needed in order to progress with the + feature. It serves as a reminder to start cleaning up the + feature toggle and removing it from the code. + + + + What was the outcome of this feature? + + theme.spacing(0.5) }} + onChange={(e, value) => { + setStatus(value as Status); + }} + > + } + /> + } + /> + } + /> + { + setVariant(variant); + }} + /> + } + /> + + + + ); +}; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/SingleVariantOptions.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/SingleVariantOptions.tsx new file mode 100644 index 0000000000..e352252127 --- /dev/null +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/SingleVariantOptions.tsx @@ -0,0 +1,47 @@ +import { Autocomplete, Checkbox, styled, TextField } from '@mui/material'; +import type { FC } from 'react'; +import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'; +import CheckBoxIcon from '@mui/icons-material/CheckBox'; +import { useParentVariantOptions } from 'hooks/api/getters/useFeatureDependencyOptions/useFeatureDependencyOptions'; + +const StyledAutocomplete = styled(Autocomplete)(({ theme }) => ({ + marginTop: theme.spacing(2), + marginBottom: theme.spacing(1.5), +})); + +export const SingleVariantOptions: FC<{ + project: string; + parent: string; + onSelect: (value: string) => void; +}> = ({ project, parent, onSelect }) => { + const { parentVariantOptions: variantOptions } = useParentVariantOptions( + project, + parent, + ); + const icon = ; + const checkedIcon = ; + return ( + ( +
  • + + {option} +
  • + )} + renderInput={(params) => ( + + )} + fullWidth + onChange={(_, selectedValue) => { + onSelect(String(selectedValue)); + }} + /> + ); +}; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx index b44c580f8c..d3a783c134 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx @@ -20,6 +20,7 @@ import { useLocationSettings } from 'hooks/useLocationSettings'; import { useShowDependentFeatures } from './useShowDependentFeatures'; import type { ILastSeenEnvironments } from 'interfaces/featureToggle'; import { FeatureLifecycle } from '../FeatureLifecycle/FeatureLifecycle'; +import { MarkCompletedDialogue } from '../FeatureLifecycle/MarkCompletedDialogue'; const StyledContainer = styled('div')(({ theme }) => ({ borderRadius: theme.shape.borderRadiusLarge, @@ -96,6 +97,9 @@ const FeatureOverviewMetaData = () => { const featureLifecycleEnabled = useUiFlag('featureLifecycle'); const navigate = useNavigate(); const [showDelDialog, setShowDelDialog] = useState(false); + const [showMarkCompletedDialogue, setShowMarkCompletedDialogue] = + useState(false); + const { locationSettings } = useLocationSettings(); const showDependentFeatures = useShowDependentFeatures(feature.project); @@ -141,7 +145,9 @@ const FeatureOverviewMetaData = () => { setShowDelDialog(true)} - onComplete={refetchFeature} + onComplete={() => + setShowMarkCompletedDialogue(true) + } onUncomplete={refetchFeature} /> @@ -237,6 +243,13 @@ const FeatureOverviewMetaData = () => { /> } /> +
    ); }; diff --git a/frontend/src/hooks/api/actions/useFeatureLifecycleApi/useFeatureLifecycleApi.ts b/frontend/src/hooks/api/actions/useFeatureLifecycleApi/useFeatureLifecycleApi.ts index 09551ec0fc..cf849bfbee 100644 --- a/frontend/src/hooks/api/actions/useFeatureLifecycleApi/useFeatureLifecycleApi.ts +++ b/frontend/src/hooks/api/actions/useFeatureLifecycleApi/useFeatureLifecycleApi.ts @@ -1,4 +1,5 @@ import useAPI from '../useApi/useApi'; +import type { FeatureLifecycleCompletedSchema } from 'openapi'; const useFeatureLifecycleApi = () => { const { makeRequest, makeLightRequest, createRequest, errors, loading } = @@ -6,11 +7,15 @@ const useFeatureLifecycleApi = () => { propagateErrors: true, }); - const markFeatureCompleted = async (name: string, project: string) => { + const markFeatureCompleted = async ( + name: string, + project: string, + status: FeatureLifecycleCompletedSchema, + ) => { const path = `api/admin/projects/${project}/features/${name}/lifecycle/complete`; const req = createRequest(path, { method: 'POST', - body: JSON.stringify({ status: 'kept' }), + body: JSON.stringify(status), }); return makeRequest(req.caller, req.id);