1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-09 00:18:00 +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, onClose,
onRemove, onRemove,
}: ISegmentDeleteProps) => { }: ISegmentDeleteProps) => {
const { strategies, loading } = useStrategiesBySegment(segment.id); const { strategies, changeRequestStrategies, loading } =
const canDeleteSegment = strategies?.length === 0; useStrategiesBySegment(segment.id);
const canDeleteSegment =
strategies?.length === 0 && changeRequestStrategies?.length === 0;
if (loading) { if (loading) {
return null; return null;
} }
@ -42,6 +43,7 @@ export const SegmentDelete = ({
open={open} open={open}
onClose={onClose} onClose={onClose}
strategies={strategies} 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 { formatEditStrategyPath } from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit';
import { formatStrategyName } from 'utils/strategyNames'; import { formatStrategyName } from 'utils/strategyNames';
import { styled } from '@mui/material'; import { styled } from '@mui/material';
import {
ChangeRequestNewStrategy,
ChangeRequestStrategy,
ChangeRequestUpdatedStrategy,
} from 'hooks/api/getters/useStrategiesBySegment/useStrategiesBySegment';
import { sortStrategiesByFeature } from './sort-strategies';
const StyledUl = styled('ul')({ const StyledUl = styled('ul')({
marginBottom: 0, marginBottom: 0,
@ -21,14 +27,29 @@ interface ISegmentDeleteUsedSegmentProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
strategies: IFeatureStrategy[] | undefined; strategies: IFeatureStrategy[] | undefined;
changeRequestStrategies: ChangeRequestStrategy[] | undefined;
} }
export const formatChangeRequestPath = (
projectId: string,
changeRequestId: number,
): string => {
return `/projects/${projectId}/change-requests/${changeRequestId}`;
};
export const SegmentDeleteUsedSegment = ({ export const SegmentDeleteUsedSegment = ({
segment, segment,
open, open,
onClose, onClose,
strategies, strategies,
changeRequestStrategies,
}: ISegmentDeleteUsedSegmentProps) => { }: ISegmentDeleteUsedSegmentProps) => {
const sortedStrategies = sortStrategiesByFeature<
IFeatureStrategy,
ChangeRequestUpdatedStrategy,
ChangeRequestNewStrategy
>(strategies ?? [], changeRequestStrategies ?? []);
return ( return (
<Dialogue <Dialogue
title="You can't delete a segment that's currently in use" 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: <strong>{segment.name}</strong> segment for their strategies:
</p> </p>
<StyledUl> <StyledUl>
{strategies?.map((strategy) => ( {sortedStrategies.map((strategy, index) =>
<li key={strategy.id}> strategyListItem(strategy, index),
<StyledLink )}
to={formatEditStrategyPath(
strategy.projectId!,
strategy.featureName!,
strategy.environment!,
strategy.id,
)}
target='_blank'
rel='noopener noreferrer'
>
{strategy.featureName!}{' '}
{formatStrategyNameParens(strategy)}
</StyledLink>
</li>
))}
</StyledUl> </StyledUl>
</Dialogue> </Dialogue>
); );
}; };
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)})`;
}; };
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 { IFeatureStrategy } from 'interfaces/strategy';
import { useConditionalSWR } from '../useConditionalSWR/useConditionalSWR'; 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 { export interface IUseStrategiesBySegmentOutput {
strategies: IFeatureStrategy[]; strategies: IFeatureStrategy[];
changeRequestStrategies: ChangeRequestStrategy[];
refetchUsedSegments: () => void; refetchUsedSegments: () => void;
loading: boolean; loading: boolean;
error?: Error; error?: Error;
@ -26,6 +44,7 @@ export const useStrategiesBySegment = (
return { return {
strategies: data?.strategies || [], strategies: data?.strategies || [],
changeRequestStrategies: data?.changeRequestStrategies || [],
refetchUsedSegments, refetchUsedSegments,
loading: !error && !data, loading: !error && !data,
error, error,