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

Frontend - Suggest change copy strategy (#2312)

* Suggest change copy strategy

* Fix merge conflicts

* Copy strategies from other environment added to draft

* Copy strategies from other environment added to draft

* Copy strategies from other environment added to draft

* Copy strategies from other environment added to draft

* fmt

* PR comments

* PR comments

* PR comments

* PR comments

* Fix: Conditionally hide Change Requests tab
This commit is contained in:
andreas-unleash 2022-11-04 11:33:07 +02:00 committed by GitHub
parent a267f13a7d
commit c1e0bd83b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 500 additions and 133 deletions

View File

@ -7,7 +7,20 @@ import { ToggleStatusChange } from '../ChangeRequestOverview/ChangeRequestFeatur
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
import { formatUnknownError } from 'utils/formatUnknownError'; import { formatUnknownError } from 'utils/formatUnknownError';
import useToast from 'hooks/useToast'; import useToast from 'hooks/useToast';
import type { IChangeRequest } from '../changeRequest.types'; import type {
IChangeRequest,
IChangeRequestAddStrategy,
} from '../changeRequest.types';
import {
StrategyAddedChange,
StrategyDeletedChange,
StrategyEditedChange,
} from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/StrategyChange';
import {
formatStrategyName,
GetFeatureStrategyIcon,
} from '../../../utils/strategyNames';
import { IChangeRequestEnabled } from '../changeRequest.types';
interface IChangeRequestProps { interface IChangeRequestProps {
changeRequest: IChangeRequest; changeRequest: IChangeRequest;
@ -54,21 +67,29 @@ export const ChangeRequest: VFC<IChangeRequestProps> = ({
condition={change.action === 'updateEnabled'} condition={change.action === 'updateEnabled'}
show={ show={
<ToggleStatusChange <ToggleStatusChange
// @ts-expect-error TODO: fix types enabled={
enabled={change?.payload?.enabled} (change as IChangeRequestEnabled)
onDiscard={onDiscard(change.id)} ?.payload?.enabled
}
onDiscard={onDiscard(change.id!)}
/> />
} }
/> />
{/* <ConditionallyRender <ConditionallyRender
condition={change.action === 'addStrategy'} condition={change.action === 'addStrategy'}
show={ show={
<StrategyAddedChange> <StrategyAddedChange>
<GetFeatureStrategyIcon <GetFeatureStrategyIcon
strategyName={change.payload.name} strategyName={
(
change as IChangeRequestAddStrategy
)?.payload.name!
}
/> />
{formatStrategyName( {formatStrategyName(
change.payload.name (
change as IChangeRequestAddStrategy
)?.payload.name!
)} )}
</StrategyAddedChange> </StrategyAddedChange>
} }
@ -80,7 +101,7 @@ export const ChangeRequest: VFC<IChangeRequestProps> = ({
<ConditionallyRender <ConditionallyRender
condition={change.action === 'updateStrategy'} condition={change.action === 'updateStrategy'}
show={<StrategyEditedChange />} show={<StrategyEditedChange />}
/> */} />
</Box> </Box>
))} ))}
</ChangeRequestFeatureToggleChange> </ChangeRequestFeatureToggleChange>

View File

@ -6,38 +6,39 @@ interface IChangeRequestDialogueProps {
isOpen: boolean; isOpen: boolean;
onConfirm: () => void; onConfirm: () => void;
onClose: () => void; onClose: () => void;
featureName?: string;
environment?: string; environment?: string;
enabled?: boolean; showBanner?: boolean;
messageComponent: JSX.Element;
} }
export const ChangeRequestDialogue: FC<IChangeRequestDialogueProps> = ({ export const ChangeRequestDialogue: FC<IChangeRequestDialogueProps> = ({
isOpen, isOpen,
onConfirm, onConfirm,
onClose, onClose,
enabled, showBanner,
featureName,
environment, environment,
messageComponent,
}) => ( }) => (
<Dialogue <Dialogue
open={isOpen} open={isOpen}
primaryButtonText="Add to draft" primaryButtonText="Add suggestion to draft"
secondaryButtonText="Cancel" secondaryButtonText="Cancel"
onClick={onConfirm} onClick={onConfirm}
onClose={onClose} onClose={onClose}
title="Request changes" title="Request changes"
fullWidth
> >
<Alert severity="info" sx={{ mb: 2 }}> {showBanner && (
Change requests is enabled for {environment}. Your changes needs to <Alert severity="info" sx={{ mb: 2 }}>
be approved before they will be live. All the changes you do now Change requests feature is enabled for {environment}. Your
will be added into a draft that you can submit for review. changes needs to be approved before they will be live. All the
</Alert> changes you do now will be added into a draft that you can
submit for review.
</Alert>
)}
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
Change requests: Your suggestion:
</Typography>
<Typography>
<strong>{enabled ? 'Disable' : 'Enable'}</strong> feature toggle{' '}
<strong>{featureName}</strong> in <strong>{environment}</strong>
</Typography> </Typography>
{messageComponent}
</Dialogue> </Dialogue>
); );

