1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-31 01:16:01 +02:00

feat: mark completed ui selector (#7025)

![image](https://github.com/Unleash/unleash/assets/964450/c6baa90b-5abb-4ba4-a3d9-36af6b426ed6)
This commit is contained in:
Jaanus Sellin 2024-05-09 16:55:09 +03:00 committed by GitHub
parent 3fc7714e78
commit 3f983d061a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 198 additions and 12 deletions

View File

@ -12,17 +12,19 @@ interface ILegalValueTextProps {
legal: ILegalValue;
control: React.ReactElement;
filter?: string;
value?: string;
}
export const LegalValueLabel = ({
legal,
control,
filter,
value,
}: ILegalValueTextProps) => {
return (
<StyledContainer>
<FormControlLabel
value={legal.value}
value={value || legal.value}
control={control}
sx={{ width: '100%' }}
label={

View File

@ -24,13 +24,7 @@ export const FeatureLifecycle: FC<{
}> = ({ 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<{
<FeatureLifecycleTooltip
stage={currentStage!}
onArchive={onArchive}
onComplete={onCompleteHandler}
onComplete={onComplete}
onUncomplete={onUncompleteHandler}
loading={loading}
>

View File

@ -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<Status>('kept');
const [variant, setVariant] = useState<string | undefined>(undefined);
const onClick = async () => {
const sentStatus = status === 'kept-with-variant' ? 'kept' : status;
await markFeatureCompleted(featureId, projectId, {
status: sentStatus,
statusValue: variant,
});
setIsOpen(false);
onComplete();
};
return (
<Dialogue
open={isOpen}
title='Mark completed'
onClose={() => {
setIsOpen(false);
}}
disabledPrimaryButton={
status === 'kept-with-variant' && variant === null
}
onClick={onClick}
primaryButtonText={'Mark completed'}
secondaryButtonText='Cancel'
>
<Box>
<Box
sx={{
mt: 2,
mb: 4,
}}
>
Marking the feature toggle as complete does not affect any
configuration, but it moves the feature toggle into its
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.
</Box>
<Typography
sx={{
mb: 2,
}}
>
<b>What was the outcome of this feature?</b>
</Typography>
<RadioGroup
aria-label='selected-value'
name='selected'
sx={{ gap: (theme) => theme.spacing(0.5) }}
onChange={(e, value) => {
setStatus(value as Status);
}}
>
<LegalValueLabel
key={'kept'}
value={'kept'}
legal={{
value: 'We decided to keep the feature',
}}
control={<Radio />}
/>
<LegalValueLabel
key={'discarded'}
value={'discarded'}
legal={{
value: 'We decided to discard the feature',
}}
control={<Radio />}
/>
<LegalValueLabel
key={'kept-with-variant'}
value={'kept-with-variant'}
legal={{
value: 'We decided to keep the feature variant',
description:
'Choose to specify which feature variant will be kept',
}}
control={<Radio />}
/>
<ConditionallyRender
condition={status === 'kept-with-variant'}
show={
<SingleVariantOptions
parent={featureId}
project={projectId}
onSelect={(variant) => {
setVariant(variant);
}}
/>
}
/>
</RadioGroup>
</Box>
</Dialogue>
);
};

View File

@ -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 = <CheckBoxOutlineBlankIcon fontSize='small' />;
const checkedIcon = <CheckBoxIcon fontSize='small' />;
return (
<StyledAutocomplete
id='single-variant-options'
options={variantOptions}
renderOption={(props, option, { selected }) => (
<li {...props}>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={selected}
/>
{option}
</li>
)}
renderInput={(params) => (
<TextField {...params} placeholder='Select variant' />
)}
fullWidth
onChange={(_, selectedValue) => {
onSelect(String(selectedValue));
}}
/>
);
};

View File

@ -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 = () => {
<FeatureLifecycle
feature={feature}
onArchive={() => setShowDelDialog(true)}
onComplete={refetchFeature}
onComplete={() =>
setShowMarkCompletedDialogue(true)
}
onUncomplete={refetchFeature}
/>
</StyledRow>
@ -237,6 +243,13 @@ const FeatureOverviewMetaData = () => {
/>
}
/>
<MarkCompletedDialogue
isOpen={showMarkCompletedDialogue}
setIsOpen={setShowMarkCompletedDialogue}
projectId={feature.project}
featureId={feature.name}
onComplete={refetchFeature}
/>
</StyledContainer>
);
};

View File

@ -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);