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