mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +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 { EnvironmentVariantsTable } from './EnvironmentVariantsTable/EnvironmentVariantsTable'; | ||||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||||
| import { Badge } from 'component/common/Badge/Badge'; | 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 }) => ({ | const StyledCard = styled('div')(({ theme }) => ({ | ||||||
|     padding: theme.spacing(3), |     padding: theme.spacing(3), | ||||||
| @ -70,6 +74,13 @@ export const EnvironmentVariantsCard = ({ | |||||||
|     searchValue, |     searchValue, | ||||||
|     children, |     children, | ||||||
| }: IEnvironmentVariantsCardProps) => { | }: IEnvironmentVariantsCardProps) => { | ||||||
|  |     const projectId = useRequiredPathParam('projectId'); | ||||||
|  |     const featureId = useRequiredPathParam('featureId'); | ||||||
|  |     const scheduledRequestIds = useVariantsFromScheduledRequests( | ||||||
|  |         projectId, | ||||||
|  |         featureId, | ||||||
|  |         environment.name, | ||||||
|  |     ); | ||||||
|     const variants = environment.variants ?? []; |     const variants = environment.variants ?? []; | ||||||
|     const stickiness = variants[0]?.stickiness || 'default'; |     const stickiness = variants[0]?.stickiness || 'default'; | ||||||
| 
 | 
 | ||||||
| @ -81,6 +92,18 @@ export const EnvironmentVariantsCard = ({ | |||||||
|                     <StyledName deprecated={!environment.enabled}> |                     <StyledName deprecated={!environment.enabled}> | ||||||
|                         {environment.name} |                         {environment.name} | ||||||
|                     </StyledName> |                     </StyledName> | ||||||
|  |                     <ConditionallyRender | ||||||
|  |                         condition={scheduledRequestIds.length > 0} | ||||||
|  |                         show={ | ||||||
|  |                             <Box sx={{ ml: 2 }}> | ||||||
|  |                                 <ChangesScheduledBadge | ||||||
|  |                                     scheduledChangeRequestIds={ | ||||||
|  |                                         scheduledRequestIds | ||||||
|  |                                     } | ||||||
|  |                                 /> | ||||||
|  |                             </Box> | ||||||
|  |                         } | ||||||
|  |                     /> | ||||||
|                 </div> |                 </div> | ||||||
|                 {children} |                 {children} | ||||||
|             </StyledHeader> |             </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 { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests'; | ||||||
| import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; | import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; | ||||||
| import { Edit } from '@mui/icons-material'; | import { Edit } from '@mui/icons-material'; | ||||||
| import { VariantInfoAlert } from 'component/common/VariantInfoAlert/VariantInfoAlert'; |  | ||||||
| import { StrategyVariantsPreferredAlert } from 'component/common/StrategyVariantsUpgradeAlert/StrategyVariantsUpgradeAlert'; | import { StrategyVariantsPreferredAlert } from 'component/common/StrategyVariantsUpgradeAlert/StrategyVariantsUpgradeAlert'; | ||||||
| 
 | 
 | ||||||
| const StyledButtonContainer = styled('div')(({ theme }) => ({ | const StyledButtonContainer = styled('div')(({ theme }) => ({ | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user