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 { 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