1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

feat: add Changes Scheduled badge to feature variants (#5312)

Adds the Changes Scheduled badge to environment variant when appropriate

Closes #
[1-1625](https://linear.app/unleash/issue/1-1625/show-a-badge-when-variant-in-scheduled-request)
<img width="1006" alt="Screenshot 2023-11-09 at 15 42 37"
src="https://github.com/Unleash/unleash/assets/104830839/118a3f0b-9acf-4a49-92d2-49bbe49a4c91">

---------

Signed-off-by: andreas-unleash <andreas@getunleash.ai>
Co-authored-by: Thomas Heartman <thomas@getunleash.io>
This commit is contained in:
andreas-unleash 2023-11-09 16:58:58 +02:00 committed by GitHub
parent de638b5b8e
commit 77db9f3258
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 325 additions and 1 deletions

View File

@ -0,0 +1,263 @@
import { testServerRoute, testServerSetup } from 'utils/testServer';
import { render } from 'utils/testRenderer';
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import { screen } from '@testing-library/dom';
import { Route, Routes } from 'react-router-dom';
import {
ChangeRequestAction,
IChangeRequest,
} from 'component/changeRequest/changeRequest.types';
import { EnvironmentVariantsCard } from './EnvironmentVariantsCard';
import { IFeatureEnvironment } from 'interfaces/featureToggle';
const server = testServerSetup();
const strategy = {
name: 'flexibleRollout',
constraints: [],
variants: [],
parameters: {
groupId: 'CR-toggle',
rollout: '100',
stickiness: 'default',
},
sortOrder: 0,
id: 'b6363cc8-ad8e-478a-b464-484bbd3b31f6',
title: '',
disabled: false,
};
const scheduledRequest = (
action: Omit<ChangeRequestAction, 'updateSegment'> = 'updateStrategy',
createdBy = 1,
): IChangeRequest => {
return {
id: 71,
title: 'Change request #71',
environment: 'production',
minApprovals: 1,
project: 'dafault',
createdBy: {
id: createdBy,
username: 'admin',
imageUrl:
'https://gravatar.com/avatar/8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918?s=42&d=retro&r=g',
},
createdAt: new Date('2023-11-08T10:28:47.183Z'),
features: [
{
name: 'feature1',
changes: [
{
id: 84,
action: action as any,
payload: {
id: 'b6363cc8-ad8e-478a-b464-484bbd3b31f6',
name: 'flexibleRollout',
title: '',
disabled: false,
segments: [],
variants: [],
parameters: {
groupId: 'CR-toggle',
rollout: '15',
stickiness: 'default',
},
constraints: [],
},
createdAt: new Date('2023-11-08T10:28:47.183Z'),
createdBy: {
id: 1,
username: 'admin',
imageUrl:
'https://gravatar.com/avatar/8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918?s=42&d=retro&r=g',
},
},
],
},
],
segments: [],
approvals: [],
rejections: [],
comments: [],
state: 'Scheduled',
schedule: {
scheduledAt: new Date().toISOString(),
status: 'pending',
},
};
};
const uiConfig = () => {
testServerRoute(server, '/api/admin/ui-config', {
versionInfo: {
current: { oss: 'version', enterprise: 'version' },
},
flags: {
scheduledConfigurationChanges: true,
},
});
};
const user = () => {
testServerRoute(server, '/api/admin/user', {
user: {
isAPI: false,
id: 1,
name: 'Some User',
email: 'user@example.com',
imageUrl:
'https://gravatar.com/avatar/8aa1132e102345f8c79322340e15340?size=42&default=retro',
seenAt: '2022-11-28T14:55:18.982Z',
loginAttempts: 0,
createdAt: '2022-11-23T13:31:17.061Z',
},
permissions: [{ permission: ADMIN }],
feedback: [],
splash: {},
});
};
const changeRequestConfig = () =>
testServerRoute(
server,
'/api/admin/projects/default/change-requests/config',
[
{
environment: 'development',
type: 'development',
changeRequestEnabled: false,
},
{
environment: 'production',
type: 'production',
changeRequestEnabled: true,
},
],
'get',
);
const feature = () => {
testServerRoute(server, '/api/admin/projects/default/features/feature1', {
environments: [
{
name: 'development',
lastSeenAt: null,
variants: [],
enabled: false,
type: 'development',
sortOrder: 2,
strategies: [],
},
{
name: 'production',
lastSeenAt: null,
variants: [],
enabled: false,
type: 'production',
sortOrder: 3,
strategies: [
{
name: 'flexibleRollout',
constraints: [],
variants: [],
parameters: {
groupId: 'CR-toggle',
rollout: '100',
stickiness: 'default',
},
sortOrder: 0,
id: 'b6363cc8-ad8e-478a-b464-484bbd3b31f6',
title: '',
disabled: false,
},
],
},
],
name: 'feature1',
favorite: false,
impressionData: false,
description: null,
project: 'MyNewProject',
stale: false,
lastSeenAt: null,
createdAt: '2023-11-01T10:11:58.505Z',
type: 'release',
variants: [],
archived: false,
dependencies: [],
children: [],
});
};
const setupOtherServerRoutes = () => {
uiConfig();
changeRequestConfig();
user();
feature();
};
beforeEach(() => {
setupOtherServerRoutes();
});
const Component = () => {
return (
<>
<Routes>
<Route
path={'/projects/:projectId/features/:featureId/variants'}
element={
<EnvironmentVariantsCard
environment={
{
name: 'production',
} as unknown as IFeatureEnvironment
}
searchValue={''}
/>
}
/>
</Routes>
</>
);
};
describe('Change request badges for variants', () => {
test('should not render a badge if no changes', async () => {
testServerRoute(
server,
'/api/admin/projects/default/change-requests/pending/feature1/variants',
[],
);
render(<Component />, {
route: '/projects/default/features/feature1/variants',
permissions: [
{
permission: ADMIN,
},
],
});
expect(screen.queryByText('Changes Scheduled')).toBe(null);
});
test('should render the badge when scheduled request with "patchVariant" action', async () => {
const changeRequest = scheduledRequest('patchVariant', 1);
testServerRoute(
server,
'/api/admin/projects/default/change-requests/pending/feature1',
[changeRequest],
);
render(<Component />, {
route: '/projects/default/features/feature1/variants',
permissions: [
{
permission: ADMIN,
},
],
});
await screen.findByText('Changes Scheduled');
});
});

View File

@ -4,6 +4,10 @@ import { IFeatureEnvironment } from 'interfaces/featureToggle';
import { EnvironmentVariantsTable } from './EnvironmentVariantsTable/EnvironmentVariantsTable';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { Badge } from 'component/common/Badge/Badge';
import { useRequiredPathParam } from '../../../../../../hooks/useRequiredPathParam';
import { useVariantsFromScheduledRequests } from './useVariantsFromScheduledRequests';
import { ChangesScheduledBadge } from '../../../../../changeRequest/ModifiedInChangeRequestStatusBadge/ChangesScheduledBadge';
import { Box } from '@mui/system';
const StyledCard = styled('div')(({ theme }) => ({
padding: theme.spacing(3),
@ -70,6 +74,13 @@ export const EnvironmentVariantsCard = ({
searchValue,
children,
}: IEnvironmentVariantsCardProps) => {
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const scheduledRequestIds = useVariantsFromScheduledRequests(
projectId,
featureId,
environment.name,
);
const variants = environment.variants ?? [];
const stickiness = variants[0]?.stickiness || 'default';
@ -81,6 +92,18 @@ export const EnvironmentVariantsCard = ({
<StyledName deprecated={!environment.enabled}>
{environment.name}
</StyledName>
<ConditionallyRender
condition={scheduledRequestIds.length > 0}
show={
<Box sx={{ ml: 2 }}>
<ChangesScheduledBadge
scheduledChangeRequestIds={
scheduledRequestIds
}
/>
</Box>
}
/>
</div>
{children}
</StyledHeader>

View File

@ -0,0 +1,39 @@
import { usePendingChangeRequestsForFeature } from 'hooks/api/getters/usePendingChangeRequestsForFeature/usePendingChangeRequestsForFeature';
export const useVariantsFromScheduledRequests = (
projectId: string,
featureId: string,
environment: string,
): number[] => {
const { changeRequests } = usePendingChangeRequestsForFeature(
projectId,
featureId,
);
const scheduledEnvironmentRequests =
changeRequests?.filter(
(request) =>
request.environment === environment &&
request.state === 'Scheduled',
) || [];
const result: number[] = [];
if (scheduledEnvironmentRequests.length === 0) {
return result;
}
scheduledEnvironmentRequests.forEach((scheduledRequest) => {
const feature = scheduledRequest?.features.find(
(feature) => feature.name === featureId,
);
const change = feature?.changes.find((change) => {
return change.action === 'patchVariant';
});
if (change) {
result.push(scheduledRequest.id);
}
});
return result;
};

View File

@ -27,7 +27,6 @@ import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import { Edit } from '@mui/icons-material';
import { VariantInfoAlert } from 'component/common/VariantInfoAlert/VariantInfoAlert';
import { StrategyVariantsPreferredAlert } from 'component/common/StrategyVariantsUpgradeAlert/StrategyVariantsUpgradeAlert';
const StyledButtonContainer = styled('div')(({ theme }) => ({