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:
parent
a267f13a7d
commit
c1e0bd83b0
@ -7,7 +7,20 @@ import { ToggleStatusChange } from '../ChangeRequestOverview/ChangeRequestFeatur
|
||||
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
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 {
|
||||
changeRequest: IChangeRequest;
|
||||
@ -54,21 +67,29 @@ export const ChangeRequest: VFC<IChangeRequestProps> = ({
|
||||
condition={change.action === 'updateEnabled'}
|
||||
show={
|
||||
<ToggleStatusChange
|
||||
// @ts-expect-error TODO: fix types
|
||||
enabled={change?.payload?.enabled}
|
||||
onDiscard={onDiscard(change.id)}
|
||||
enabled={
|
||||
(change as IChangeRequestEnabled)
|
||||
?.payload?.enabled
|
||||
}
|
||||
onDiscard={onDiscard(change.id!)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{/* <ConditionallyRender
|
||||
<ConditionallyRender
|
||||
condition={change.action === 'addStrategy'}
|
||||
show={
|
||||
<StrategyAddedChange>
|
||||
<GetFeatureStrategyIcon
|
||||
strategyName={change.payload.name}
|
||||
strategyName={
|
||||
(
|
||||
change as IChangeRequestAddStrategy
|
||||
)?.payload.name!
|
||||
}
|
||||
/>
|
||||
{formatStrategyName(
|
||||
change.payload.name
|
||||
(
|
||||
change as IChangeRequestAddStrategy
|
||||
)?.payload.name!
|
||||
)}
|
||||
</StrategyAddedChange>
|
||||
}
|
||||
@ -80,7 +101,7 @@ export const ChangeRequest: VFC<IChangeRequestProps> = ({
|
||||
<ConditionallyRender
|
||||
condition={change.action === 'updateStrategy'}
|
||||
show={<StrategyEditedChange />}
|
||||
/> */}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</ChangeRequestFeatureToggleChange>
|
||||
|
@ -6,38 +6,39 @@ interface IChangeRequestDialogueProps {
|
||||
isOpen: boolean;
|
||||
onConfirm: () => void;
|
||||
onClose: () => void;
|
||||
featureName?: string;
|
||||
environment?: string;
|
||||
enabled?: boolean;
|
||||
showBanner?: boolean;
|
||||
messageComponent: JSX.Element;
|
||||
}
|
||||
|
||||
export const ChangeRequestDialogue: FC<IChangeRequestDialogueProps> = ({
|
||||
isOpen,
|
||||
onConfirm,
|
||||
onClose,
|
||||
enabled,
|
||||
featureName,
|
||||
showBanner,
|
||||
environment,
|
||||
messageComponent,
|
||||
}) => (
|
||||
<Dialogue
|
||||
open={isOpen}
|
||||
primaryButtonText="Add to draft"
|
||||
primaryButtonText="Add suggestion to draft"
|
||||
secondaryButtonText="Cancel"
|
||||
onClick={onConfirm}
|
||||
onClose={onClose}
|
||||
title="Request changes"
|
||||
fullWidth
|
||||
>
|
||||
<Alert severity="info" sx={{ mb: 2 }}>
|
||||
Change requests is enabled for {environment}. Your changes needs to
|
||||
be approved before they will be live. All the changes you do now
|
||||
will be added into a draft that you can submit for review.
|
||||
</Alert>
|
||||
{showBanner && (
|
||||
<Alert severity="info" sx={{ mb: 2 }}>
|
||||
Change requests feature is enabled for {environment}. Your
|
||||
changes needs to be approved before they will be live. All the
|
||||
changes you do now will be added into a draft that you can
|
||||
submit for review.
|
||||
</Alert>
|
||||
)}
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Change requests:
|
||||
</Typography>
|
||||
<Typography>
|
||||
<strong>{enabled ? 'Disable' : 'Enable'}</strong> feature toggle{' '}
|
||||
<strong>{featureName}</strong> in <strong>{environment}</strong>
|
||||
Your suggestion:
|
||||
</Typography>
|
||||
{messageComponent}
|
||||
</Dialogue>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
@ -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>
|
||||
);
|
@ -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>
|
||||
);
|
@ -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 =
|
||||
| 'Draft'
|
||||
| 'Approved'
|
||||
@ -5,32 +33,53 @@ export type ChangeRequestState =
|
||||
| 'Applied'
|
||||
| 'Cancelled';
|
||||
|
||||
export interface IChangeRequest {
|
||||
id: number;
|
||||
environment: string;
|
||||
state: ChangeRequestState;
|
||||
project: string;
|
||||
createdBy: ICreatedBy;
|
||||
createdAt: string;
|
||||
features: IChangeRequestFeatures[];
|
||||
type ChangeRequestPayload =
|
||||
| ChangeRequestEnabled
|
||||
| ChangeRequestAddStrategy
|
||||
| ChangeRequestEditStrategy
|
||||
| ChangeRequestDeleteStrategy;
|
||||
|
||||
export interface IChangeRequestAddStrategy extends IChangeRequestBase {
|
||||
action: 'addStrategy';
|
||||
payload: ChangeRequestAddStrategy;
|
||||
}
|
||||
|
||||
interface ICreatedBy {
|
||||
id: number;
|
||||
username: string;
|
||||
imageUrl: string;
|
||||
export interface IChangeRequestDeleteStrategy extends IChangeRequestBase {
|
||||
action: 'deleteStrategy';
|
||||
payload: ChangeRequestDeleteStrategy;
|
||||
}
|
||||
|
||||
interface IChangeRequestFeatures {
|
||||
name: string;
|
||||
changes: IChangeRequestFeatureChanges[];
|
||||
export interface IChangeRequestUpdateStrategy extends IChangeRequestBase {
|
||||
action: 'updateStrategy';
|
||||
payload: ChangeRequestEditStrategy;
|
||||
}
|
||||
|
||||
interface IChangeRequestFeatureChanges {
|
||||
id: number;
|
||||
action: string;
|
||||
payload: unknown;
|
||||
createdAt: string;
|
||||
createdBy: ICreatedBy;
|
||||
warning?: string;
|
||||
export interface IChangeRequestEnabled extends IChangeRequestBase {
|
||||
action: 'updateEnabled';
|
||||
payload: ChangeRequestEnabled;
|
||||
}
|
||||
|
||||
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';
|
||||
|
@ -12,8 +12,10 @@ import { useFeatureImmutable } from 'hooks/api/getters/useFeature/useFeatureImmu
|
||||
import { getFeatureStrategyIcon } from 'utils/strategyNames';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { CopyButton } from './CopyButton/CopyButton';
|
||||
import { useSegments } from '../../../../hooks/api/getters/useSegments/useSegments';
|
||||
import { IFeatureStrategyPayload } from '../../../../interfaces/strategy';
|
||||
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { useChangeRequestAddStrategy } from '../../../../hooks/useChangeRequestAddStrategy';
|
||||
import { ChangeRequestDialogue } from '../../../changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog';
|
||||
import { CopyStrategiesMessage } from '../../../changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/CopyStrategiesMessage';
|
||||
|
||||
interface IFeatureStrategyEmptyProps {
|
||||
projectId: string;
|
||||
@ -42,6 +44,16 @@ export const FeatureStrategyEmpty = ({
|
||||
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) => {
|
||||
refetchFeature();
|
||||
refetchFeatureImmutable();
|
||||
@ -61,6 +73,15 @@ export const FeatureStrategyEmpty = ({
|
||||
environment => environment.name === fromEnvironmentName
|
||||
)?.strategies || [];
|
||||
|
||||
if (changeRequestsEnabled) {
|
||||
await onChangeRequestAddStrategies(
|
||||
environmentId,
|
||||
strategies,
|
||||
fromEnvironmentName
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await Promise.all(
|
||||
strategies.map(strategy => {
|
||||
@ -118,77 +139,96 @@ export const FeatureStrategyEmpty = ({
|
||||
otherAvailableEnvironments && otherAvailableEnvironments.length > 0;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.title}>
|
||||
You have not defined any strategies yet.
|
||||
<>
|
||||
<ChangeRequestDialogue
|
||||
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>
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -10,9 +10,9 @@ import React from 'react';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { useStyles } from './FeatureOverviewEnvSwitch.styles';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { useChangeRequestToggle } from 'hooks/useChangeRequestToggle';
|
||||
import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog';
|
||||
import { UpdateEnabledMessage } from '../../../../../changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/UpdateEnabledMessage';
|
||||
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||
|
||||
interface IFeatureOverviewEnvSwitchProps {
|
||||
@ -123,9 +123,15 @@ const FeatureOverviewEnvSwitch = ({
|
||||
<ChangeRequestDialogue
|
||||
isOpen={changeRequestDialogDetails.isOpen}
|
||||
onClose={onChangeRequestToggleClose}
|
||||
featureName={featureId}
|
||||
environment={changeRequestDialogDetails?.environment}
|
||||
onConfirm={onChangeRequestToggleConfirm}
|
||||
messageComponent={
|
||||
<UpdateEnabledMessage
|
||||
enabled={changeRequestDialogDetails?.enabled!}
|
||||
featureName={changeRequestDialogDetails?.featureName!}
|
||||
environment={changeRequestDialogDetails.environment!}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
Tooltip,
|
||||
} from '@mui/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 { IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||
import AccessContext from 'contexts/AccessContext';
|
||||
@ -19,20 +19,24 @@ import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFe
|
||||
import useToast from 'hooks/useToast';
|
||||
import { useFeatureImmutable } from 'hooks/api/getters/useFeature/useFeatureImmutable';
|
||||
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 {
|
||||
environmentId: string;
|
||||
environments: IFeatureEnvironment['name'][];
|
||||
strategy: IFeatureStrategy;
|
||||
}
|
||||
|
||||
export const CopyStrategyIconMenu: VFC<ICopyStrategyIconMenuProps> = ({
|
||||
environmentId,
|
||||
environments,
|
||||
strategy,
|
||||
}) => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const { segments } = useSegments(strategy.id);
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
@ -47,19 +51,40 @@ export const CopyStrategyIconMenu: VFC<ICopyStrategyIconMenuProps> = ({
|
||||
setAnchorEl(null);
|
||||
};
|
||||
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 } = {
|
||||
...strategy,
|
||||
environment: environmentId,
|
||||
environment,
|
||||
copyOf: strategy.id,
|
||||
};
|
||||
if (changeRequestsEnabled) {
|
||||
await onChangeRequestAddStrategy(
|
||||
environment,
|
||||
{
|
||||
id,
|
||||
...strategyCopy,
|
||||
},
|
||||
environmentId
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await addStrategyToFeature(
|
||||
projectId,
|
||||
featureId,
|
||||
environmentId,
|
||||
strategyCopy
|
||||
strategy
|
||||
);
|
||||
refetchFeature();
|
||||
refetchFeatureImmutable();
|
||||
@ -80,6 +105,20 @@ export const CopyStrategyIconMenu: VFC<ICopyStrategyIconMenuProps> = ({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ChangeRequestDialogue
|
||||
isOpen={changeRequestDialogDetails.isOpen}
|
||||
onClose={onChangeRequestAddStrategyClose}
|
||||
environment={changeRequestDialogDetails?.environment}
|
||||
onConfirm={onChangeRequestAddStrategyConfirm}
|
||||
messageComponent={
|
||||
<CopyStrategyMessage
|
||||
fromEnvironment={
|
||||
changeRequestDialogDetails.fromEnvironment!
|
||||
}
|
||||
payload={changeRequestDialogDetails.strategy!}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Tooltip
|
||||
title={`Copy to another environment${
|
||||
enabled ? '' : ' (Access denied)'
|
||||
@ -128,7 +167,7 @@ export const CopyStrategyIconMenu: VFC<ICopyStrategyIconMenuProps> = ({
|
||||
>
|
||||
<div>
|
||||
<MenuItem
|
||||
onClick={() => onClick(environment)}
|
||||
onClick={() => onCopyStrategy(environment)}
|
||||
disabled={!access}
|
||||
>
|
||||
<ConditionallyRender
|
||||
|
@ -54,6 +54,7 @@ export const StrategyItem: VFC<IStrategyItemProps> = ({
|
||||
)}
|
||||
show={() => (
|
||||
<CopyStrategyIconMenu
|
||||
environmentId={environmentId}
|
||||
environments={otherEnvironments as string[]}
|
||||
strategy={strategy}
|
||||
/>
|
||||
|
@ -38,6 +38,8 @@ import { useMediaQuery } from '@mui/material';
|
||||
import { Search } from 'component/common/Search/Search';
|
||||
import { useChangeRequestToggle } from 'hooks/useChangeRequestToggle';
|
||||
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';
|
||||
|
||||
interface IProjectFeatureTogglesProps {
|
||||
@ -524,9 +526,15 @@ export const ProjectFeatureToggles = ({
|
||||
<ChangeRequestDialogue
|
||||
isOpen={changeRequestDialogDetails.isOpen}
|
||||
onClose={onChangeRequestToggleClose}
|
||||
featureName={changeRequestDialogDetails?.featureName}
|
||||
environment={changeRequestDialogDetails?.environment}
|
||||
onConfirm={onChangeRequestToggleConfirm}
|
||||
messageComponent={
|
||||
<UpdateEnabledMessage
|
||||
featureName={changeRequestDialogDetails.featureName!}
|
||||
enabled={changeRequestDialogDetails.enabled!}
|
||||
environment={changeRequestDialogDetails?.environment!}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</PageContent>
|
||||
);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import useAPI from '../useApi/useApi';
|
||||
|
||||
interface IChangeRequestsSchema {
|
||||
export interface IChangeRequestsSchema {
|
||||
feature: string;
|
||||
action:
|
||||
| 'updateEnabled'
|
||||
|
@ -2,10 +2,7 @@ import useSWR from 'swr';
|
||||
import { useMemo } from 'react';
|
||||
import { formatApiPath } from 'utils/formatPath';
|
||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||
import {
|
||||
ChangeRequestState,
|
||||
IChangeRequest,
|
||||
} from 'component/changeRequest/changeRequest.types';
|
||||
import { IChangeRequest } from 'component/changeRequest/changeRequest.types';
|
||||
|
||||
const fetcher = (path: string) => {
|
||||
return fetch(path)
|
||||
|
131
frontend/src/hooks/useChangeRequestAddStrategy.ts
Normal file
131
frontend/src/hooks/useChangeRequestAddStrategy.ts
Normal 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,
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue
Block a user