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:
parent
de638b5b8e
commit
77db9f3258
@ -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');
|
||||
});
|
||||
});
|
@ -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>
|
||||
|
@ -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;
|
||||
};
|
@ -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 }) => ({
|
||||
|
Loading…
Reference in New Issue
Block a user