mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01:00
Feat: show change request data on segment project usage page (#5410)
Show usage in change requests if that'd cause you to not be able to move the segment into a project. - [x] ~Relies on changes from #5407 (and #5405, #5406) to go through first.~ ![image](https://github.com/Unleash/unleash/assets/17786332/e6b84664-db86-457e-885f-a86c95bc46ec)
This commit is contained in:
parent
38a1fda28c
commit
dba1c90db8
@ -12,8 +12,14 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
|
|||||||
import useProjects from 'hooks/api/getters/useProjects/useProjects';
|
import useProjects from 'hooks/api/getters/useProjects/useProjects';
|
||||||
import { useOptionalPathParam } from 'hooks/useOptionalPathParam';
|
import { useOptionalPathParam } from 'hooks/useOptionalPathParam';
|
||||||
import { GO_BACK } from 'constants/navigate';
|
import { GO_BACK } from 'constants/navigate';
|
||||||
import { useStrategiesBySegment } from 'hooks/api/getters/useStrategiesBySegment/useStrategiesBySegment';
|
import {
|
||||||
|
ChangeRequestNewStrategy,
|
||||||
|
ChangeRequestUpdatedStrategy,
|
||||||
|
useStrategiesBySegment,
|
||||||
|
} from 'hooks/api/getters/useStrategiesBySegment/useStrategiesBySegment';
|
||||||
import { SegmentProjectAlert } from './SegmentProjectAlert';
|
import { SegmentProjectAlert } from './SegmentProjectAlert';
|
||||||
|
import { sortStrategiesByFeature } from './SegmentDelete/SegmentDeleteUsedSegment/sort-strategies';
|
||||||
|
import { IFeatureStrategy } from 'interfaces/strategy';
|
||||||
|
|
||||||
interface ISegmentFormPartOneProps {
|
interface ISegmentFormPartOneProps {
|
||||||
name: string;
|
name: string;
|
||||||
@ -71,11 +77,20 @@ export const SegmentFormStepOne: React.FC<ISegmentFormPartOneProps> = ({
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { projects, loading: loadingProjects } = useProjects();
|
const { projects, loading: loadingProjects } = useProjects();
|
||||||
|
|
||||||
const { strategies, loading: loadingStrategies } =
|
const {
|
||||||
useStrategiesBySegment(segmentId);
|
strategies,
|
||||||
|
changeRequestStrategies,
|
||||||
|
loading: loadingStrategies,
|
||||||
|
} = useStrategiesBySegment(segmentId);
|
||||||
|
|
||||||
|
const collectedStrategies = sortStrategiesByFeature<
|
||||||
|
IFeatureStrategy,
|
||||||
|
ChangeRequestUpdatedStrategy,
|
||||||
|
ChangeRequestNewStrategy
|
||||||
|
>(strategies ?? [], changeRequestStrategies ?? []);
|
||||||
|
|
||||||
const projectsUsed = new Set<string>(
|
const projectsUsed = new Set<string>(
|
||||||
strategies.map(({ projectId }) => projectId!).filter(Boolean),
|
collectedStrategies.map(({ projectId }) => projectId!).filter(Boolean),
|
||||||
);
|
);
|
||||||
const availableProjects = projects.filter(
|
const availableProjects = projects.filter(
|
||||||
({ id }) =>
|
({ id }) =>
|
||||||
@ -142,7 +157,7 @@ export const SegmentFormStepOne: React.FC<ISegmentFormPartOneProps> = ({
|
|||||||
/>
|
/>
|
||||||
<SegmentProjectAlert
|
<SegmentProjectAlert
|
||||||
projects={projects}
|
projects={projects}
|
||||||
strategies={strategies}
|
strategies={collectedStrategies}
|
||||||
projectsUsed={Array.from(projectsUsed)}
|
projectsUsed={Array.from(projectsUsed)}
|
||||||
availableProjects={availableProjects}
|
availableProjects={availableProjects}
|
||||||
/>
|
/>
|
||||||
|
60
frontend/src/component/segments/SegmentProjectAlert.test.tsx
Normal file
60
frontend/src/component/segments/SegmentProjectAlert.test.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render } from 'utils/testRenderer';
|
||||||
|
import { screen } from '@testing-library/react';
|
||||||
|
import { SegmentProjectAlert } from './SegmentProjectAlert';
|
||||||
|
|
||||||
|
describe('SegmentDeleteUsedSegment', () => {
|
||||||
|
it('should link to change requests for change request strategies', async () => {
|
||||||
|
const projectId = 'project1';
|
||||||
|
|
||||||
|
const strategies = [
|
||||||
|
{
|
||||||
|
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' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const projectsUsed = [...new Set(strategies.map((s) => s.projectId))];
|
||||||
|
|
||||||
|
render(
|
||||||
|
<SegmentProjectAlert
|
||||||
|
projects={[]}
|
||||||
|
availableProjects={[]}
|
||||||
|
projectsUsed={projectsUsed}
|
||||||
|
strategies={strategies}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const links = await screen.findAllByRole('link');
|
||||||
|
expect(links).toHaveLength(strategies.length + projectsUsed.length);
|
||||||
|
|
||||||
|
const [projectLink, crLink1, crLink2] = links;
|
||||||
|
|
||||||
|
expect(projectLink).toHaveTextContent(projectId);
|
||||||
|
expect(projectLink).toHaveAttribute('href', `/projects/${projectId}`);
|
||||||
|
|
||||||
|
expect(crLink1).toHaveTextContent('#1');
|
||||||
|
expect(crLink1).toHaveAccessibleDescription('Change request 1');
|
||||||
|
expect(crLink1).toHaveAttribute(
|
||||||
|
'href',
|
||||||
|
`/projects/${projectId}/change-requests/1`,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(crLink2).toHaveTextContent('#2 (My cool CR)');
|
||||||
|
expect(crLink2).toHaveAccessibleDescription('Change request 2');
|
||||||
|
expect(crLink2).toHaveAttribute(
|
||||||
|
'href',
|
||||||
|
`/projects/${projectId}/change-requests/2`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@ -6,6 +6,11 @@ import { Link } from 'react-router-dom';
|
|||||||
import { formatStrategyName } from 'utils/strategyNames';
|
import { formatStrategyName } from 'utils/strategyNames';
|
||||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import {
|
||||||
|
ChangeRequestNewStrategy,
|
||||||
|
ChangeRequestStrategy,
|
||||||
|
ChangeRequestUpdatedStrategy,
|
||||||
|
} from 'hooks/api/getters/useStrategiesBySegment/useStrategiesBySegment';
|
||||||
|
|
||||||
const StyledUl = styled('ul')(({ theme }) => ({
|
const StyledUl = styled('ul')(({ theme }) => ({
|
||||||
listStyle: 'none',
|
listStyle: 'none',
|
||||||
@ -18,7 +23,11 @@ const StyledAlert = styled(Alert)(({ theme }) => ({
|
|||||||
|
|
||||||
interface ISegmentProjectAlertProps {
|
interface ISegmentProjectAlertProps {
|
||||||
projects: IProjectCard[];
|
projects: IProjectCard[];
|
||||||
strategies: IFeatureStrategy[];
|
strategies: (
|
||||||
|
| IFeatureStrategy
|
||||||
|
| ChangeRequestUpdatedStrategy
|
||||||
|
| ChangeRequestNewStrategy
|
||||||
|
)[];
|
||||||
projectsUsed: string[];
|
projectsUsed: string[];
|
||||||
availableProjects: IProjectCard[];
|
availableProjects: IProjectCard[];
|
||||||
}
|
}
|
||||||
@ -56,23 +65,9 @@ export const SegmentProjectAlert = ({
|
|||||||
?.filter(
|
?.filter(
|
||||||
(strategy) => strategy.projectId === projectId,
|
(strategy) => strategy.projectId === projectId,
|
||||||
)
|
)
|
||||||
.map((strategy) => (
|
.map((strategy, index) =>
|
||||||
<li key={strategy.id}>
|
strategyListItem(strategy, index),
|
||||||
<Link
|
)}
|
||||||
to={formatEditStrategyPath(
|
|
||||||
strategy.projectId!,
|
|
||||||
strategy.featureName!,
|
|
||||||
strategy.environment!,
|
|
||||||
strategy.id,
|
|
||||||
)}
|
|
||||||
target='_blank'
|
|
||||||
rel='noreferrer'
|
|
||||||
>
|
|
||||||
{strategy.featureName!}{' '}
|
|
||||||
{formatStrategyNameParens(strategy)}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
@ -100,10 +95,73 @@ export const SegmentProjectAlert = ({
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatStrategyNameParens = (strategy: IFeatureStrategy): string => {
|
const formatStrategyNameParens = (strategy: {
|
||||||
|
strategyName?: string;
|
||||||
|
}): string => {
|
||||||
if (!strategy.strategyName) {
|
if (!strategy.strategyName) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return `(${formatStrategyName(strategy.strategyName)})`;
|
return `(${formatStrategyName(strategy.strategyName)})`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const formatChangeRequestPath = (
|
||||||
|
projectId: string,
|
||||||
|
changeRequestId: number,
|
||||||
|
): string => {
|
||||||
|
return `/projects/${projectId}/change-requests/${changeRequestId}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 `}
|
||||||
|
|
||||||
|
<Link
|
||||||
|
to={formatChangeRequestPath(strategy.projectId, id)}
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
title={`Change request ${id}`}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<li key={strategy.id}>
|
||||||
|
<Link
|
||||||
|
to={formatEditStrategyPath(
|
||||||
|
strategy.projectId!,
|
||||||
|
strategy.featureName!,
|
||||||
|
strategy.environment!,
|
||||||
|
strategy.id,
|
||||||
|
)}
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
>
|
||||||
|
{strategy.featureName!} {formatStrategyNameParens(strategy)}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user