mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: project environment added and removed events (#5459)
This commit is contained in:
		
							parent
							
								
									9ed2c37b25
								
							
						
					
					
						commit
						2965daa195
					
				| @ -18,11 +18,13 @@ import { | ||||
|     insertLastSeenAt, | ||||
|     insertFeatureEnvironmentsLastSeen, | ||||
| } from '../../../../test/e2e/helpers/test-helper'; | ||||
| import { EventService } from '../../../services'; | ||||
| 
 | ||||
| let stores: IUnleashStores; | ||||
| let db; | ||||
| let service: FeatureToggleService; | ||||
| let segmentService: ISegmentService; | ||||
| let eventService: EventService; | ||||
| let environmentService: EnvironmentService; | ||||
| let unleashConfig; | ||||
| 
 | ||||
| @ -48,6 +50,8 @@ beforeAll(async () => { | ||||
|     segmentService = createSegmentService(db.rawDatabase, config); | ||||
| 
 | ||||
|     service = createFeatureToggleService(db.rawDatabase, config); | ||||
| 
 | ||||
|     eventService = new EventService(stores, config); | ||||
| }); | ||||
| 
 | ||||
| afterAll(async () => { | ||||
| @ -253,13 +257,17 @@ test('adding and removing an environment preserves variants when variants per en | ||||
|     ); | ||||
| 
 | ||||
|     //force the variantEnvironments flag off so that we can test legacy behavior
 | ||||
|     environmentService = new EnvironmentService(stores, { | ||||
|         ...unleashConfig, | ||||
|         flagResolver: { | ||||
|             // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||
|             isEnabled: (toggleName: string) => false, | ||||
|     environmentService = new EnvironmentService( | ||||
|         stores, | ||||
|         { | ||||
|             ...unleashConfig, | ||||
|             flagResolver: { | ||||
|                 // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||
|                 isEnabled: (toggleName: string) => false, | ||||
|             }, | ||||
|         }, | ||||
|     }); | ||||
|         eventService, | ||||
|     ); | ||||
| 
 | ||||
|     await environmentService.addEnvironmentToProject(prodEnv, 'default'); | ||||
|     await environmentService.removeEnvironmentFromProject(prodEnv, 'default'); | ||||
|  | ||||
| @ -18,6 +18,8 @@ import { | ||||
|     ProjectEnvironmentSchema, | ||||
| } from '../../../openapi'; | ||||
| import { OpenApiService, ProjectService } from '../../../services'; | ||||
| import { extractUsername } from '../../../util'; | ||||
| import { IAuthRequest } from '../../unleash-types'; | ||||
| 
 | ||||
| const PREFIX = '/:projectId/environments'; | ||||
| 
 | ||||
| @ -124,7 +126,7 @@ export default class EnvironmentsController extends Controller { | ||||
|     } | ||||
| 
 | ||||
|     async addEnvironmentToProject( | ||||
|         req: Request< | ||||
|         req: IAuthRequest< | ||||
|             Omit<IProjectEnvironmentParams, 'environment'>, | ||||
|             void, | ||||
|             ProjectEnvironmentSchema | ||||
| @ -138,13 +140,14 @@ export default class EnvironmentsController extends Controller { | ||||
|         await this.environmentService.addEnvironmentToProject( | ||||
|             environment, | ||||
|             projectId, | ||||
|             extractUsername(req), | ||||
|         ); | ||||
| 
 | ||||
|         res.status(200).end(); | ||||
|     } | ||||
| 
 | ||||
|     async removeEnvironmentFromProject( | ||||
|         req: Request<IProjectEnvironmentParams>, | ||||
|         req: IAuthRequest<IProjectEnvironmentParams>, | ||||
|         res: Response<void>, | ||||
|     ): Promise<void> { | ||||
|         const { projectId, environment } = req.params; | ||||
| @ -152,6 +155,7 @@ export default class EnvironmentsController extends Controller { | ||||
|         await this.environmentService.removeEnvironmentFromProject( | ||||
|             environment, | ||||
|             projectId, | ||||
|             extractUsername(req), | ||||
|         ); | ||||
| 
 | ||||
|         res.status(200).end(); | ||||
|  | ||||
| @ -7,6 +7,8 @@ import { | ||||
|     ISortOrder, | ||||
|     IUnleashConfig, | ||||
|     IUnleashStores, | ||||
|     PROJECT_ENVIRONMENT_ADDED, | ||||
|     PROJECT_ENVIRONMENT_REMOVED, | ||||
| } from '../types'; | ||||
| import { Logger } from '../logger'; | ||||
| import { BadDataError, UNIQUE_CONSTRAINT_VIOLATION } from '../error'; | ||||
| @ -17,6 +19,7 @@ import { IProjectStore } from 'lib/types/stores/project-store'; | ||||
| import MinimumOneEnvironmentError from '../error/minimum-one-environment-error'; | ||||
| import { IFlagResolver } from 'lib/types/experimental'; | ||||
| import { CreateFeatureStrategySchema } from '../openapi'; | ||||
| import EventService from './event-service'; | ||||
| 
 | ||||
| export default class EnvironmentService { | ||||
|     private logger: Logger; | ||||
| @ -29,6 +32,8 @@ export default class EnvironmentService { | ||||
| 
 | ||||
|     private featureEnvironmentStore: IFeatureEnvironmentStore; | ||||
| 
 | ||||
|     private eventService: EventService; | ||||
| 
 | ||||
|     private flagResolver: IFlagResolver; | ||||
| 
 | ||||
|     constructor( | ||||
| @ -48,12 +53,14 @@ export default class EnvironmentService { | ||||
|             getLogger, | ||||
|             flagResolver, | ||||
|         }: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>, | ||||
|         eventService: EventService, | ||||
|     ) { | ||||
|         this.logger = getLogger('services/environment-service.ts'); | ||||
|         this.environmentStore = environmentStore; | ||||
|         this.featureStrategiesStore = featureStrategiesStore; | ||||
|         this.featureEnvironmentStore = featureEnvironmentStore; | ||||
|         this.projectStore = projectStore; | ||||
|         this.eventService = eventService; | ||||
|         this.flagResolver = flagResolver; | ||||
|     } | ||||
| 
 | ||||
| @ -92,6 +99,7 @@ export default class EnvironmentService { | ||||
|     async addEnvironmentToProject( | ||||
|         environment: string, | ||||
|         projectId: string, | ||||
|         username = 'unknown', | ||||
|     ): Promise<void> { | ||||
|         try { | ||||
|             await this.featureEnvironmentStore.connectProject( | ||||
| @ -102,6 +110,12 @@ export default class EnvironmentService { | ||||
|                 environment, | ||||
|                 projectId, | ||||
|             ); | ||||
|             await this.eventService.storeEvent({ | ||||
|                 type: PROJECT_ENVIRONMENT_ADDED, | ||||
|                 project: projectId, | ||||
|                 environment, | ||||
|                 createdBy: username, | ||||
|             }); | ||||
|         } catch (e) { | ||||
|             if (e.code === UNIQUE_CONSTRAINT_VIOLATION) { | ||||
|                 throw new NameExistsError( | ||||
| @ -213,6 +227,7 @@ export default class EnvironmentService { | ||||
|     async removeEnvironmentFromProject( | ||||
|         environment: string, | ||||
|         projectId: string, | ||||
|         username = 'unknown', | ||||
|     ): Promise<void> { | ||||
|         const projectEnvs = | ||||
|             await this.projectStore.getEnvironmentsForProject(projectId); | ||||
| @ -222,6 +237,12 @@ export default class EnvironmentService { | ||||
|                 environment, | ||||
|                 projectId, | ||||
|             ); | ||||
|             await this.eventService.storeEvent({ | ||||
|                 type: PROJECT_ENVIRONMENT_REMOVED, | ||||
|                 project: projectId, | ||||
|                 environment, | ||||
|                 createdBy: username, | ||||
|             }); | ||||
|             return; | ||||
|         } | ||||
|         throw new MinimumOneEnvironmentError( | ||||
|  | ||||
| @ -222,7 +222,11 @@ export const createServices = ( | ||||
|         dependentFeaturesReadModel, | ||||
|         dependentFeaturesService, | ||||
|     ); | ||||
|     const environmentService = new EnvironmentService(stores, config); | ||||
|     const environmentService = new EnvironmentService( | ||||
|         stores, | ||||
|         config, | ||||
|         eventService, | ||||
|     ); | ||||
|     const featureTagService = new FeatureTagService( | ||||
|         stores, | ||||
|         config, | ||||
|  | ||||
| @ -108,6 +108,9 @@ export const GROUP_DELETED = 'group-deleted' as const; | ||||
| export const SETTING_CREATED = 'setting-created' as const; | ||||
| export const SETTING_UPDATED = 'setting-updated' as const; | ||||
| export const SETTING_DELETED = 'setting-deleted' as const; | ||||
| export const PROJECT_ENVIRONMENT_ADDED = 'project-environment-added' as const; | ||||
| export const PROJECT_ENVIRONMENT_REMOVED = | ||||
|     'project-environment-removed' as const; | ||||
| 
 | ||||
| export const CLIENT_METRICS = 'client-metrics' as const; | ||||
| export const CLIENT_REGISTER = 'client-register' as const; | ||||
| @ -292,6 +295,8 @@ export const IEventTypes = [ | ||||
|     BANNER_CREATED, | ||||
|     BANNER_UPDATED, | ||||
|     BANNER_DELETED, | ||||
|     PROJECT_ENVIRONMENT_ADDED, | ||||
|     PROJECT_ENVIRONMENT_REMOVED, | ||||
| ] as const; | ||||
| export type IEventType = (typeof IEventTypes)[number]; | ||||
| 
 | ||||
|  | ||||
| @ -4,16 +4,19 @@ import dbInit from '../helpers/database-init'; | ||||
| import NotFoundError from '../../../lib/error/notfound-error'; | ||||
| import { IUnleashStores } from '../../../lib/types'; | ||||
| import NameExistsError from '../../../lib/error/name-exists-error'; | ||||
| import { EventService } from '../../../lib/services'; | ||||
| 
 | ||||
| let stores: IUnleashStores; | ||||
| let db; | ||||
| let service: EnvironmentService; | ||||
| let eventService: EventService; | ||||
| 
 | ||||
| beforeAll(async () => { | ||||
|     const config = createTestConfig(); | ||||
|     db = await dbInit('environment_service_serial', config.getLogger); | ||||
|     stores = db.stores; | ||||
|     service = new EnvironmentService(stores, config); | ||||
|     eventService = new EventService(stores, config); | ||||
|     service = new EnvironmentService(stores, config, eventService); | ||||
| }); | ||||
| afterAll(async () => { | ||||
|     await db.destroy(); | ||||
| @ -50,7 +53,7 @@ test('Can connect environment to project', async () => { | ||||
|         description: '', | ||||
|         stale: false, | ||||
|     }); | ||||
|     await service.addEnvironmentToProject('test-connection', 'default'); | ||||
|     await service.addEnvironmentToProject('test-connection', 'default', 'user'); | ||||
|     const overview = await stores.featureStrategiesStore.getFeatureOverview({ | ||||
|         projectId: 'default', | ||||
|     }); | ||||
| @ -68,6 +71,13 @@ test('Can connect environment to project', async () => { | ||||
|             }, | ||||
|         ]); | ||||
|     }); | ||||
|     const { events } = await eventService.getEvents(); | ||||
|     expect(events[0]).toMatchObject({ | ||||
|         type: 'project-environment-added', | ||||
|         project: 'default', | ||||
|         environment: 'test-connection', | ||||
|         createdBy: 'user', | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| test('Can remove environment from project', async () => { | ||||
| @ -98,7 +108,11 @@ test('Can remove environment from project', async () => { | ||||
|             }, | ||||
|         ]); | ||||
|     }); | ||||
|     await service.removeEnvironmentFromProject('removal-test', 'default'); | ||||
|     await service.removeEnvironmentFromProject( | ||||
|         'removal-test', | ||||
|         'default', | ||||
|         'user', | ||||
|     ); | ||||
|     overview = await stores.featureStrategiesStore.getFeatureOverview({ | ||||
|         projectId: 'default', | ||||
|     }); | ||||
| @ -106,6 +120,13 @@ test('Can remove environment from project', async () => { | ||||
|     overview.forEach((o) => { | ||||
|         expect(o.environments).toEqual([]); | ||||
|     }); | ||||
|     const { events } = await eventService.getEvents(); | ||||
|     expect(events[0]).toMatchObject({ | ||||
|         type: 'project-environment-removed', | ||||
|         project: 'default', | ||||
|         environment: 'removal-test', | ||||
|         createdBy: 'user', | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| test('Adding same environment twice should throw a NameExistsError', async () => { | ||||
|  | ||||
| @ -64,7 +64,7 @@ beforeAll(async () => { | ||||
| 
 | ||||
|     featureToggleService = createFeatureToggleService(db.rawDatabase, config); | ||||
| 
 | ||||
|     environmentService = new EnvironmentService(stores, config); | ||||
|     environmentService = new EnvironmentService(stores, config, eventService); | ||||
|     projectService = createProjectService(db.rawDatabase, config); | ||||
| }); | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user