mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: Feature lifecycle controller (#6788)
This commit is contained in:
		
							parent
							
								
									efda70ac5d
								
							
						
					
					
						commit
						28a3a064b9
					
				| @ -0,0 +1,86 @@ | |||||||
|  | import type { FeatureLifecycleService } from './feature-lifecycle-service'; | ||||||
|  | import { | ||||||
|  |     type IFlagResolver, | ||||||
|  |     type IUnleashConfig, | ||||||
|  |     type IUnleashServices, | ||||||
|  |     NONE, | ||||||
|  |     serializeDates, | ||||||
|  | } from '../../types'; | ||||||
|  | import type { OpenApiService } from '../../services'; | ||||||
|  | import { | ||||||
|  |     createResponseSchema, | ||||||
|  |     featureLifecycleSchema, | ||||||
|  |     type FeatureLifecycleSchema, | ||||||
|  |     getStandardResponses, | ||||||
|  | } from '../../openapi'; | ||||||
|  | import Controller from '../../routes/controller'; | ||||||
|  | import type { Request, Response } from 'express'; | ||||||
|  | import { NotFoundError } from '../../error'; | ||||||
|  | 
 | ||||||
|  | interface FeatureLifecycleParams { | ||||||
|  |     projectId: string; | ||||||
|  |     featureName: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const PATH = '/:projectId/features/:featureName/lifecycle'; | ||||||
|  | 
 | ||||||
|  | export default class FeatureLifecycleController extends Controller { | ||||||
|  |     private featureLifecycleService: FeatureLifecycleService; | ||||||
|  | 
 | ||||||
|  |     private openApiService: OpenApiService; | ||||||
|  | 
 | ||||||
|  |     private flagResolver: IFlagResolver; | ||||||
|  | 
 | ||||||
|  |     constructor( | ||||||
|  |         config: IUnleashConfig, | ||||||
|  |         { | ||||||
|  |             featureLifecycleService, | ||||||
|  |             openApiService, | ||||||
|  |         }: Pick<IUnleashServices, 'openApiService' | 'featureLifecycleService'>, | ||||||
|  |     ) { | ||||||
|  |         super(config); | ||||||
|  |         this.featureLifecycleService = featureLifecycleService; | ||||||
|  |         this.openApiService = openApiService; | ||||||
|  |         this.flagResolver = config.flagResolver; | ||||||
|  | 
 | ||||||
|  |         this.route({ | ||||||
|  |             method: 'get', | ||||||
|  |             path: PATH, | ||||||
|  |             handler: this.getFeatureLifecycle, | ||||||
|  |             permission: NONE, | ||||||
|  |             middleware: [ | ||||||
|  |                 openApiService.validPath({ | ||||||
|  |                     tags: ['Unstable'], | ||||||
|  |                     summary: 'Get feature lifecycle', | ||||||
|  |                     description: | ||||||
|  |                         'Information about the lifecycle stages of the feature.', | ||||||
|  |                     operationId: 'getFeatureLifecycle', | ||||||
|  |                     responses: { | ||||||
|  |                         200: createResponseSchema('featureLifecycleSchema'), | ||||||
|  |                         ...getStandardResponses(401, 403, 404), | ||||||
|  |                     }, | ||||||
|  |                 }), | ||||||
|  |             ], | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async getFeatureLifecycle( | ||||||
|  |         req: Request<FeatureLifecycleParams, any, any, any>, | ||||||
|  |         res: Response<FeatureLifecycleSchema>, | ||||||
|  |     ): Promise<void> { | ||||||
|  |         if (!this.flagResolver.isEnabled('featureLifecycle')) { | ||||||
|  |             throw new NotFoundError('Feature lifecycle is disabled.'); | ||||||
|  |         } | ||||||
|  |         const { featureName } = req.params; | ||||||
|  | 
 | ||||||
|  |         const result = | ||||||
|  |             await this.featureLifecycleService.getFeatureLifecycle(featureName); | ||||||
|  | 
 | ||||||
|  |         this.openApiService.respondWithValidation( | ||||||
|  |             200, | ||||||
|  |             res, | ||||||
|  |             featureLifecycleSchema.$id, | ||||||
|  |             serializeDates(result), | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,52 @@ | |||||||
|  | import dbInit, { type ITestDb } from '../../../test/e2e/helpers/database-init'; | ||||||
|  | import { | ||||||
|  |     type IUnleashTest, | ||||||
|  |     setupAppWithAuth, | ||||||
|  | } from '../../../test/e2e/helpers/test-helper'; | ||||||
|  | import getLogger from '../../../test/fixtures/no-logger'; | ||||||
|  | 
 | ||||||
|  | let app: IUnleashTest; | ||||||
|  | let db: ITestDb; | ||||||
|  | 
 | ||||||
|  | beforeAll(async () => { | ||||||
|  |     db = await dbInit('feature_lifecycle', getLogger); | ||||||
|  |     app = await setupAppWithAuth( | ||||||
|  |         db.stores, | ||||||
|  |         { | ||||||
|  |             experimental: { | ||||||
|  |                 flags: { | ||||||
|  |                     featureLifecycle: true, | ||||||
|  |                 }, | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         db.rawDatabase, | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     await app.request | ||||||
|  |         .post(`/auth/demo/login`) | ||||||
|  |         .send({ | ||||||
|  |             email: 'user@getunleash.io', | ||||||
|  |         }) | ||||||
|  |         .expect(200); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | afterAll(async () => { | ||||||
|  |     await app.destroy(); | ||||||
|  |     await db.destroy(); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | beforeEach(async () => {}); | ||||||
|  | 
 | ||||||
|  | const getFeatureLifecycle = async (featureName: string, expectedCode = 200) => { | ||||||
|  |     return app.request | ||||||
|  |         .get(`/api/admin/projects/default/features/${featureName}/lifecycle`) | ||||||
|  |         .expect(expectedCode); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | test('should return lifecycle stages', async () => { | ||||||
|  |     await app.createFeature('my_feature_a'); | ||||||
|  | 
 | ||||||
|  |     const { body } = await getFeatureLifecycle('my_feature_a'); | ||||||
|  | 
 | ||||||
|  |     expect(body).toEqual([]); | ||||||
|  | }); | ||||||
| @ -40,6 +40,7 @@ import { | |||||||
| import { projectApplicationsQueryParameters } from '../../openapi/spec/project-applications-query-parameters'; | import { projectApplicationsQueryParameters } from '../../openapi/spec/project-applications-query-parameters'; | ||||||
| import { normalizeQueryParams } from '../feature-search/search-utils'; | import { normalizeQueryParams } from '../feature-search/search-utils'; | ||||||
| import ProjectInsightsController from '../project-insights/project-insights-controller'; | import ProjectInsightsController from '../project-insights/project-insights-controller'; | ||||||
|  | import FeatureLifecycleController from '../feature-lifecycle/feature-lifecycle-controller'; | ||||||
| 
 | 
 | ||||||
| export default class ProjectController extends Controller { | export default class ProjectController extends Controller { | ||||||
|     private projectService: ProjectService; |     private projectService: ProjectService; | ||||||
| @ -181,6 +182,7 @@ export default class ProjectController extends Controller { | |||||||
|             ).router, |             ).router, | ||||||
|         ); |         ); | ||||||
|         this.use('/', new ProjectInsightsController(config, services).router); |         this.use('/', new ProjectInsightsController(config, services).router); | ||||||
|  |         this.use('/', new FeatureLifecycleController(config, services).router); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async getProjects( |     async getProjects( | ||||||
|  | |||||||
							
								
								
									
										33
									
								
								src/lib/openapi/spec/feature-lifecycle-schema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/lib/openapi/spec/feature-lifecycle-schema.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | |||||||
|  | import type { FromSchema } from 'json-schema-to-ts'; | ||||||
|  | 
 | ||||||
|  | export const featureLifecycleSchema = { | ||||||
|  |     $id: '#/components/schemas/featureLifecycleSchema', | ||||||
|  |     type: 'array', | ||||||
|  |     description: 'A list of lifecycle stages for a given feature', | ||||||
|  |     items: { | ||||||
|  |         additionalProperties: false, | ||||||
|  |         type: 'object', | ||||||
|  |         required: ['stage', 'enteredStageAt'], | ||||||
|  |         properties: { | ||||||
|  |             stage: { | ||||||
|  |                 type: 'string', | ||||||
|  |                 enum: ['initial', 'pre-live', 'live', 'completed', 'archived'], | ||||||
|  |                 example: 'initial', | ||||||
|  |                 description: | ||||||
|  |                     'The name of the lifecycle stage that got recorded for a given feature', | ||||||
|  |             }, | ||||||
|  |             enteredStageAt: { | ||||||
|  |                 type: 'string', | ||||||
|  |                 format: 'date-time', | ||||||
|  |                 example: '2023-01-28T16:21:39.975Z', | ||||||
|  |                 description: 'The date when the feature entered a given stage', | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         description: 'The lifecycle stage of the feature', | ||||||
|  |     }, | ||||||
|  |     components: { | ||||||
|  |         schemas: {}, | ||||||
|  |     }, | ||||||
|  | } as const; | ||||||
|  | 
 | ||||||
|  | export type FeatureLifecycleSchema = FromSchema<typeof featureLifecycleSchema>; | ||||||
| @ -75,6 +75,7 @@ export * from './feature-dependencies-schema'; | |||||||
| export * from './feature-environment-metrics-schema'; | export * from './feature-environment-metrics-schema'; | ||||||
| export * from './feature-environment-schema'; | export * from './feature-environment-schema'; | ||||||
| export * from './feature-events-schema'; | export * from './feature-events-schema'; | ||||||
|  | export * from './feature-lifecycle-schema'; | ||||||
| export * from './feature-metrics-schema'; | export * from './feature-metrics-schema'; | ||||||
| export * from './feature-schema'; | export * from './feature-schema'; | ||||||
| export * from './feature-search-environment-schema'; | export * from './feature-search-environment-schema'; | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user