mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: display change details (#2327)
* feat: display change details * refactor: reorganize components * feat: display deleted strategy name if present * feat: UI tweaks * fix: types * refactor: remove unnecessary checks for types
This commit is contained in:
		
							parent
							
								
									4b281d9513
								
							
						
					
					
						commit
						065833e5d1
					
				| @ -2,15 +2,11 @@ import { VFC } from 'react'; | |||||||
| import { Box } from '@mui/material'; | import { Box } from '@mui/material'; | ||||||
| import { ChangeRequestFeatureToggleChange } from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/ChangeRequestFeatureToggleChange'; | import { ChangeRequestFeatureToggleChange } from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/ChangeRequestFeatureToggleChange'; | ||||||
| import { objectId } from 'utils/objectId'; | import { objectId } from 'utils/objectId'; | ||||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; |  | ||||||
| import { ToggleStatusChange } from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/ToggleStatusChange'; | import { ToggleStatusChange } from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/ToggleStatusChange'; | ||||||
| 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 { | import type { IChangeRequest } from '../changeRequest.types'; | ||||||
|     IChangeRequest, |  | ||||||
|     IChangeRequestAddStrategy, |  | ||||||
| } from '../changeRequest.types'; |  | ||||||
| import { | import { | ||||||
|     StrategyAddedChange, |     StrategyAddedChange, | ||||||
|     StrategyDeletedChange, |     StrategyDeletedChange, | ||||||
| @ -20,7 +16,7 @@ import { | |||||||
|     formatStrategyName, |     formatStrategyName, | ||||||
|     GetFeatureStrategyIcon, |     GetFeatureStrategyIcon, | ||||||
| } from 'utils/strategyNames'; | } from 'utils/strategyNames'; | ||||||
| import { IChangeRequestEnabled } from '../changeRequest.types'; | import { hasNameField } from '../changeRequest.types'; | ||||||
| 
 | 
 | ||||||
| interface IChangeRequestProps { | interface IChangeRequestProps { | ||||||
|     changeRequest: IChangeRequest; |     changeRequest: IChangeRequest; | ||||||
| @ -62,46 +58,60 @@ export const ChangeRequest: VFC<IChangeRequestProps> = ({ | |||||||
|                     onNavigate={onNavigate} |                     onNavigate={onNavigate} | ||||||
|                 > |                 > | ||||||
|                     {featureToggleChange.changes.map(change => ( |                     {featureToggleChange.changes.map(change => ( | ||||||
|                         <Box key={objectId(change)}> |                         <Box | ||||||
|                             <ConditionallyRender |                             key={objectId(change)} | ||||||
|                                 condition={change.action === 'updateEnabled'} |                             sx={theme => ({ | ||||||
|                                 show={ |                                 padding: 2, | ||||||
|  |                                 borderTop: '1px solid', | ||||||
|  |                                 borderColor: theme => | ||||||
|  |                                     theme.palette.dividerAlternative, | ||||||
|  |                             })} | ||||||
|  |                         > | ||||||
|  |                             {change.action === 'updateEnabled' && ( | ||||||
|                                 <ToggleStatusChange |                                 <ToggleStatusChange | ||||||
|                                         enabled={ |                                     enabled={change.payload.enabled} | ||||||
|                                             (change as IChangeRequestEnabled) |                                     onDiscard={onDiscard(change.id)} | ||||||
|                                                 ?.payload?.enabled |  | ||||||
|                                         } |  | ||||||
|                                         onDiscard={onDiscard(change.id!)} |  | ||||||
|                                 /> |                                 /> | ||||||
|                                 } |                             )} | ||||||
|  |                             {change.action === 'addStrategy' && ( | ||||||
|  |                                 <StrategyAddedChange | ||||||
|  |                                     onDiscard={onDiscard(change.id)} | ||||||
|  |                                 > | ||||||
|  |                                     <GetFeatureStrategyIcon | ||||||
|  |                                         strategyName={change.payload.name} | ||||||
|                                     /> |                                     /> | ||||||
|                             <ConditionallyRender | 
 | ||||||
|                                 condition={change.action === 'addStrategy'} |                                     {formatStrategyName(change.payload.name)} | ||||||
|                                 show={ |                                 </StrategyAddedChange> | ||||||
|                                     <StrategyAddedChange> |                             )} | ||||||
|  |                             {change.action === 'deleteStrategy' && ( | ||||||
|  |                                 <StrategyDeletedChange | ||||||
|  |                                     onDiscard={onDiscard(change.id)} | ||||||
|  |                                 > | ||||||
|  |                                     {hasNameField(change.payload) && ( | ||||||
|  |                                         <> | ||||||
|                                             <GetFeatureStrategyIcon |                                             <GetFeatureStrategyIcon | ||||||
|                                                 strategyName={ |                                                 strategyName={ | ||||||
|                                                 ( |                                                     change.payload.name | ||||||
|                                                     change as IChangeRequestAddStrategy |  | ||||||
|                                                 )?.payload.name! |  | ||||||
|                                                 } |                                                 } | ||||||
|                                             /> |                                             /> | ||||||
|                                             {formatStrategyName( |                                             {formatStrategyName( | ||||||
|                                             ( |                                                 change.payload.name | ||||||
|                                                 change as IChangeRequestAddStrategy |  | ||||||
|                                             )?.payload.name! |  | ||||||
|                                             )} |                                             )} | ||||||
|                                     </StrategyAddedChange> |                                         </> | ||||||
|                                 } |                                     )} | ||||||
|                             /> |                                 </StrategyDeletedChange> | ||||||
|                             <ConditionallyRender |                             )} | ||||||
|                                 condition={change.action === 'deleteStrategy'} |                             {change.action === 'updateStrategy' && ( | ||||||
|                                 show={<StrategyDeletedChange />} |                                 <StrategyEditedChange | ||||||
|                             /> |                                     onDiscard={onDiscard(change.id)} | ||||||
|                             <ConditionallyRender |                                 > | ||||||
|                                 condition={change.action === 'updateStrategy'} |                                     <GetFeatureStrategyIcon | ||||||
|                                 show={<StrategyEditedChange />} |                                         strategyName={change.payload.name} | ||||||
|                                     /> |                                     /> | ||||||
|  |                                     {formatStrategyName(change.payload.name)} | ||||||
|  |                                 </StrategyEditedChange> | ||||||
|  |                             )} | ||||||
|                         </Box> |                         </Box> | ||||||
|                     ))} |                     ))} | ||||||
|                 </ChangeRequestFeatureToggleChange> |                 </ChangeRequestFeatureToggleChange> | ||||||
|  | |||||||
| @ -26,7 +26,7 @@ export const ChangeRequestFeatureToggleChange: FC< | |||||||
|             <Box |             <Box | ||||||
|                 sx={theme => ({ |                 sx={theme => ({ | ||||||
|                     backgroundColor: theme.palette.tableHeaderBackground, |                     backgroundColor: theme.palette.tableHeaderBackground, | ||||||
|                     p: 2, |                     padding: theme.spacing(3), | ||||||
|                 })} |                 })} | ||||||
|             > |             > | ||||||
|                 <Box sx={{ display: 'flex', gap: 1 }}> |                 <Box sx={{ display: 'flex', gap: 1 }}> | ||||||
| @ -42,7 +42,7 @@ export const ChangeRequestFeatureToggleChange: FC< | |||||||
|                     </Typography> |                     </Typography> | ||||||
|                 </Box> |                 </Box> | ||||||
|             </Box> |             </Box> | ||||||
|             <Box sx={{ p: 2 }}>{children}</Box> |             <Box>{children}</Box> | ||||||
|         </Card> |         </Card> | ||||||
|     ); |     ); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,27 +1,74 @@ | |||||||
| import { Box, Typography } from '@mui/material'; | import { Box, Link, styled, Typography } from '@mui/material'; | ||||||
| import { FC } from 'react'; | import { FC } from 'react'; | ||||||
| 
 | 
 | ||||||
| export const StrategyAddedChange: FC = ({ children }) => { | interface IStrategyChangeProps { | ||||||
|  |     onDiscard: () => void; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const ChangeItemWrapper = styled(Box)(({ theme }) => ({ | ||||||
|  |     display: 'flex', | ||||||
|  |     justifyContent: 'space-between', | ||||||
|  |     padding: theme.spacing(1), | ||||||
|  | })); | ||||||
|  | 
 | ||||||
|  | const ChangeItemInfo: FC = styled(Box)(({ theme }) => ({ | ||||||
|  |     display: 'flex', | ||||||
|  |     gap: theme.spacing(1), | ||||||
|  | })); | ||||||
|  | 
 | ||||||
|  | const Discard: FC<IStrategyChangeProps> = ({ onDiscard }) => ( | ||||||
|  |     <Box> | ||||||
|  |         <Link onClick={onDiscard}>Discard</Link> | ||||||
|  |     </Box> | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | export const StrategyAddedChange: FC<IStrategyChangeProps> = ({ | ||||||
|  |     children, | ||||||
|  |     onDiscard, | ||||||
|  | }) => { | ||||||
|     return ( |     return ( | ||||||
|         <Box sx={{ p: 1, display: 'flex', gap: 1 }}> |         <ChangeItemWrapper> | ||||||
|             <Typography sx={theme => ({ color: theme.palette.success.main })}> |             <ChangeItemInfo> | ||||||
|                 + Strategy Added: |                 <Typography | ||||||
|  |                     sx={theme => ({ color: theme.palette.success.main })} | ||||||
|  |                 > | ||||||
|  |                     + Adding strategy: | ||||||
|                 </Typography> |                 </Typography> | ||||||
|                 {children} |                 {children} | ||||||
|         </Box> |             </ChangeItemInfo> | ||||||
|  |             <Discard onDiscard={onDiscard} /> | ||||||
|  |         </ChangeItemWrapper> | ||||||
|     ); |     ); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const StrategyEditedChange: FC = () => { | export const StrategyEditedChange: FC<IStrategyChangeProps> = ({ | ||||||
|     return <Box sx={{ p: 1 }}>Strategy Edited</Box>; |     children, | ||||||
| }; |     onDiscard, | ||||||
| 
 | }) => { | ||||||
| export const StrategyDeletedChange: FC = () => { |  | ||||||
|     return ( |     return ( | ||||||
|         <Box sx={{ p: 1 }}> |         <ChangeItemWrapper> | ||||||
|             <Typography sx={theme => ({ color: theme.palette.error.main })}> |             <ChangeItemInfo> | ||||||
|                 - Strategy Deleted |                 <Typography>Editing strategy:</Typography> | ||||||
|             </Typography> |                 {children} | ||||||
|         </Box> |             </ChangeItemInfo> | ||||||
|  |             <Discard onDiscard={onDiscard} /> | ||||||
|  |         </ChangeItemWrapper> | ||||||
|  |     ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const StrategyDeletedChange: FC<IStrategyChangeProps> = ({ | ||||||
|  |     onDiscard, | ||||||
|  |     children, | ||||||
|  | }) => { | ||||||
|  |     return ( | ||||||
|  |         <ChangeItemWrapper> | ||||||
|  |             <ChangeItemInfo> | ||||||
|  |                 <Typography sx={theme => ({ color: theme.palette.error.main })}> | ||||||
|  |                     - Deleting strategy | ||||||
|  |                 </Typography> | ||||||
|  |                 {children} | ||||||
|  |             </ChangeItemInfo> | ||||||
|  |             <Discard onDiscard={onDiscard} /> | ||||||
|  |         </ChangeItemWrapper> | ||||||
|     ); |     ); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ import { VFC } from 'react'; | |||||||
| import { Link, Box } from '@mui/material'; | import { Link, Box } from '@mui/material'; | ||||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||||
| import { Badge } from 'component/common/Badge/Badge'; | import { Badge } from 'component/common/Badge/Badge'; | ||||||
|  | import { ChangeItemWrapper } from './StrategyChange'; | ||||||
| 
 | 
 | ||||||
| interface IPlaygroundResultsTable { | interface IPlaygroundResultsTable { | ||||||
|     enabled: boolean; |     enabled: boolean; | ||||||
| @ -13,7 +14,7 @@ export const ToggleStatusChange: VFC<IPlaygroundResultsTable> = ({ | |||||||
|     onDiscard, |     onDiscard, | ||||||
| }) => { | }) => { | ||||||
|     return ( |     return ( | ||||||
|         <Box sx={{ p: 1, display: 'flex', justifyContent: 'space-between' }}> |         <ChangeItemWrapper> | ||||||
|             <Box> |             <Box> | ||||||
|                 New status:{' '} |                 New status:{' '} | ||||||
|                 <Badge color={enabled ? 'success' : 'error'}> |                 <Badge color={enabled ? 'success' : 'error'}> | ||||||
| @ -28,6 +29,6 @@ export const ToggleStatusChange: VFC<IPlaygroundResultsTable> = ({ | |||||||
|                     </Box> |                     </Box> | ||||||
|                 } |                 } | ||||||
|             /> |             /> | ||||||
|         </Box> |         </ChangeItemWrapper> | ||||||
|     ); |     ); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -6,6 +6,7 @@ import { | |||||||
|     styled, |     styled, | ||||||
|     Tooltip, |     Tooltip, | ||||||
|     Divider, |     Divider, | ||||||
|  |     IconButton, | ||||||
| } from '@mui/material'; | } from '@mui/material'; | ||||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||||
| import { SidebarModal } from 'component/common/SidebarModal/SidebarModal'; | import { SidebarModal } from 'component/common/SidebarModal/SidebarModal'; | ||||||
| @ -17,6 +18,7 @@ import { ChangeRequest } from '../ChangeRequest/ChangeRequest'; | |||||||
| import { useChangeRequestOpen } from 'hooks/api/getters/useChangeRequestOpen/useChangeRequestOpen'; | import { useChangeRequestOpen } from 'hooks/api/getters/useChangeRequestOpen/useChangeRequestOpen'; | ||||||
| import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; | import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; | ||||||
| import { ChangeRequestStatusBadge } from '../ChangeRequestStatusBadge/ChangeRequestStatusBadge'; | import { ChangeRequestStatusBadge } from '../ChangeRequestStatusBadge/ChangeRequestStatusBadge'; | ||||||
|  | import CloseIcon from '@mui/icons-material/Close'; | ||||||
| 
 | 
 | ||||||
| interface IChangeRequestSidebarProps { | interface IChangeRequestSidebarProps { | ||||||
|     open: boolean; |     open: boolean; | ||||||
| @ -112,8 +114,19 @@ export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({ | |||||||
|                 header={ |                 header={ | ||||||
|                     <PageHeader |                     <PageHeader | ||||||
|                         secondary |                         secondary | ||||||
|  |                         actions={ | ||||||
|  |                             <IconButton onClick={onClose}> | ||||||
|  |                                 <CloseIcon /> | ||||||
|  |                             </IconButton> | ||||||
|  |                         } | ||||||
|                         titleElement={ |                         titleElement={ | ||||||
|                             <> |                             <> | ||||||
|  |                                 <Box | ||||||
|  |                                     sx={{ | ||||||
|  |                                         display: 'flex', | ||||||
|  |                                         alignItems: 'center', | ||||||
|  |                                     }} | ||||||
|  |                                 > | ||||||
|                                     Review your changes |                                     Review your changes | ||||||
|                                     <Tooltip |                                     <Tooltip | ||||||
|                                         title="You can review your changes from this page. |                                         title="You can review your changes from this page. | ||||||
| @ -122,6 +135,7 @@ export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({ | |||||||
|                                     > |                                     > | ||||||
|                                         <StyledHelpOutline /> |                                         <StyledHelpOutline /> | ||||||
|                                     </Tooltip> |                                     </Tooltip> | ||||||
|  |                                 </Box> | ||||||
|                                 <StyledHeaderHint> |                                 <StyledHeaderHint> | ||||||
|                                     Make sure you are sending the right changes |                                     Make sure you are sending the right changes | ||||||
|                                     to be reviewed |                                     to be reviewed | ||||||
| @ -228,7 +242,6 @@ export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({ | |||||||
|                         </Box> |                         </Box> | ||||||
|                     </Box> |                     </Box> | ||||||
|                 ))} |                 ))} | ||||||
|                 <BackButton onClick={onClose}>Close</BackButton> |  | ||||||
|             </StyledPageContent> |             </StyledPageContent> | ||||||
|         </SidebarModal> |         </SidebarModal> | ||||||
|     ); |     ); | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ export interface IChangeRequestFeature { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface IChangeRequestBase { | export interface IChangeRequestBase { | ||||||
|     id?: number; |     id: number; | ||||||
|     action: ChangeRequestAction; |     action: ChangeRequestAction; | ||||||
|     payload: ChangeRequestPayload; |     payload: ChangeRequestPayload; | ||||||
|     conflict?: string; |     conflict?: string; | ||||||
| @ -83,3 +83,6 @@ export type ChangeRequestAction = | |||||||
|     | 'addStrategy' |     | 'addStrategy' | ||||||
|     | 'updateStrategy' |     | 'updateStrategy' | ||||||
|     | 'deleteStrategy'; |     | 'deleteStrategy'; | ||||||
|  | 
 | ||||||
|  | export const hasNameField = (payload: unknown): payload is { name: string } => | ||||||
|  |     typeof payload === 'object' && payload !== null && 'name' in payload; | ||||||
|  | |||||||
| @ -15,6 +15,7 @@ import PermissionIconButton from 'component/common/PermissionIconButton/Permissi | |||||||
| import { Delete } from '@mui/icons-material'; | import { Delete } from '@mui/icons-material'; | ||||||
| import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; | import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; | ||||||
| import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; | import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; | ||||||
|  | import { useChangeRequestOpen } from 'hooks/api/getters/useChangeRequestOpen/useChangeRequestOpen'; | ||||||
| 
 | 
 | ||||||
| interface IFeatureStrategyRemoveProps { | interface IFeatureStrategyRemoveProps { | ||||||
|     projectId: string; |     projectId: string; | ||||||
| @ -130,6 +131,7 @@ const useOnSuggestRemove = ({ | |||||||
|     strategyId, |     strategyId, | ||||||
| }: IRemoveProps) => { | }: IRemoveProps) => { | ||||||
|     const { addChangeRequest } = useChangeRequestApi(); |     const { addChangeRequest } = useChangeRequestApi(); | ||||||
|  |     const { refetch: refetchChangeRequests } = useChangeRequestOpen(projectId); | ||||||
|     const { setToastData, setToastApiError } = useToast(); |     const { setToastData, setToastApiError } = useToast(); | ||||||
|     const onSuggestRemove = async (event: React.FormEvent) => { |     const onSuggestRemove = async (event: React.FormEvent) => { | ||||||
|         try { |         try { | ||||||
| @ -145,6 +147,7 @@ const useOnSuggestRemove = ({ | |||||||
|                 title: 'Changes added to the draft!', |                 title: 'Changes added to the draft!', | ||||||
|                 type: 'success', |                 type: 'success', | ||||||
|             }); |             }); | ||||||
|  |             await refetchChangeRequests(); | ||||||
|         } catch (error: unknown) { |         } catch (error: unknown) { | ||||||
|             setToastApiError(formatUnknownError(error)); |             setToastApiError(formatUnknownError(error)); | ||||||
|         } |         } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user