mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: show strategies used by segments (#5407)
This PR displays change request usage of segments when such usage is returned from the API. It expects at least #5406 to have been merged before it can be merged. 
This commit is contained in:
		
							parent
							
								
									20bfa73b82
								
							
						
					
					
						commit
						b021e7cf85
					
				| @ -18,9 +18,10 @@ export const SegmentDelete = ({ | ||||
|     onClose, | ||||
|     onRemove, | ||||
| }: ISegmentDeleteProps) => { | ||||
|     const { strategies, loading } = useStrategiesBySegment(segment.id); | ||||
|     const canDeleteSegment = strategies?.length === 0; | ||||
| 
 | ||||
|     const { strategies, changeRequestStrategies, loading } = | ||||
|         useStrategiesBySegment(segment.id); | ||||
|     const canDeleteSegment = | ||||
|         strategies?.length === 0 && changeRequestStrategies?.length === 0; | ||||
|     if (loading) { | ||||
|         return null; | ||||
|     } | ||||
| @ -42,6 +43,7 @@ export const SegmentDelete = ({ | ||||
|                     open={open} | ||||
|                     onClose={onClose} | ||||
|                     strategies={strategies} | ||||
|                     changeRequestStrategies={changeRequestStrategies} | ||||
|                 /> | ||||
|             } | ||||
|         /> | ||||
|  | ||||
| @ -0,0 +1,63 @@ | ||||
| import React from 'react'; | ||||
| import { render } from 'utils/testRenderer'; | ||||
| import { screen } from '@testing-library/react'; | ||||
| import { SegmentDeleteUsedSegment } from './SegmentDeleteUsedSegment'; | ||||
| 
 | ||||
| describe('SegmentDeleteUsedSegment', () => { | ||||
|     it('should link to change requests for change request strategies', async () => { | ||||
|         const projectId = 'project1'; | ||||
| 
 | ||||
|         const crStrategies = [ | ||||
|             { | ||||
|                 projectId, | ||||
|                 featureName: 'feature1', | ||||
|                 strategyName: 'flexible rollout', | ||||
|                 environment: 'default', | ||||
|                 changeRequest: { id: 1, title: null }, | ||||
|             }, | ||||
|             { | ||||
|                 projectId, | ||||
|                 featureName: 'feature1', | ||||
|                 strategyName: 'flexible rollout', | ||||
|                 environment: 'default', | ||||
|                 changeRequest: { id: 2, title: 'My cool CR' }, | ||||
|             }, | ||||
|         ]; | ||||
| 
 | ||||
|         render( | ||||
|             <SegmentDeleteUsedSegment | ||||
|                 changeRequestStrategies={crStrategies} | ||||
|                 segment={{ | ||||
|                     id: 1, | ||||
|                     name: 'segment', | ||||
|                     description: 'description', | ||||
|                     project: projectId, | ||||
|                     constraints: [], | ||||
|                     createdAt: '', | ||||
|                     createdBy: '', | ||||
|                 }} | ||||
|                 open={true} | ||||
|                 strategies={[]} | ||||
|                 onClose={() => {}} | ||||
|             />, | ||||
|         ); | ||||
| 
 | ||||
|         const links = await screen.findAllByRole('link'); | ||||
|         expect(links).toHaveLength(crStrategies.length); | ||||
| 
 | ||||
|         const [link1, link2] = links; | ||||
| 
 | ||||
|         expect(link1).toHaveTextContent('#1'); | ||||
|         expect(link1).toHaveAccessibleDescription('Change request 1'); | ||||
|         expect(link1).toHaveAttribute( | ||||
|             'href', | ||||
|             `/projects/${projectId}/change-requests/1`, | ||||
|         ); | ||||
|         expect(link2).toHaveTextContent('#2 (My cool CR)'); | ||||
|         expect(link2).toHaveAccessibleDescription('Change request 2'); | ||||
|         expect(link2).toHaveAttribute( | ||||
|             'href', | ||||
|             `/projects/${projectId}/change-requests/2`, | ||||
|         ); | ||||
|     }); | ||||
| }); | ||||
| @ -5,6 +5,12 @@ import { Link } from 'react-router-dom'; | ||||
| import { formatEditStrategyPath } from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit'; | ||||
| import { formatStrategyName } from 'utils/strategyNames'; | ||||
| import { styled } from '@mui/material'; | ||||
| import { | ||||
|     ChangeRequestNewStrategy, | ||||
|     ChangeRequestStrategy, | ||||
|     ChangeRequestUpdatedStrategy, | ||||
| } from 'hooks/api/getters/useStrategiesBySegment/useStrategiesBySegment'; | ||||
| import { sortStrategiesByFeature } from './sort-strategies'; | ||||
| 
 | ||||
| const StyledUl = styled('ul')({ | ||||
|     marginBottom: 0, | ||||
| @ -21,14 +27,29 @@ interface ISegmentDeleteUsedSegmentProps { | ||||
|     open: boolean; | ||||
|     onClose: () => void; | ||||
|     strategies: IFeatureStrategy[] | undefined; | ||||
|     changeRequestStrategies: ChangeRequestStrategy[] | undefined; | ||||
| } | ||||
| 
 | ||||
| export const formatChangeRequestPath = ( | ||||
|     projectId: string, | ||||
|     changeRequestId: number, | ||||
| ): string => { | ||||
|     return `/projects/${projectId}/change-requests/${changeRequestId}`; | ||||
| }; | ||||
| 
 | ||||
| export const SegmentDeleteUsedSegment = ({ | ||||
|     segment, | ||||
|     open, | ||||
|     onClose, | ||||
|     strategies, | ||||
|     changeRequestStrategies, | ||||
| }: ISegmentDeleteUsedSegmentProps) => { | ||||
|     const sortedStrategies = sortStrategiesByFeature< | ||||
|         IFeatureStrategy, | ||||
|         ChangeRequestUpdatedStrategy, | ||||
|         ChangeRequestNewStrategy | ||||
|     >(strategies ?? [], changeRequestStrategies ?? []); | ||||
| 
 | ||||
|     return ( | ||||
|         <Dialogue | ||||
|             title="You can't delete a segment that's currently in use" | ||||
| @ -41,32 +62,74 @@ export const SegmentDeleteUsedSegment = ({ | ||||
|                 <strong>{segment.name}</strong> segment for their strategies: | ||||
|             </p> | ||||
|             <StyledUl> | ||||
|                 {strategies?.map((strategy) => ( | ||||
|                     <li key={strategy.id}> | ||||
|                         <StyledLink | ||||
|                             to={formatEditStrategyPath( | ||||
|                                 strategy.projectId!, | ||||
|                                 strategy.featureName!, | ||||
|                                 strategy.environment!, | ||||
|                                 strategy.id, | ||||
|                             )} | ||||
|                             target='_blank' | ||||
|                             rel='noopener noreferrer' | ||||
|                         > | ||||
|                             {strategy.featureName!}{' '} | ||||
|                             {formatStrategyNameParens(strategy)} | ||||
|                         </StyledLink> | ||||
|                     </li> | ||||
|                 ))} | ||||
|                 {sortedStrategies.map((strategy, index) => | ||||
|                     strategyListItem(strategy, index), | ||||
|                 )} | ||||
|             </StyledUl> | ||||
|         </Dialogue> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const formatStrategyNameParens = (strategy: IFeatureStrategy): string => { | ||||
| const formatStrategyNameParens = (strategy: { | ||||
|     strategyName?: string; | ||||
| }): string => { | ||||
|     if (!strategy.strategyName) { | ||||
|         return ''; | ||||
|     } | ||||
| 
 | ||||
|     return `(${formatStrategyName(strategy.strategyName)})`; | ||||
| }; | ||||
| 
 | ||||
| const strategyListItem = ( | ||||
|     strategy: | ||||
|         | IFeatureStrategy | ||||
|         | ChangeRequestUpdatedStrategy | ||||
|         | ChangeRequestNewStrategy, | ||||
|     index: number, | ||||
| ) => { | ||||
|     const isChangeRequest = ( | ||||
|         strategy: IFeatureStrategy | ChangeRequestStrategy, | ||||
|     ): strategy is ChangeRequestStrategy => 'changeRequest' in strategy; | ||||
| 
 | ||||
|     if (isChangeRequest(strategy)) { | ||||
|         const { id, title } = strategy.changeRequest; | ||||
| 
 | ||||
|         const text = title ? `#${id} (${title})` : `#${id}`; | ||||
|         return ( | ||||
|             <li key={`#${strategy.changeRequest.id}@${index}`}> | ||||
|                 <p> | ||||
|                     {strategy.featureName}{' '} | ||||
|                     {`${formatStrategyNameParens( | ||||
|                         strategy, | ||||
|                     )} — in change request `}
 | ||||
| 
 | ||||
|                     <StyledLink | ||||
|                         to={formatChangeRequestPath(strategy.projectId, id)} | ||||
|                         target='_blank' | ||||
|                         rel='noopener noreferrer' | ||||
|                         title={`Change request ${id}`} | ||||
|                     > | ||||
|                         {text} | ||||
|                     </StyledLink> | ||||
|                 </p> | ||||
|             </li> | ||||
|         ); | ||||
|     } else { | ||||
|         return ( | ||||
|             <li key={strategy.id}> | ||||
|                 <StyledLink | ||||
|                     to={formatEditStrategyPath( | ||||
|                         strategy.projectId!, | ||||
|                         strategy.featureName!, | ||||
|                         strategy.environment!, | ||||
|                         strategy.id, | ||||
|                     )} | ||||
|                     target='_blank' | ||||
|                     rel='noopener noreferrer' | ||||
|                 > | ||||
|                     {strategy.featureName!} {formatStrategyNameParens(strategy)} | ||||
|                 </StyledLink> | ||||
|             </li> | ||||
|         ); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| @ -5,8 +5,26 @@ import handleErrorResponses from '../httpErrorResponseHandler'; | ||||
| import { IFeatureStrategy } from 'interfaces/strategy'; | ||||
| import { useConditionalSWR } from '../useConditionalSWR/useConditionalSWR'; | ||||
| 
 | ||||
| export type ChangeRequestInfo = { id: number; title: string | null }; | ||||
| export type ChangeRequestNewStrategy = { | ||||
|     projectId: string; | ||||
|     featureName: string; | ||||
|     strategyName: string; | ||||
|     environment: string; | ||||
|     changeRequest: ChangeRequestInfo; | ||||
| }; | ||||
| 
 | ||||
| export type ChangeRequestUpdatedStrategy = ChangeRequestNewStrategy & { | ||||
|     id: string; | ||||
| }; | ||||
| 
 | ||||
| export type ChangeRequestStrategy = | ||||
|     | ChangeRequestNewStrategy | ||||
|     | ChangeRequestUpdatedStrategy; | ||||
| 
 | ||||
| export interface IUseStrategiesBySegmentOutput { | ||||
|     strategies: IFeatureStrategy[]; | ||||
|     changeRequestStrategies: ChangeRequestStrategy[]; | ||||
|     refetchUsedSegments: () => void; | ||||
|     loading: boolean; | ||||
|     error?: Error; | ||||
| @ -26,6 +44,7 @@ export const useStrategiesBySegment = ( | ||||
| 
 | ||||
|     return { | ||||
|         strategies: data?.strategies || [], | ||||
|         changeRequestStrategies: data?.changeRequestStrategies || [], | ||||
|         refetchUsedSegments, | ||||
|         loading: !error && !data, | ||||
|         error, | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user