mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01: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