1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-31 00:16:47 +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.


![image](https://github.com/Unleash/unleash/assets/17786332/c74bb1c9-07f9-4bca-95bb-4ca020398444)
This commit is contained in:
Thomas Heartman 2023-11-27 11:34:34 +01:00 committed by GitHub
parent 20bfa73b82
commit b021e7cf85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 168 additions and 21 deletions

View File

@ -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}
/>
}
/>

View File

@ -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`,
);
});
});

View File

@ -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>
);
}
};

View File

@ -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,