mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: search page - improved change request tooltip (#9750)
improved and tested tooltip for change requests and adjusted column width
This commit is contained in:
		
							parent
							
								
									01c1ec5c29
								
							
						
					
					
						commit
						3fd74262bb
					
				| @ -69,13 +69,10 @@ export interface IHtmlTooltipProps extends TooltipProps { | |||||||
|     maxHeight?: SpacingArgument; |     maxHeight?: SpacingArgument; | ||||||
|     fontSize?: string; |     fontSize?: string; | ||||||
|     tabIndex?: number; |     tabIndex?: number; | ||||||
|  |     disableFocusListener?: boolean; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const HtmlTooltip = (props: IHtmlTooltipProps) => { | export const HtmlTooltip = (props: IHtmlTooltipProps) => { | ||||||
|     if (!props.title) return props.children; |     if (!props.title) return props.children; | ||||||
|     return ( |     return <StyledHtmlTooltip {...props}>{props.children}</StyledHtmlTooltip>; | ||||||
|         <StyledHtmlTooltip {...props} disableFocusListener> |  | ||||||
|             {props.children} |  | ||||||
|         </StyledHtmlTooltip> |  | ||||||
|     ); |  | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -54,7 +54,13 @@ export const FeatureLifecycleCell: VFC<IFeatureLifecycleProps> = ({ | |||||||
|         : []; |         : []; | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|         <Box sx={{ display: 'flex', justifyContent: 'center' }}> |         <Box | ||||||
|  |             sx={(theme) => ({ | ||||||
|  |                 display: 'flex', | ||||||
|  |                 justifyContent: 'center', | ||||||
|  |                 padding: theme.spacing(0, expanded ? 1 : 0), | ||||||
|  |             })} | ||||||
|  |         > | ||||||
|             <FeatureLifecycle |             <FeatureLifecycle | ||||||
|                 onArchive={onArchive} |                 onArchive={onArchive} | ||||||
|                 onComplete={onComplete} |                 onComplete={onComplete} | ||||||
|  | |||||||
| @ -197,7 +197,6 @@ export const FeatureToggleListTable: FC = () => { | |||||||
|                               <StatusCell {...original} /> |                               <StatusCell {...original} /> | ||||||
|                           ), |                           ), | ||||||
|                           enableSorting: false, |                           enableSorting: false, | ||||||
|                           size: 80, |  | ||||||
|                       }), |                       }), | ||||||
|                       columnHelper.accessor('project', { |                       columnHelper.accessor('project', { | ||||||
|                           header: 'Project', |                           header: 'Project', | ||||||
| @ -208,10 +207,12 @@ export const FeatureToggleListTable: FC = () => { | |||||||
|                               )?.name; |                               )?.name; | ||||||
| 
 | 
 | ||||||
|                               return ( |                               return ( | ||||||
|                                   <LinkCell |                                   <Box sx={{ minWidth: '180px' }}> | ||||||
|                                       title={projectName || projectId} |                                       <LinkCell | ||||||
|                                       to={`/projects/${projectId}`} |                                           title={projectName || projectId} | ||||||
|                                   /> |                                           to={`/projects/${projectId}`} | ||||||
|  |                                       /> | ||||||
|  |                                   </Box> | ||||||
|                               ); |                               ); | ||||||
|                           }, |                           }, | ||||||
|                       }), |                       }), | ||||||
|  | |||||||
| @ -0,0 +1,84 @@ | |||||||
|  | import { render } from 'utils/testRenderer'; | ||||||
|  | import { StatusCell } from './StatusCell'; | ||||||
|  | import { screen } from '@testing-library/dom'; | ||||||
|  | import userEvent from '@testing-library/user-event'; | ||||||
|  | 
 | ||||||