View File

@ -0,0 +1,33 @@
import { styled, Typography } from '@mui/material';
import { formatStrategyName } from '../../../../utils/strategyNames';
import { IFeatureStrategy } from '../../../../interfaces/strategy';
import { CopyStrategyMsg } from './CopyStrategyMessage';
const MsgContainer = styled('div')(({ theme }) => ({
'&>*:nth-child(n)': {
margin: theme.spacing(1, 0),
},
}));
export const CopyStrategiesMessage = ({
payload,
fromEnvironment,
environment,
}: CopyStrategyMsg) => (
<MsgContainer>
<Typography>
<strong>Copy: </strong>
</Typography>
{(payload as IFeatureStrategy[])?.map(strategy => (
<Typography>
<strong>
{formatStrategyName((strategy as IFeatureStrategy)?.name)}{' '}
strategy{' '}
</strong>{' '}
</Typography>
))}
<Typography>
from {fromEnvironment} to {environment}
</Typography>
</MsgContainer>
);

View File

@ -0,0 +1,23 @@
import { Typography } from '@mui/material';
import { formatStrategyName } from '../../../../utils/strategyNames';
import { IFeatureStrategy } from '../../../../interfaces/strategy';
export interface CopyStrategyMsg {
payload: IFeatureStrategy | IFeatureStrategy[];
fromEnvironment?: string;
environment?: string;
}
export const CopyStrategyMessage = ({
payload,
fromEnvironment,
environment,
}: CopyStrategyMsg) => (
<Typography>
<strong>
Copy {formatStrategyName((payload as IFeatureStrategy)?.name)}{' '}
strategy{' '}
</strong>{' '}
from {fromEnvironment} to {environment}
</Typography>
);

View File

@ -0,0 +1,18 @@
import { Typography } from '@mui/material';
interface UpdateEnabledMsg {
enabled: boolean;
featureName: string;
environment: string;
}
export const UpdateEnabledMessage = ({
enabled,
featureName,
environment,
}: UpdateEnabledMsg) => (
<Typography>
<strong>{enabled ? 'Disable' : 'Enable'}</strong> feature toggle{' '}
<strong>{featureName}</strong> in <strong>{environment}</strong>
</Typography>
);

View File

