mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	hide discard for nonauthors (#2634)
This commit is contained in:
		
							parent
							
								
									1be2483e6a
								
							
						
					
					
						commit
						2107834768
					
				| @ -1,30 +1,9 @@ | ||||
| import React, { FC, VFC } from 'react'; | ||||
| import { Alert, Box, styled } from '@mui/material'; | ||||
| import { ChangeRequestFeatureToggleChange } from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/ChangeRequestFeatureToggleChange'; | ||||
| import { objectId } from 'utils/objectId'; | ||||
| import { ToggleStatusChange } from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/ToggleStatusChange'; | ||||
| import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; | ||||
| import { formatUnknownError } from 'utils/formatUnknownError'; | ||||
| import useToast from 'hooks/useToast'; | ||||
| import type { | ||||
|     IChange, | ||||
|     IChangeRequest, | ||||
|     IChangeRequestFeature, | ||||
| } from '../changeRequest.types'; | ||||
| import { hasNameField } from '../changeRequest.types'; | ||||
| import { | ||||
|     Discard, | ||||
|     StrategyAddedChange, | ||||
|     StrategyDeletedChange, | ||||
|     StrategyEditedChange, | ||||
| } from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/StrategyChange'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; | ||||
| import { StrategyExecution } from '../../feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution'; | ||||
| import { | ||||
|     CodeSnippetPopover, | ||||
|     PopoverDiff, | ||||
| } from './CodeSnippetPopover/CodeSnippetPopover'; | ||||
| import React, { VFC } from 'react'; | ||||
| import { Box } from '@mui/material'; | ||||
| import type { IChangeRequest } from '../changeRequest.types'; | ||||
| import { FeatureToggleChanges } from './Changes/FeatureToggleChanges'; | ||||
| import { Change } from './Changes/Change/Change'; | ||||
| import { DiscardContainer } from './Changes/Change/Discard'; | ||||
| 
 | ||||
| interface IChangeRequestProps { | ||||
|     changeRequest: IChangeRequest; | ||||
| @ -32,190 +11,15 @@ interface IChangeRequestProps { | ||||
|     onNavigate?: () => void; | ||||
| } | ||||
| 
 | ||||
| const StyledSingleChangeBox = styled(Box, { | ||||
|     shouldForwardProp: (prop: string) => !prop.startsWith('$'), | ||||
| })<{ | ||||
|     $hasConflict: boolean; | ||||
|     $isAfterWarning: boolean; | ||||
|     $isLast: boolean; | ||||
|     $isInConflictFeature: boolean; | ||||
| }>( | ||||
|     ({ | ||||
|         theme, | ||||
|         $hasConflict, | ||||
|         $isInConflictFeature, | ||||
|         $isAfterWarning, | ||||
|         $isLast, | ||||
|     }) => ({ | ||||
|         borderLeft: '1px solid', | ||||
|         borderRight: '1px solid', | ||||
|         borderTop: '1px solid', | ||||
|         borderBottom: $isLast ? '1px solid' : 'none', | ||||
|         borderRadius: $isLast | ||||
|             ? `0 0
 | ||||
|                 ${theme.shape.borderRadiusLarge}px ${theme.shape.borderRadiusLarge}px` | ||||
|             : 0, | ||||
|         borderColor: | ||||
|             $hasConflict || $isInConflictFeature | ||||
|                 ? theme.palette.warning.border | ||||
|                 : theme.palette.dividerAlternative, | ||||
|         borderTopColor: | ||||
|             ($hasConflict || $isAfterWarning) && !$isInConflictFeature | ||||
|                 ? theme.palette.warning.border | ||||
|                 : theme.palette.dividerAlternative, | ||||
|     }) | ||||
| ); | ||||
| 
 | ||||
| const StyledAlert = styled(Alert)(({ theme }) => ({ | ||||
|     borderRadius: 0, | ||||
|     padding: theme.spacing(0, 2), | ||||
|     '&.MuiAlert-standardWarning': { | ||||
|         borderStyle: 'none none solid none', | ||||
|     }, | ||||
| })); | ||||
| 
 | ||||
| const Change: FC<{ | ||||
|     onDiscard: () => Promise<void>; | ||||
|     index: number; | ||||
|     changeRequest: IChangeRequest; | ||||
|     change: IChange; | ||||
|     feature: IChangeRequestFeature; | ||||
| }> = ({ index, change, feature, changeRequest, onDiscard }) => { | ||||
|     const { isChangeRequestConfigured } = useChangeRequestsEnabled( | ||||
|         changeRequest.project | ||||
|     ); | ||||
|     const allowChangeRequestActions = isChangeRequestConfigured( | ||||
|         changeRequest.environment | ||||
|     ); | ||||
| 
 | ||||
|     const showDiscard = | ||||
|         allowChangeRequestActions && | ||||
|         !['Cancelled', 'Applied'].includes(changeRequest.state) && | ||||
|         changeRequest.features.flatMap(feature => feature.changes).length > 1; | ||||
| 
 | ||||
|     return ( | ||||
|         <StyledSingleChangeBox | ||||
|             key={objectId(change)} | ||||
|             $hasConflict={Boolean(change.conflict)} | ||||
|             $isInConflictFeature={Boolean(feature.conflict)} | ||||
|             $isAfterWarning={Boolean(feature.changes[index - 1]?.conflict)} | ||||
|             $isLast={index + 1 === feature.changes.length} | ||||
|         > | ||||
|             <ConditionallyRender | ||||
|                 condition={Boolean(change.conflict) && !feature.conflict} | ||||
|                 show={ | ||||
|                     <StyledAlert severity="warning"> | ||||
|                         <strong>Conflict!</strong> This change can’t be applied.{' '} | ||||
|                         {change.conflict}. | ||||
|                     </StyledAlert> | ||||
|                 } | ||||
|             /> | ||||
|             <Box sx={{ p: 2 }}> | ||||
|                 {change.action === 'updateEnabled' && ( | ||||
|                     <ToggleStatusChange | ||||
|                         enabled={change.payload.enabled} | ||||
|                         discard={ | ||||
|                             <ConditionallyRender | ||||
|                                 condition={showDiscard} | ||||
|                                 show={<Discard onDiscard={onDiscard} />} | ||||
|                             /> | ||||
|                         } | ||||
|                     /> | ||||
|                 )} | ||||
|                 {change.action === 'addStrategy' && ( | ||||
|                     <> | ||||
|                         <StrategyAddedChange | ||||
|                             discard={ | ||||
|                                 <ConditionallyRender | ||||
|                                     condition={showDiscard} | ||||
|                                     show={<Discard onDiscard={onDiscard} />} | ||||
|                                 /> | ||||
|                             } | ||||
|                         > | ||||
|                             <CodeSnippetPopover change={change}> | ||||
|                                 <PopoverDiff | ||||
|                                     change={change} | ||||
|                                     feature={feature.name} | ||||
|                                     environmentName={changeRequest.environment} | ||||
|                                     project={changeRequest.project} | ||||
|                                 /> | ||||
|                             </CodeSnippetPopover> | ||||
|                         </StrategyAddedChange> | ||||
|                         <StrategyExecution strategy={change.payload} /> | ||||
|                     </> | ||||
|                 )} | ||||
|                 {change.action === 'deleteStrategy' && ( | ||||
|                     <StrategyDeletedChange | ||||
|                         discard={ | ||||
|                             <ConditionallyRender | ||||
|                                 condition={showDiscard} | ||||
|                                 show={<Discard onDiscard={onDiscard} />} | ||||
|                             /> | ||||
|                         } | ||||
|                     > | ||||
|                         {hasNameField(change.payload) && ( | ||||
|                             <CodeSnippetPopover change={change}> | ||||
|                                 <PopoverDiff | ||||
|                                     change={change} | ||||
|                                     feature={feature.name} | ||||
|                                     environmentName={changeRequest.environment} | ||||
|                                     project={changeRequest.project} | ||||
|                                 /> | ||||
|                             </CodeSnippetPopover> | ||||
|                         )} | ||||
|                     </StrategyDeletedChange> | ||||
|                 )} | ||||
|                 {change.action === 'updateStrategy' && ( | ||||
|                     <> | ||||
|                         <StrategyEditedChange | ||||
|                             discard={ | ||||
|                                 <ConditionallyRender | ||||
|                                     condition={showDiscard} | ||||
|                                     show={<Discard onDiscard={onDiscard} />} | ||||
|                                 /> | ||||
|                             } | ||||
|                         > | ||||
|                             <CodeSnippetPopover change={change}> | ||||
|                                 <PopoverDiff | ||||
|                                     change={change} | ||||
|                                     feature={feature.name} | ||||
|                                     environmentName={changeRequest.environment} | ||||
|                                     project={changeRequest.project} | ||||
|                                 /> | ||||
|                             </CodeSnippetPopover> | ||||
|                         </StrategyEditedChange> | ||||
|                         <StrategyExecution strategy={change.payload} /> | ||||
|                     </> | ||||
|                 )} | ||||
|             </Box> | ||||
|         </StyledSingleChangeBox> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export const ChangeRequest: VFC<IChangeRequestProps> = ({ | ||||
|     changeRequest, | ||||
|     onRefetch, | ||||
|     onNavigate, | ||||
| }) => { | ||||
|     const { discardChange } = useChangeRequestApi(); | ||||
|     const { setToastData, setToastApiError } = useToast(); | ||||
|     const onDiscard = (id: number) => async () => { | ||||
|         try { | ||||
|             await discardChange(changeRequest.project, changeRequest.id, id); | ||||
|             setToastData({ | ||||
|                 title: 'Change discarded from change request draft.', | ||||
|                 type: 'success', | ||||
|             }); | ||||
|             onRefetch?.(); | ||||
|         } catch (error: unknown) { | ||||
|             setToastApiError(formatUnknownError(error)); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <Box> | ||||
|             {changeRequest.features?.map(feature => ( | ||||
|                 <ChangeRequestFeatureToggleChange | ||||
|                 <FeatureToggleChanges | ||||
|                     key={feature.name} | ||||
|                     featureName={feature.name} | ||||
|                     projectId={changeRequest.project} | ||||
| @ -225,14 +29,20 @@ export const ChangeRequest: VFC<IChangeRequestProps> = ({ | ||||
|                     {feature.changes.map((change, index) => ( | ||||
|                         <Change | ||||
|                             key={index} | ||||
|                             onDiscard={onDiscard(change.id)} | ||||
|                             discard={ | ||||
|                                 <DiscardContainer | ||||
|                                     changeRequest={changeRequest} | ||||
|                                     changeId={change.id} | ||||
|                                     onPostDiscard={onRefetch} | ||||
|                                 /> | ||||
|                             } | ||||
|                             index={index} | ||||
|                             changeRequest={changeRequest} | ||||
|                             change={change} | ||||
|                             feature={feature} | ||||
|                         /> | ||||
|                     ))} | ||||
|                 </ChangeRequestFeatureToggleChange> | ||||
|                 </FeatureToggleChanges> | ||||
|             ))} | ||||
|         </Box> | ||||
|     ); | ||||
|  | ||||
| @ -0,0 +1,144 @@ | ||||
| import React, { FC, ReactNode } from 'react'; | ||||
| import { | ||||
|     hasNameField, | ||||
|     IChange, | ||||
|     IChangeRequest, | ||||
|     IChangeRequestFeature, | ||||
| } from '../../../changeRequest.types'; | ||||
| import { objectId } from 'utils/objectId'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { Alert, Box, styled } from '@mui/material'; | ||||
| 
 | ||||
| import { | ||||
|     CodeSnippetPopover, | ||||
|     PopoverDiff, | ||||
| } from '../../CodeSnippetPopover/CodeSnippetPopover'; | ||||
| import { StrategyExecution } from '../../../../feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution'; | ||||
| import { ToggleStatusChange } from './ToggleStatusChange'; | ||||
| import { | ||||
|     StrategyAddedChange, | ||||
|     StrategyDeletedChange, | ||||
|     StrategyEditedChange, | ||||
| } from './StrategyChange'; | ||||
| 
 | ||||
| const StyledSingleChangeBox = styled(Box, { | ||||
|     shouldForwardProp: (prop: string) => !prop.startsWith('$'), | ||||
| })<{ | ||||
|     $hasConflict: boolean; | ||||
|     $isAfterWarning: boolean; | ||||
|     $isLast: boolean; | ||||
|     $isInConflictFeature: boolean; | ||||
| }>( | ||||
|     ({ | ||||
|         theme, | ||||
|         $hasConflict, | ||||
|         $isInConflictFeature, | ||||
|         $isAfterWarning, | ||||
|         $isLast, | ||||
|     }) => ({ | ||||
|         borderLeft: '1px solid', | ||||
|         borderRight: '1px solid', | ||||
|         borderTop: '1px solid', | ||||
|         borderBottom: $isLast ? '1px solid' : 'none', | ||||
|         borderRadius: $isLast | ||||
|             ? `0 0
 | ||||
|                 ${theme.shape.borderRadiusLarge}px ${theme.shape.borderRadiusLarge}px` | ||||
|             : 0, | ||||
|         borderColor: | ||||
|             $hasConflict || $isInConflictFeature | ||||
|                 ? theme.palette.warning.border | ||||
|                 : theme.palette.dividerAlternative, | ||||
|         borderTopColor: | ||||
|             ($hasConflict || $isAfterWarning) && !$isInConflictFeature | ||||
|                 ? theme.palette.warning.border | ||||
|                 : theme.palette.dividerAlternative, | ||||
|     }) | ||||
| ); | ||||
| 
 | ||||
| const StyledAlert = styled(Alert)(({ theme }) => ({ | ||||
|     borderRadius: 0, | ||||
|     padding: theme.spacing(0, 2), | ||||
|     '&.MuiAlert-standardWarning': { | ||||
|         borderStyle: 'none none solid none', | ||||
|     }, | ||||
| })); | ||||
| 
 | ||||
| export const Change: FC<{ | ||||
|     discard: ReactNode; | ||||
|     index: number; | ||||
|     changeRequest: IChangeRequest; | ||||
|     change: IChange; | ||||
|     feature: IChangeRequestFeature; | ||||
| }> = ({ index, change, feature, changeRequest, discard }) => { | ||||
|     return ( | ||||
|         <StyledSingleChangeBox | ||||
|             key={objectId(change)} | ||||
|             $hasConflict={Boolean(change.conflict)} | ||||
|             $isInConflictFeature={Boolean(feature.conflict)} | ||||
|             $isAfterWarning={Boolean(feature.changes[index - 1]?.conflict)} | ||||
|             $isLast={index + 1 === feature.changes.length} | ||||
|         > | ||||
|             <ConditionallyRender | ||||
|                 condition={Boolean(change.conflict) && !feature.conflict} | ||||
|                 show={ | ||||
|                     <StyledAlert severity="warning"> | ||||
|                         <strong>Conflict!</strong> This change can’t be applied.{' '} | ||||
|                         {change.conflict}. | ||||
|                     </StyledAlert> | ||||
|                 } | ||||
|             /> | ||||
|             <Box sx={{ p: 2 }}> | ||||
|                 {change.action === 'updateEnabled' && ( | ||||
|                     <ToggleStatusChange | ||||
|                         enabled={change.payload.enabled} | ||||
|                         discard={discard} | ||||
|                     /> | ||||
|                 )} | ||||
|                 {change.action === 'addStrategy' && ( | ||||
|                     <> | ||||
|                         <StrategyAddedChange discard={discard}> | ||||
|                             <CodeSnippetPopover change={change}> | ||||
|                                 <PopoverDiff | ||||
|                                     change={change} | ||||
|                                     feature={feature.name} | ||||
|                                     environmentName={changeRequest.environment} | ||||
|                                     project={changeRequest.project} | ||||
|                                 /> | ||||
|                             </CodeSnippetPopover> | ||||
|                         </StrategyAddedChange> | ||||
|                         <StrategyExecution strategy={change.payload} /> | ||||
|                     </> | ||||
|                 )} | ||||
|                 {change.action === 'deleteStrategy' && ( | ||||
|                     <StrategyDeletedChange discard={discard}> | ||||
|                         {hasNameField(change.payload) && ( | ||||
|                             <CodeSnippetPopover change={change}> | ||||
|                                 <PopoverDiff | ||||
|                                     change={change} | ||||
|                                     feature={feature.name} | ||||
|                                     environmentName={changeRequest.environment} | ||||
|                                     project={changeRequest.project} | ||||
|                                 /> | ||||
|                             </CodeSnippetPopover> | ||||
|                         )} | ||||
|                     </StrategyDeletedChange> | ||||
|                 )} | ||||
|                 {change.action === 'updateStrategy' && ( | ||||
|                     <> | ||||
|                         <StrategyEditedChange discard={discard}> | ||||
|                             <CodeSnippetPopover change={change}> | ||||
|                                 <PopoverDiff | ||||
|                                     change={change} | ||||
|                                     feature={feature.name} | ||||
|                                     environmentName={changeRequest.environment} | ||||
|                                     project={changeRequest.project} | ||||
|                                 /> | ||||
|                             </CodeSnippetPopover> | ||||
|                         </StrategyEditedChange> | ||||
|                         <StrategyExecution strategy={change.payload} /> | ||||
|                     </> | ||||
|                 )} | ||||
|             </Box> | ||||
|         </StyledSingleChangeBox> | ||||
|     ); | ||||
| }; | ||||
| @ -0,0 +1,74 @@ | ||||
| import React, { FC } from 'react'; | ||||
| import { IChangeRequest } from '../../../changeRequest.types'; | ||||
| import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; | ||||
| import useToast from 'hooks/useToast'; | ||||
| import { formatUnknownError } from 'utils/formatUnknownError'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; | ||||
| import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser'; | ||||
| import { changesCount } from '../../../changesCount'; | ||||
| import { Box, Link, styled } from '@mui/material'; | ||||
| 
 | ||||
| const useShowDiscard = (changeRequest: IChangeRequest) => { | ||||
|     const { isChangeRequestConfigured } = useChangeRequestsEnabled( | ||||
|         changeRequest.project | ||||
|     ); | ||||
|     const allowChangeRequestActions = isChangeRequestConfigured( | ||||
|         changeRequest.environment | ||||
|     ); | ||||
|     const isPending = !['Cancelled', 'Applied'].includes(changeRequest.state); | ||||
| 
 | ||||
|     const { user } = useAuthUser(); | ||||
|     const isAuthor = user?.id === changeRequest.createdBy.id; | ||||
| 
 | ||||
|     const showDiscard = | ||||
|         allowChangeRequestActions && | ||||
|         isPending && | ||||
|         isAuthor && | ||||
|         changesCount(changeRequest) > 1; | ||||
| 
 | ||||
|     return showDiscard; | ||||
| }; | ||||
| 
 | ||||
| const StyledLink = styled(Link)(() => ({ | ||||
|     textDecoration: 'none', | ||||
|     '&:hover, &:focus': { | ||||
|         textDecoration: 'underline', | ||||
|     }, | ||||
| })); | ||||
| 
 | ||||
| const Discard: FC<{ onDiscard: () => void }> = ({ onDiscard }) => ( | ||||
|     <Box> | ||||
|         <StyledLink onClick={onDiscard}>Discard</StyledLink> | ||||
|     </Box> | ||||
| ); | ||||
| 
 | ||||
| export const DiscardContainer: FC<{ | ||||
|     changeRequest: IChangeRequest; | ||||
|     changeId: number; | ||||
|     onPostDiscard?: () => void; | ||||
| }> = ({ changeRequest, changeId, onPostDiscard }) => { | ||||
|     const showDiscard = useShowDiscard(changeRequest); | ||||
|     const { discardChange } = useChangeRequestApi(); | ||||
|     const { setToastData, setToastApiError } = useToast(); | ||||
| 
 | ||||
|     const onDiscard = (id: number) => async () => { | ||||
|         try { | ||||
|             await discardChange(changeRequest.project, changeRequest.id, id); | ||||
|             setToastData({ | ||||
|                 title: 'Change discarded from change request draft.', | ||||
|                 type: 'success', | ||||
|             }); | ||||
|             onPostDiscard?.(); | ||||
|         } catch (error: unknown) { | ||||
|             setToastApiError(formatUnknownError(error)); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <ConditionallyRender | ||||
|             condition={showDiscard} | ||||
|             show={<Discard onDiscard={onDiscard(changeId)} />} | ||||
|         /> | ||||
|     ); | ||||
| }; | ||||
| @ -1,10 +1,6 @@ | ||||
| import { Box, Link, styled, Typography } from '@mui/material'; | ||||
| import { FC, ReactNode } from 'react'; | ||||
| 
 | ||||
| interface IStrategyChangeProps { | ||||
|     onDiscard: () => void; | ||||
| } | ||||
| 
 | ||||
| export const ChangeItemWrapper = styled(Box)(({ theme }) => ({ | ||||
|     display: 'flex', | ||||
|     justifyContent: 'space-between', | ||||
| @ -16,19 +12,6 @@ const ChangeItemInfo: FC = styled(Box)(({ theme }) => ({ | ||||
|     gap: theme.spacing(1), | ||||
| })); | ||||
| 
 | ||||
| const StyledLink = styled(Link)(() => ({ | ||||
|     textDecoration: 'none', | ||||
|     '&:hover, &:focus': { | ||||
|         textDecoration: 'underline', | ||||
|     }, | ||||
| })); | ||||
| 
 | ||||
| export const Discard: FC<IStrategyChangeProps> = ({ onDiscard }) => ( | ||||
|     <Box> | ||||
|         <StyledLink onClick={onDiscard}>Discard</StyledLink> | ||||
|     </Box> | ||||
| ); | ||||
| 
 | ||||
| export const StrategyAddedChange: FC<{ discard?: ReactNode }> = ({ | ||||
|     children, | ||||
|     discard, | ||||
| @ -3,16 +3,20 @@ import { Link } from 'react-router-dom'; | ||||
| import { Alert, Box, Card, Typography } from '@mui/material'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| 
 | ||||
| interface IChangeRequestToggleChange { | ||||
| interface IFeatureToggleChanges { | ||||
|     featureName: string; | ||||
|     projectId: string; | ||||
|     conflict?: string; | ||||
|     onNavigate?: () => void; | ||||
| } | ||||
| 
 | ||||
| export const ChangeRequestFeatureToggleChange: FC< | ||||
|     IChangeRequestToggleChange | ||||
| > = ({ featureName, projectId, conflict, onNavigate, children }) => ( | ||||
| export const FeatureToggleChanges: FC<IFeatureToggleChanges> = ({ | ||||
|     featureName, | ||||
|     projectId, | ||||
|     conflict, | ||||
|     onNavigate, | ||||
|     children, | ||||
| }) => ( | ||||
|     <Card | ||||
|         elevation={0} | ||||
|         sx={theme => ({ | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user