mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Feat/add strategy update event on strategy ordering (#4234)
Adds a `feature-strategy-update-event` when the strategy sort-order is changed. Makes all fields in the eventDataSchema nullable Closes # [1-11120](https://linear.app/unleash/issue/1-1112/we-should-have-event-for-re-ordering-strategies) --------- Signed-off-by: andreas-unleash <andreas@getunleash.ai>
This commit is contained in:
		
							parent
							
								
									3e98e1743f
								
							
						
					
					
						commit
						16e3799b9a
					
				| @ -12,29 +12,35 @@ const eventDataSchema = { | ||||
|             description: | ||||
|                 'Name of the feature toggle/strategy/environment that this event relates to', | ||||
|             example: 'my.first.toggle', | ||||
|             nullable: true, | ||||
|         }, | ||||
|         description: { | ||||
|             type: 'string', | ||||
|             description: 'The description of the object this event relates to', | ||||
|             example: 'Toggle description', | ||||
|             nullable: true, | ||||
|         }, | ||||
|         type: { | ||||
|             type: 'string', | ||||
|             nullable: true, | ||||
|             description: | ||||
|                 'If this event relates to a feature toggle, the type of feature toggle.', | ||||
|             example: 'release', | ||||
|         }, | ||||
|         project: { | ||||
|             nullable: true, | ||||
|             type: 'string', | ||||
|             description: 'The project this event relates to', | ||||
|             example: 'default', | ||||
|         }, | ||||
|         stale: { | ||||
|             nullable: true, | ||||
|             description: 'Is the feature toggle this event relates to stale', | ||||
|             type: 'boolean', | ||||
|             example: true, | ||||
|         }, | ||||
|         variants: { | ||||
|             nullable: true, | ||||
|             description: 'Variants configured for this toggle', | ||||
|             type: 'array', | ||||
|             items: { | ||||
| @ -42,6 +48,7 @@ const eventDataSchema = { | ||||
|             }, | ||||
|         }, | ||||
|         createdAt: { | ||||
|             nullable: true, | ||||
|             type: 'string', | ||||
|             format: 'date-time', | ||||
|             description: | ||||
| @ -49,11 +56,11 @@ const eventDataSchema = { | ||||
|             example: '2023-07-05T12:56:00.000Z', | ||||
|         }, | ||||
|         lastSeenAt: { | ||||
|             nullable: true, | ||||
|             type: 'string', | ||||
|             format: 'date-time', | ||||
|             description: 'The time the feature was last seen', | ||||
|             example: '2023-07-05T12:56:00.000Z', | ||||
|             nullable: true, | ||||
|         }, | ||||
|         impressionData: { | ||||
|             description: | ||||
| @ -127,7 +134,12 @@ export const eventSchema = { | ||||
|                 'The name of the feature toggle the event relates to, if applicable.', | ||||
|             example: 'my.first.feature', | ||||
|         }, | ||||
|         data: eventDataSchema, | ||||
|         data: { | ||||
|             ...eventDataSchema, | ||||
|             description: | ||||
|                 "Data relating to the current state of the event's subject.", | ||||
|             nullable: true, | ||||
|         }, | ||||
|         preData: { | ||||
|             ...eventDataSchema, | ||||
|             description: | ||||
|  | ||||
| @ -936,7 +936,7 @@ export default class ProjectFeaturesController extends Controller { | ||||
|     } | ||||
| 
 | ||||
|     async setStrategiesSortOrder( | ||||
|         req: Request< | ||||
|         req: IAuthRequest< | ||||
|             FeatureStrategyParams, | ||||
|             any, | ||||
|             SetStrategySortOrderSchema, | ||||
| @ -944,10 +944,18 @@ export default class ProjectFeaturesController extends Controller { | ||||
|         >, | ||||
|         res: Response, | ||||
|     ): Promise<void> { | ||||
|         const { featureName } = req.params; | ||||
|         await this.featureService.updateStrategiesSortOrder( | ||||
|             featureName, | ||||
|             req.body, | ||||
|         const { featureName, projectId: project, environment } = req.params; | ||||
|         const createdBy = extractUsername(req); | ||||
|         await this.startTransaction(async (tx) => | ||||
|             this.transactionalFeatureToggleService( | ||||
|                 tx, | ||||
|             ).updateStrategiesSortOrder( | ||||
|                 featureName, | ||||
|                 environment, | ||||
|                 project, | ||||
|                 createdBy, | ||||
|                 req.body, | ||||
|             ), | ||||
|         ); | ||||
| 
 | ||||
|         res.status(200).send(); | ||||
|  | ||||
| @ -369,7 +369,7 @@ class FeatureToggleService { | ||||
|         featureStrategy: IFeatureStrategy, | ||||
|         segments: ISegment[] = [], | ||||
|     ): Saved<IStrategyConfig> { | ||||
|         return { | ||||
|         const result: Saved<IStrategyConfig> = { | ||||
|             id: featureStrategy.id, | ||||
|             name: featureStrategy.strategyName, | ||||
|             title: featureStrategy.title, | ||||
| @ -378,16 +378,56 @@ class FeatureToggleService { | ||||
|             parameters: featureStrategy.parameters, | ||||
|             segments: segments.map((segment) => segment.id) ?? [], | ||||
|         }; | ||||
| 
 | ||||
|         if (this.flagResolver.isEnabled('strategyVariant')) { | ||||
|             result.sortOrder = featureStrategy.sortOrder; | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     async updateStrategiesSortOrder( | ||||
|         featureName: string, | ||||
|         environment: string, | ||||
|         project: string, | ||||
|         createdBy: string, | ||||
|         sortOrders: SetStrategySortOrderSchema, | ||||
|     ): Promise<Saved<any>> { | ||||
|         await Promise.all( | ||||
|             sortOrders.map(async ({ id, sortOrder }) => | ||||
|                 this.featureStrategiesStore.updateSortOrder(id, sortOrder), | ||||
|             ), | ||||
|             sortOrders.map(async ({ id, sortOrder }) => { | ||||
|                 const strategyToUpdate = | ||||
|                     await this.featureStrategiesStore.getStrategyById(id); | ||||
|                 await this.featureStrategiesStore.updateSortOrder( | ||||
|                     id, | ||||
|                     sortOrder, | ||||
|                 ); | ||||
|                 const updatedStrategy = | ||||
|                     await this.featureStrategiesStore.getStrategyById(id); | ||||
| 
 | ||||
|                 const tags = await this.tagStore.getAllTagsForFeature( | ||||
|                     featureName, | ||||
|                 ); | ||||
|                 const segments = await this.segmentService.getByStrategy( | ||||
|                     strategyToUpdate.id, | ||||
|                 ); | ||||
|                 const strategy = this.featureStrategyToPublic( | ||||
|                     updatedStrategy, | ||||
|                     segments, | ||||
|                 ); | ||||
|                 await this.eventStore.store( | ||||
|                     new FeatureStrategyUpdateEvent({ | ||||
|                         featureName, | ||||
|                         environment, | ||||
|                         project, | ||||
|                         createdBy, | ||||
|                         preData: this.featureStrategyToPublic( | ||||
|                             strategyToUpdate, | ||||
|                             segments, | ||||
|                         ), | ||||
|                         data: strategy, | ||||
|                         tags: tags, | ||||
|                     }), | ||||
|                 ); | ||||
|             }), | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
| @ -472,24 +512,31 @@ class FeatureToggleService { | ||||
|                 ); | ||||
|             } | ||||
| 
 | ||||
|             const tags = await this.tagStore.getAllTagsForFeature(featureName); | ||||
|             const segments = await this.segmentService.getByStrategy( | ||||
|                 newFeatureStrategy.id, | ||||
|             ); | ||||
| 
 | ||||
|             const strategy = this.featureStrategyToPublic( | ||||
|                 newFeatureStrategy, | ||||
|                 segments, | ||||
|             ); | ||||
|             await this.eventStore.store( | ||||
|                 new FeatureStrategyAddEvent({ | ||||
|                     project: projectId, | ||||
| 
 | ||||
|             if (this.flagResolver.isEnabled('strategyVariant')) { | ||||
|                 const tags = await this.tagStore.getAllTagsForFeature( | ||||
|                     featureName, | ||||
|                     createdBy, | ||||
|                     environment, | ||||
|                     data: strategy, | ||||
|                     tags, | ||||
|                 }), | ||||
|             ); | ||||
|                 ); | ||||
| 
 | ||||
|                 await this.eventStore.store( | ||||
|                     new FeatureStrategyAddEvent({ | ||||
|                         project: projectId, | ||||
|                         featureName, | ||||
|                         createdBy, | ||||
|                         environment, | ||||
|                         data: strategy, | ||||
|                         tags, | ||||
|                     }), | ||||
|                 ); | ||||
|             } | ||||
|             return strategy; | ||||
|         } catch (e) { | ||||
|             if (e.code === FOREIGN_KEY_VIOLATION) { | ||||
|  | ||||
| @ -26,6 +26,10 @@ import { v4 as uuidv4 } from 'uuid'; | ||||
| import supertest from 'supertest'; | ||||
| import { randomId } from '../../../../../lib/util/random-id'; | ||||
| import { DEFAULT_PROJECT } from '../../../../../lib/types'; | ||||
| import { | ||||
|     FeatureStrategySchema, | ||||
|     SetStrategySortOrderSchema, | ||||
| } from '../../../../../lib/openapi'; | ||||
| 
 | ||||
| let app: IUnleashTest; | ||||
| let db: ITestDb; | ||||
| @ -3227,3 +3231,66 @@ test('Enabling a feature environment should add the default strategy when only d | ||||
|             expect(res.body.strategies[1].disabled).toBeFalsy(); | ||||
|         }); | ||||
| }); | ||||
| 
 | ||||
| test('Updating feature strategy sort-order should trigger a FeatureStrategyUpdatedEvent when strategyVariant is true', async () => { | ||||
|     app = await setupAppWithCustomConfig( | ||||
|         db.stores, | ||||
|         { | ||||
|             experimental: { | ||||
|                 flags: { | ||||
|                     strictSchemaValidation: true, | ||||
|                     strategyVariant: true, | ||||
|                 }, | ||||
|             }, | ||||
|         }, | ||||
|         db.rawDatabase, | ||||
|     ); | ||||
| 
 | ||||
|     const envName = 'sort-order-within-environment-strategyVariant'; | ||||
|     const featureName = 'feature.sort.order.event.list'; | ||||
| 
 | ||||
|     await db.stores.environmentStore.create({ | ||||
|         name: envName, | ||||
|         type: 'test', | ||||
|     }); | ||||
| 
 | ||||
|     await app.request | ||||
|         .post('/api/admin/projects/default/environments') | ||||
|         .send({ | ||||
|             environment: envName, | ||||
|         }) | ||||
|         .expect(200); | ||||
| 
 | ||||
|     await app.request | ||||
|         .post('/api/admin/projects/default/features') | ||||
|         .send({ name: featureName }) | ||||
|         .expect(201); | ||||
| 
 | ||||
|     await addStrategies(featureName, envName); | ||||
|     const { body } = await app.request.get( | ||||
|         `/api/admin/projects/default/features/${featureName}/environments/${envName}/strategies`, | ||||
|     ); | ||||
| 
 | ||||
|     const strategies: FeatureStrategySchema[] = body; | ||||
|     let order = 1; | ||||
|     const sortOrders: SetStrategySortOrderSchema = []; | ||||
| 
 | ||||
|     strategies.forEach((strategy) => { | ||||
|         sortOrders.push({ id: strategy.id!, sortOrder: order++ }); | ||||
|     }); | ||||
| 
 | ||||
|     await app.request | ||||
|         .post( | ||||
|             `/api/admin/projects/default/features/${featureName}/environments/${envName}/strategies/set-sort-order`, | ||||
|         ) | ||||
|         .send(sortOrders) | ||||
|         .expect(200); | ||||
| 
 | ||||
|     const response = await app.request.get(`/api/admin/events`); | ||||
|     const { body: eventsBody } = response; | ||||
|     let { events } = eventsBody; | ||||
| 
 | ||||
|     expect(events[0].type).toBe('feature-strategy-update'); | ||||
|     expect(events[1].type).toBe('feature-strategy-update'); | ||||
|     expect(events[2].type).toBe('feature-strategy-update'); | ||||
| }); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user