@ -1,3 +1,31 @@
import { IFeatureStrategy } from '../../interfaces/strategy';
import { IUser } from '../../interfaces/user';
export interface IChangeRequest {
id: number;
state: ChangeRequestState;
project: string;
environment: string;
createdBy: Pick<IUser, 'id' | 'username' | 'imageUrl'>;
createdAt: Date;
features: IChangeRequestFeature[];
}
export interface IChangeRequestFeature {
name: string;
conflict?: string;
changes: IChangeRequestEvent[];
}
export interface IChangeRequestBase {
id?: number;
action: ChangeRequestAction;
payload: ChangeRequestPayload;
conflict?: string;
createdBy?: Pick<IUser, 'id' | 'username' | 'imageUrl'>;
createdAt?: Date;
}
export type ChangeRequestState = export type ChangeRequestState =
| 'Draft' | 'Draft'
| 'Approved' | 'Approved'
@ -5,32 +33,53 @@ export type ChangeRequestState =
| 'Applied' | 'Applied'
| 'Cancelled'; | 'Cancelled';
export interface IChangeRequest { type ChangeRequestPayload =
id: number; | ChangeRequestEnabled
environment: string; | ChangeRequestAddStrategy
state: ChangeRequestState; | ChangeRequestEditStrategy
project: string; | ChangeRequestDeleteStrategy;
createdBy: ICreatedBy;
createdAt: string; export interface IChangeRequestAddStrategy extends IChangeRequestBase {
features: IChangeRequestFeatures[]; action: 'addStrategy';
payload: ChangeRequestAddStrategy;
} }
interface ICreatedBy { export interface IChangeRequestDeleteStrategy extends IChangeRequestBase {
id: number; action: 'deleteStrategy';
username: string; payload: ChangeRequestDeleteStrategy;
imageUrl: string;
} }
interface IChangeRequestFeatures { export interface IChangeRequestUpdateStrategy extends IChangeRequestBase {
name: string; action: 'updateStrategy';
changes: IChangeRequestFeatureChanges[]; payload: ChangeRequestEditStrategy;
} }
interface IChangeRequestFeatureChanges { export interface IChangeRequestEnabled extends IChangeRequestBase {
id: number; action: 'updateEnabled';
action: string; payload: ChangeRequestEnabled;
payload: unknown;
createdAt: string;
createdBy: ICreatedBy;
warning?: string;
} }
export type IChangeRequestEvent =
| IChangeRequestAddStrategy
| IChangeRequestDeleteStrategy
| IChangeRequestUpdateStrategy
| IChangeRequestEnabled;
type ChangeRequestEnabled = { enabled: boolean };
type ChangeRequestAddStrategy = Pick<
IFeatureStrategy,
'parameters' | 'constraints'
> & { name: string };
type ChangeRequestEditStrategy = ChangeRequestAddStrategy & { id: string };
type ChangeRequestDeleteStrategy = {
id: string;
};
export type ChangeRequestAction =
| 'updateEnabled'
| 'addStrategy'
| 'updateStrategy'
| 'deleteStrategy';

View File