|  | describe('StatusCell', () => { | ||||||
|  |     it('displays "–" as default status', () => { | ||||||
|  |         const { getByText } = render( | ||||||
|  |             <StatusCell | ||||||
|  |                 project='test-project' | ||||||
|  |                 lifecycle={{ | ||||||
|  |                     stage: 'live', | ||||||
|  |                     enteredStageAt: '2025-04-01T00:00:00Z', | ||||||
|  |                 }} | ||||||
|  |                 environments={[ | ||||||
|  |                     { | ||||||
|  |                         name: 'production', | ||||||
|  |                         type: 'production', | ||||||
|  |                         enabled: true, | ||||||
|  |                         hasStrategies: true, | ||||||
|  |                         hasEnabledStrategies: true, | ||||||
|  |                         changeRequestIds: [], | ||||||
|  |                     }, | ||||||
|  |                 ]} | ||||||
|  |             />, | ||||||
|  |         ); | ||||||
|  |         expect(getByText('–')).toBeInTheDocument(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('shows "change requests" icon', () => { | ||||||
|  |         const { getByTestId } = render( | ||||||
|  |             <StatusCell | ||||||
|  |                 project='test-project' | ||||||
|  |                 lifecycle={{ | ||||||
|  |                     stage: 'live', | ||||||
|  |                     enteredStageAt: '2025-04-01T00:00:00Z', | ||||||
|  |                 }} | ||||||
|  |                 environments={[ | ||||||
|  |                     { | ||||||
|  |                         name: 'production', | ||||||
|  |                         type: 'production', | ||||||
|  |                         enabled: true, | ||||||
|  |                         hasStrategies: true, | ||||||
|  |                         hasEnabledStrategies: true, | ||||||
|  |                         changeRequestIds: [123], | ||||||
|  |                     }, | ||||||
|  |                 ]} | ||||||
|  |             />, | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         expect(getByTestId('change-requests-icon')).toBeInTheDocument(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('shows change requests on focus', async () => { | ||||||
|  |         const ui = ( | ||||||
|  |             <StatusCell | ||||||
|  |                 project='test-project' | ||||||
|  |                 lifecycle={{ | ||||||
|  |                     stage: 'live', | ||||||
|  |                     enteredStageAt: '2025-04-01T00:00:00Z', | ||||||
|  |                 }} | ||||||
|  |                 environments={[ | ||||||
|  |                     { | ||||||
|  |                         name: 'production', | ||||||
|  |                         type: 'production', | ||||||
|  |                         enabled: true, | ||||||
|  |                         hasStrategies: true, | ||||||
|  |                         hasEnabledStrategies: true, | ||||||
|  |                         changeRequestIds: [123], | ||||||
|  |                     }, | ||||||
|  |                 ]} | ||||||
|  |             /> | ||||||
|  |         ); | ||||||
|  |         const { getByTestId, getByText, rerender } = render(ui); | ||||||
|  | 
 | ||||||
|  |         expect(await screen.queryByText('Change requests:')).toBeNull(); | ||||||
|  |         await userEvent.hover(getByTestId('change-requests-icon')); | ||||||
|  |         await screen.findByRole('tooltip'); | ||||||
|  |         expect( | ||||||
|  |             await screen.queryByText('Change requests:'), | ||||||
|  |         ).toBeInTheDocument(); | ||||||
|  |         expect(getByText('#123')).toBeInTheDocument(); | ||||||
|  |     }); | ||||||
|  | }); | ||||||
| @ -5,25 +5,39 @@ import { getStatus } from './getStatus'; | |||||||
| import DifferenceIcon from '@mui/icons-material/Difference'; | import DifferenceIcon from '@mui/icons-material/Difference'; | ||||||
| import { Link } from 'react-router-dom'; | import { Link } from 'react-router-dom'; | ||||||
| import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip'; | import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip'; | ||||||
|  | import { Truncator } from 'component/common/Truncator/Truncator'; | ||||||
| 
 | 
 | ||||||
| const Container = styled('div')(({ theme }) => ({ | const Container = styled('div')(({ theme }) => ({ | ||||||
|     padding: theme.spacing(0, 2), |     padding: theme.spacing(0, 2), | ||||||
|     display: 'flex', |     display: 'flex', | ||||||
|     alignItems: 'center', |     alignItems: 'center', | ||||||
|     gap: theme.spacing(1), |     gap: theme.spacing(0.5), | ||||||
|  |     minWidth: '180px', | ||||||
| })); | })); | ||||||
| 
 | 
 | ||||||
| const ChangeRequestIcon = styled(DifferenceIcon)(({ theme }) => ({ | const ChangeRequestIcon = styled(DifferenceIcon)(({ theme }) => ({ | ||||||
|     color: theme.palette.primary.main, |     color: theme.palette.primary.main, | ||||||
|     fontSize: theme.spacing(2.5), |     fontSize: theme.spacing(3.5), | ||||||
|     marginLeft: theme.spacing(0.5), |     padding: theme.spacing(0.5), | ||||||
| })); | })); | ||||||
| 
 | 
 | ||||||
| export const StatusCell: FC<FeatureSearchResponseSchema> = ({ | const ChangeRequestTooltip = styled('div')(({ theme }) => ({ | ||||||
|     lifecycle, |     display: 'flex', | ||||||
|     environments, |     flexDirection: 'column', | ||||||
|     project, |     alignItems: 'center', | ||||||
| }) => { |     gap: theme.spacing(0.5), | ||||||
|  |     ul: { | ||||||
|  |         listStyle: 'none', | ||||||
|  |         padding: 0, | ||||||
|  |         margin: 0, | ||||||
|  |         display: 'flex', | ||||||
|  |         flexDirection: 'column', | ||||||
|  |     }, | ||||||
|  | })); | ||||||
|  | 
 | ||||||
|  | export const StatusCell: FC< | ||||||
|  |     Pick<FeatureSearchResponseSchema, 'lifecycle' | 'environments' | 'project'> | ||||||
|  | > = ({ lifecycle, environments, project }) => { | ||||||
|     const status = useMemo( |     const status = useMemo( | ||||||
|         () => getStatus({ lifecycle, environments }), |         () => getStatus({ lifecycle, environments }), | ||||||
|         [lifecycle, environments], |         [lifecycle, environments], | ||||||
| @ -35,28 +49,35 @@ export const StatusCell: FC<FeatureSearchResponseSchema> = ({ | |||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|         <Container> |         <Container> | ||||||
|             <div>{status}</div> |             <Truncator title={status} lines={2}> | ||||||
|  |                 {status} | ||||||
|  |             </Truncator> | ||||||
|             {changeRequestIds.length > 0 && ( |             {changeRequestIds.length > 0 && ( | ||||||
|                 <HtmlTooltip |                 <HtmlTooltip | ||||||
|                     arrow |                     arrow | ||||||
|                     title={ |                     title={ | ||||||
|                         <div> |                         <ChangeRequestTooltip> | ||||||
|                             <span>Change requests:</span> |                             <div>Change requests:</div> | ||||||
|                             <br /> |                             <ul> | ||||||
|                             {changeRequestIds.map((id) => ( |                                 {changeRequestIds.map((id) => ( | ||||||
|                                 <Link |                                     <li key={id}> | ||||||
|                                     key={id} |                                         <Link | ||||||
|                                     to={`/projects/${project}/change-requests/${id}`} |                                             to={`/projects/${project}/change-requests/${id}`} | ||||||
|                                     target='_blank' |                                             target='_blank' | ||||||
|                                     rel='noopener noreferrer' |                                             rel='noopener noreferrer' | ||||||
|                                 > |                                         > | ||||||
|                                     {`#${id}`} |                                             {`#${id}`} | ||||||
|                                 </Link> |                                         </Link> | ||||||
|                             ))} |                                     </li> | ||||||
|                         </div> |                                 ))} | ||||||
|  |                             </ul> | ||||||
|  |                         </ChangeRequestTooltip> | ||||||
|                     } |                     } | ||||||
|                 > |                 > | ||||||
|                     <ChangeRequestIcon /> |                     <ChangeRequestIcon | ||||||
|  |                         data-testid='change-requests-icon' | ||||||
|  |                         tabIndex={0} | ||||||
|  |                     /> | ||||||
|                 </HtmlTooltip> |                 </HtmlTooltip> | ||||||
|             )} |             )} | ||||||
|         </Container> |         </Container> | ||||||
|  | |||||||
| @ -148,4 +148,44 @@ describe('getStatus', () => { | |||||||
|             ).toBe('No strategies'); |             ).toBe('No strategies'); | ||||||
|         }); |         }); | ||||||
|     }); |     }); | ||||||
|  | 
 | ||||||
|  |     describe('release plan - milestones', () => { | ||||||
|  |         it('should show the release plan', () => { | ||||||
|  |             expect( | ||||||
|  |                 getStatus({ | ||||||
|  |                     environments: [ | ||||||
|  |                         { | ||||||
|  |                             ...prodEnvEnabled, | ||||||
|  |                             totalMilestones: 2, | ||||||
|  |                             milestoneOrder: 0, | ||||||
|  |                             milestoneName: 'First step', | ||||||
|  |                         }, | ||||||
|  |                     ], | ||||||
|  |                     lifecycle: { | ||||||
|  |                         stage: 'live', | ||||||
|  |                         enteredStageAt: null as any, | ||||||
|  |                     }, | ||||||
|  |                 }), | ||||||
|  |             ).toBe('Milestone: First step (1 of 2)'); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         it('should not show the milestone if a flag is disabled', () => { | ||||||
|  |             expect( | ||||||
|  |                 getStatus({ | ||||||
|  |                     environments: [ | ||||||
|  |                         { | ||||||
|  |                             ...prodEnvDisabled, | ||||||
|  |                             totalMilestones: 2, | ||||||
|  |                             milestoneOrder: 0, | ||||||
|  |                             milestoneName: 'First step', | ||||||
|  |                         }, | ||||||
|  |                     ], | ||||||
|  |                     lifecycle: { | ||||||
|  |                         stage: 'live', | ||||||
|  |                         enteredStageAt: null as any, | ||||||
|  |                     }, | ||||||
|  |                 }), | ||||||
|  |             ).not.toBe('Release plan'); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
| }); | }); | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user