@ -12,8 +12,10 @@ import { useFeatureImmutable } from 'hooks/api/getters/useFeature/useFeatureImmu
import { getFeatureStrategyIcon } from 'utils/strategyNames'; import { getFeatureStrategyIcon } from 'utils/strategyNames';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { CopyButton } from './CopyButton/CopyButton'; import { CopyButton } from './CopyButton/CopyButton';
import { useSegments } from '../../../../hooks/api/getters/useSegments/useSegments'; import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
import { IFeatureStrategyPayload } from '../../../../interfaces/strategy'; import { useChangeRequestAddStrategy } from '../../../../hooks/useChangeRequestAddStrategy';
import { ChangeRequestDialogue } from '../../../changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog';
import { CopyStrategiesMessage } from '../../../changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/CopyStrategiesMessage';
interface IFeatureStrategyEmptyProps { interface IFeatureStrategyEmptyProps {
projectId: string; projectId: string;
@ -42,6 +44,16 @@ export const FeatureStrategyEmpty = ({
environment.strategies.length > 0 environment.strategies.length > 0
); );
const { uiConfig } = useUiConfig();
const changeRequestsEnabled = uiConfig?.flags?.changeRequests;
const {
changeRequestDialogDetails,
onChangeRequestAddStrategies,
onChangeRequestAddStrategiesConfirm,
onChangeRequestAddStrategyClose,
} = useChangeRequestAddStrategy(projectId, featureId, 'addStrategy');
const onAfterAddStrategy = (multiple = false) => { const onAfterAddStrategy = (multiple = false) => {
refetchFeature(); refetchFeature();
refetchFeatureImmutable(); refetchFeatureImmutable();
@ -61,6 +73,15 @@ export const FeatureStrategyEmpty = ({
environment => environment.name === fromEnvironmentName environment => environment.name === fromEnvironmentName
)?.strategies || []; )?.strategies || [];
if (changeRequestsEnabled) {
await onChangeRequestAddStrategies(
environmentId,
strategies,
fromEnvironmentName
);
return;
}
try { try {
await Promise.all( await Promise.all(
strategies.map(strategy => { strategies.map(strategy => {
@ -118,77 +139,96 @@ export const FeatureStrategyEmpty = ({
otherAvailableEnvironments && otherAvailableEnvironments.length > 0; otherAvailableEnvironments && otherAvailableEnvironments.length > 0;
return ( return (
<div className={styles.container}> <>
<div className={styles.title}> <ChangeRequestDialogue
You have not defined any strategies yet. isOpen={changeRequestDialogDetails.isOpen}
onClose={onChangeRequestAddStrategyClose}
environment={changeRequestDialogDetails?.environment}
onConfirm={onChangeRequestAddStrategiesConfirm}
messageComponent={
<CopyStrategiesMessage
fromEnvironment={
changeRequestDialogDetails.fromEnvironment!
}
payload={changeRequestDialogDetails.strategies!}
/>
}
/>
<div className={styles.container}>
<div className={styles.title}>
You have not defined any strategies yet.
</div>
<p className={styles.description}>
Strategies added in this environment will only be executed
if the SDK is using an{' '}
<Link to="/admin/api">API key configured</Link> for this
environment.
</p>
<Box
sx={{
w: '100%',
display: 'flex',
flexWrap: 'wrap',
gap: 2,
alignItems: 'center',
justifyContent: 'center',
}}
>
<FeatureStrategyMenu
label="Add your first strategy"
projectId={projectId}
featureId={featureId}
environmentId={environmentId}
matchWidth={canCopyFromOtherEnvironment}
/>
<ConditionallyRender
condition={canCopyFromOtherEnvironment}
show={
<CopyButton
environmentId={environmentId}
environments={otherAvailableEnvironments.map(
environment => environment.name
)}
onClick={onCopyStrategies}
/>
}
/>
</Box>
<Box sx={{ width: '100%', mt: 3 }}>
<SectionSeparator>
Or use a strategy template
</SectionSeparator>
</Box>
<Box
sx={{
display: 'grid',
width: '100%',
gap: 2,
gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr' },
}}
>
<PresetCard
title="Standard strategy"
Icon={getFeatureStrategyIcon('default')}
onClick={onAddSimpleStrategy}
projectId={projectId}
environmentId={environmentId}
>
The standard strategy is strictly on/off for your entire
userbase.
</PresetCard>
<PresetCard
title="Gradual rollout"
Icon={getFeatureStrategyIcon('flexibleRollout')}
onClick={onAddGradualRolloutStrategy}
projectId={projectId}
environmentId={environmentId}
>
Roll out to a percentage of your userbase.
</PresetCard>
</Box>
</div> </div>
<p className={styles.description}> </>
Strategies added in this environment will only be executed if
the SDK is using an{' '}
<Link to="/admin/api">API key configured</Link> for this
environment.
</p>
<Box
sx={{
w: '100%',
display: 'flex',
flexWrap: 'wrap',
gap: 2,
alignItems: 'center',
justifyContent: 'center',
}}
>
<FeatureStrategyMenu
label="Add your first strategy"
projectId={projectId}
featureId={featureId}
environmentId={environmentId}
matchWidth={canCopyFromOtherEnvironment}
/>
<ConditionallyRender
condition={canCopyFromOtherEnvironment}
show={
<CopyButton
environmentId={environmentId}
environments={otherAvailableEnvironments.map(
environment => environment.name
)}
onClick={onCopyStrategies}
/>
}
/>
</Box>
<Box sx={{ width: '100%', mt: 3 }}>
<SectionSeparator>Or use a strategy template</SectionSeparator>
</Box>
<Box
sx={{
display: 'grid',
width: '100%',
gap: 2,
gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr' },
}}
>
<PresetCard
title="Standard strategy"
Icon={getFeatureStrategyIcon('default')}
onClick={onAddSimpleStrategy}
projectId={projectId}
environmentId={environmentId}
>
The standard strategy is strictly on/off for your entire
userbase.
</PresetCard>
<PresetCard
title="Gradual rollout"
Icon={getFeatureStrategyIcon('flexibleRollout')}
onClick={onAddGradualRolloutStrategy}
projectId={projectId}
environmentId={environmentId}
>
Roll out to a percentage of your userbase.
</PresetCard>
</Box>
</div>
); );
}; };

View File

@ -10,9 +10,9 @@ import React from 'react';
import { formatUnknownError } from 'utils/formatUnknownError'; import { formatUnknownError } from 'utils/formatUnknownError';
import { useStyles } from './FeatureOverviewEnvSwitch.styles'; import { useStyles } from './FeatureOverviewEnvSwitch.styles';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { useChangeRequestToggle } from 'hooks/useChangeRequestToggle'; import { useChangeRequestToggle } from 'hooks/useChangeRequestToggle';
import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog'; import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog';
import { UpdateEnabledMessage } from '../../../../../changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/UpdateEnabledMessage';
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
interface IFeatureOverviewEnvSwitchProps { interface IFeatureOverviewEnvSwitchProps {
@ -123,9 +123,15 @@ const FeatureOverviewEnvSwitch = ({
<ChangeRequestDialogue <ChangeRequestDialogue
isOpen={changeRequestDialogDetails.isOpen} isOpen={changeRequestDialogDetails.isOpen}
onClose={onChangeRequestToggleClose} onClose={onChangeRequestToggleClose}
featureName={featureId}
environment={changeRequestDialogDetails?.environment} environment={changeRequestDialogDetails?.environment}
onConfirm={onChangeRequestToggleConfirm} onConfirm={onChangeRequestToggleConfirm}
messageComponent={
<UpdateEnabledMessage
enabled={changeRequestDialogDetails?.enabled!}
featureName={changeRequestDialogDetails?.featureName!}
environment={changeRequestDialogDetails.environment!}
/>
}
/> />
</div> </div>
); );

View File

@ -8,7 +8,7 @@ import {
Tooltip, Tooltip,
} from '@mui/material'; } from '@mui/material';
import { AddToPhotos as CopyIcon, Lock } from '@mui/icons-material'; import { AddToPhotos as CopyIcon, Lock } from '@mui/icons-material';
import { IFeatureStrategy, IFeatureStrategyPayload } from 'interfaces/strategy'; import { IFeatureStrategy } from 'interfaces/strategy';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { IFeatureEnvironment } from 'interfaces/featureToggle'; import { IFeatureEnvironment } from 'interfaces/featureToggle';
import AccessContext from 'contexts/AccessContext'; import AccessContext from 'contexts/AccessContext';
@ -19,20 +19,24 @@ import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFe
import useToast from 'hooks/useToast'; import useToast from 'hooks/useToast';
import { useFeatureImmutable } from 'hooks/api/getters/useFeature/useFeatureImmutable'; import { useFeatureImmutable } from 'hooks/api/getters/useFeature/useFeatureImmutable';
import { formatUnknownError } from 'utils/formatUnknownError'; import { formatUnknownError } from 'utils/formatUnknownError';
import { useSegments } from '../../../../../../../../../../hooks/api/getters/useSegments/useSegments'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { useChangeRequestAddStrategy } from 'hooks/useChangeRequestAddStrategy';
import { ChangeRequestDialogue } from '../../../../../../../../../changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog';
import { CopyStrategyMessage } from '../../../../../../../../../changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/CopyStrategyMessage';
interface ICopyStrategyIconMenuProps { interface ICopyStrategyIconMenuProps {
environmentId: string;
environments: IFeatureEnvironment['name'][]; environments: IFeatureEnvironment['name'][];
strategy: IFeatureStrategy; strategy: IFeatureStrategy;
} }
export const CopyStrategyIconMenu: VFC<ICopyStrategyIconMenuProps> = ({ export const CopyStrategyIconMenu: VFC<ICopyStrategyIconMenuProps> = ({
environmentId,
environments, environments,
strategy, strategy,
}) => { }) => {
const projectId = useRequiredPathParam('projectId'); const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId'); const featureId = useRequiredPathParam('featureId');
const { segments } = useSegments(strategy.id);
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
@ -47,19 +51,40 @@ export const CopyStrategyIconMenu: VFC<ICopyStrategyIconMenuProps> = ({
setAnchorEl(null); setAnchorEl(null);
}; };
const { hasAccess } = useContext(AccessContext); const { hasAccess } = useContext(AccessContext);
const onClick = async (environmentId: string) => { const { uiConfig } = useUiConfig();
const changeRequestsEnabled = uiConfig?.flags?.changeRequests;
const {
changeRequestDialogDetails,
onChangeRequestAddStrategyClose,
onChangeRequestAddStrategy,
onChangeRequestAddStrategyConfirm,
} = useChangeRequestAddStrategy(projectId, featureId, 'addStrategy');
const onCopyStrategy = async (environment: string) => {
const { id, ...strategyCopy } = { const { id, ...strategyCopy } = {
...strategy, ...strategy,
environment: environmentId, environment,
copyOf: strategy.id, copyOf: strategy.id,
}; };
if (changeRequestsEnabled) {
await onChangeRequestAddStrategy(
environment,
{
id,
...strategyCopy,
},
environmentId
);
return;
}
try { try {
await addStrategyToFeature( await addStrategyToFeature(
projectId, projectId,
featureId, featureId,
environmentId, environmentId,
strategyCopy strategy
); );
refetchFeature(); refetchFeature();
refetchFeatureImmutable(); refetchFeatureImmutable();
@ -80,6 +105,20 @@ export const CopyStrategyIconMenu: VFC<ICopyStrategyIconMenuProps> = ({
return ( return (
<div> <div>
<ChangeRequestDialogue
isOpen={changeRequestDialogDetails.isOpen}
onClose={onChangeRequestAddStrategyClose}
environment={changeRequestDialogDetails?.environment}
onConfirm={onChangeRequestAddStrategyConfirm}
messageComponent={
<CopyStrategyMessage
fromEnvironment={
changeRequestDialogDetails.fromEnvironment!
}
payload={changeRequestDialogDetails.strategy!}
/>
}
/>
<Tooltip <Tooltip
title={`Copy to another environment${ title={`Copy to another environment${
enabled ? '' : ' (Access denied)' enabled ? '' : ' (Access denied)'
@ -128,7 +167,7 @@ export const CopyStrategyIconMenu: VFC<ICopyStrategyIconMenuProps> = ({
> >
<div> <div>
<MenuItem <MenuItem
onClick={() => onClick(environment)} onClick={() => onCopyStrategy(environment)}
disabled={!access} disabled={!access}
> >
<ConditionallyRender <ConditionallyRender

View File

@ -54,6 +54,7 @@ export const StrategyItem: VFC<IStrategyItemProps> = ({
)} )}
show={() => ( show={() => (
<CopyStrategyIconMenu <CopyStrategyIconMenu
environmentId={environmentId}
environments={otherEnvironments as string[]} environments={otherEnvironments as string[]}
strategy={strategy} strategy={strategy}
/> />

View File

@ -38,6 +38,8 @@ import { useMediaQuery } from '@mui/material';
import { Search } from 'component/common/Search/Search'; import { Search } from 'component/common/Search/Search';
import { useChangeRequestToggle } from 'hooks/useChangeRequestToggle'; import { useChangeRequestToggle } from 'hooks/useChangeRequestToggle';
import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog'; import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog';
import { CopyStrategyMessage } from '../../../changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/CopyStrategyMessage';
import { UpdateEnabledMessage } from '../../../changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/UpdateEnabledMessage';
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
interface IProjectFeatureTogglesProps { interface IProjectFeatureTogglesProps {
@ -524,9 +526,15 @@ export const ProjectFeatureToggles = ({
<ChangeRequestDialogue <ChangeRequestDialogue
isOpen={changeRequestDialogDetails.isOpen} isOpen={changeRequestDialogDetails.isOpen}
onClose={onChangeRequestToggleClose} onClose={onChangeRequestToggleClose}
featureName={changeRequestDialogDetails?.featureName}
environment={changeRequestDialogDetails?.environment} environment={changeRequestDialogDetails?.environment}
onConfirm={onChangeRequestToggleConfirm} onConfirm={onChangeRequestToggleConfirm}
messageComponent={
<UpdateEnabledMessage
featureName={changeRequestDialogDetails.featureName!}
enabled={changeRequestDialogDetails.enabled!}
environment={changeRequestDialogDetails?.environment!}
/>
}
/> />
</PageContent> </PageContent>
); );

View File

@ -1,6 +1,6 @@
import useAPI from '../useApi/useApi'; import useAPI from '../useApi/useApi';
interface IChangeRequestsSchema { export interface IChangeRequestsSchema {
feature: string; feature: string;
action: action:
| 'updateEnabled' | 'updateEnabled'

View File

@ -2,10 +2,7 @@ import useSWR from 'swr';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { formatApiPath } from 'utils/formatPath'; import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler'; import handleErrorResponses from '../httpErrorResponseHandler';
import { import { IChangeRequest } from 'component/changeRequest/changeRequest.types';
ChangeRequestState,
IChangeRequest,
} from 'component/changeRequest/changeRequest.types';
const fetcher = (path: string) => { const fetcher = (path: string) => {
return fetch(path) return fetch(path)

View File

@ -0,0 +1,131 @@
import { useCallback, useState } from 'react';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import {
IFeatureStrategy,
IFeatureStrategyPayload,
} from '../interfaces/strategy';
import { useChangeRequestApi } from './api/actions/useChangeRequestApi/useChangeRequestApi';
import { useChangeRequestOpen } from './api/getters/useChangeRequestOpen/useChangeRequestOpen';
export type ChangeRequestStrategyAction =
| 'addStrategy'
| 'updateStrategy'
| 'deleteStrategy';
export const useChangeRequestAddStrategy = (
project: string,
featureName: string,
action: ChangeRequestStrategyAction
) => {
const { setToastData, setToastApiError } = useToast();
const { addChangeRequest } = useChangeRequestApi();
const { refetch } = useChangeRequestOpen(project);
const [changeRequestDialogDetails, setChangeRequestDialogDetails] =
useState<{
strategy?: IFeatureStrategy;
strategies?: IFeatureStrategy[];
featureName?: string;
environment?: string;
fromEnvironment?: string;
isOpen: boolean;
}>({ isOpen: false });
const onChangeRequestAddStrategy = useCallback(
(
environment: string,
strategy: IFeatureStrategy,
fromEnvironment?: string
) => {
setChangeRequestDialogDetails({
featureName,
environment,
fromEnvironment,
strategy,
isOpen: true,
});
},
[]
);
const onChangeRequestAddStrategies = useCallback(
(
environment: string,
strategies: IFeatureStrategy[],
fromEnvironment: string
) => {
setChangeRequestDialogDetails({
featureName,
environment,
fromEnvironment,
strategies,
isOpen: true,
});
},
[]
);
const onChangeRequestAddStrategyClose = useCallback(() => {
setChangeRequestDialogDetails({ isOpen: false });
}, []);
const onChangeRequestAddStrategyConfirm = useCallback(async () => {
try {
await addChangeRequest(
project,
changeRequestDialogDetails.environment!,
{
feature: changeRequestDialogDetails.featureName!,
action: action,
payload: changeRequestDialogDetails.strategy!,
}
);
refetch();
setChangeRequestDialogDetails({ isOpen: false });
setToastData({
type: 'success',
title: 'Changes added to the draft!',
});
} catch (error) {
setToastApiError(formatUnknownError(error));
setChangeRequestDialogDetails({ isOpen: false });
}
}, [addChangeRequest]);
const onChangeRequestAddStrategiesConfirm = useCallback(async () => {
try {
await Promise.all(
changeRequestDialogDetails.strategies!.map(strategy => {
return addChangeRequest(
project,
changeRequestDialogDetails.environment!,
{
feature: changeRequestDialogDetails.featureName!,
action: action,
payload: strategy,
}
);
})
);
refetch();
setChangeRequestDialogDetails({ isOpen: false });
setToastData({
type: 'success',
title: 'Changes added to the draft!',
});
} catch (error) {
setToastApiError(formatUnknownError(error));
setChangeRequestDialogDetails({ isOpen: false });
}
}, [addChangeRequest]);
return {
onChangeRequestAddStrategy,
onChangeRequestAddStrategies,
onChangeRequestAddStrategyClose,
onChangeRequestAddStrategyConfirm,
onChangeRequestAddStrategiesConfirm,
changeRequestDialogDetails,
};
};