1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-02 01:17:58 +02: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:
andreas-unleash 2023-07-14 04:46:13 +03:00 committed by GitHub
parent 3e98e1743f
commit 16e3799b9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 155 additions and 21 deletions

View File

@ -12,29 +12,35 @@ const eventDataSchema = {
description: description:
'Name of the feature toggle/strategy/environment that this event relates to', 'Name of the feature toggle/strategy/environment that this event relates to',
example: 'my.first.toggle', example: 'my.first.toggle',
nullable: true,
}, },
description: { description: {
type: 'string', type: 'string',
description: 'The description of the object this event relates to', description: 'The description of the object this event relates to',
example: 'Toggle description', example: 'Toggle description',
nullable: true,
}, },
type: { type: {
type: 'string', type: 'string',
nullable: true,
description: description:
'If this event relates to a feature toggle, the type of feature toggle.', 'If this event relates to a feature toggle, the type of feature toggle.',
example: 'release', example: 'release',
}, },
project: { project: {
nullable: true,
type: 'string', type: 'string',
description: 'The project this event relates to', description: 'The project this event relates to',
example: 'default', example: 'default',
}, },
stale: { stale: {
nullable: true,
description: 'Is the feature toggle this event relates to stale', description: 'Is the feature toggle this event relates to stale',
type: 'boolean', type: 'boolean',
example: true, example: true,
}, },
variants: { variants: {
nullable: true,
description: 'Variants configured for this toggle', description: 'Variants configured for this toggle',
type: 'array', type: 'array',
items: { items: {
@ -42,6 +48,7 @@ const eventDataSchema = {
}, },
}, },
createdAt: { createdAt: {
nullable: true,
type: 'string', type: 'string',
format: 'date-time', format: 'date-time',
description: description:
@ -49,11 +56,11 @@ const eventDataSchema = {
example: '2023-07-05T12:56:00.000Z', example: '2023-07-05T12:56:00.000Z',
}, },
lastSeenAt: { lastSeenAt: {
nullable: true,
type: 'string', type: 'string',
format: 'date-time', format: 'date-time',
description: 'The time the feature was last seen', description: 'The time the feature was last seen',
example: '2023-07-05T12:56:00.000Z', example: '2023-07-05T12:56:00.000Z',
nullable: true,
}, },
impressionData: { impressionData: {
description: description:
@ -127,7 +134,12 @@ export const eventSchema = {
'The name of the feature toggle the event relates to, if applicable.', 'The name of the feature toggle the event relates to, if applicable.',
example: 'my.first.feature', example: 'my.first.feature',
}, },
data: eventDataSchema, data: {
...eventDataSchema,
description:
"Data relating to the current state of the event's subject.",
nullable: true,
},
preData: { preData: {
...eventDataSchema, ...eventDataSchema,
description: description:

View File

@ -936,7 +936,7 @@ export default class ProjectFeaturesController extends Controller {
} }
async setStrategiesSortOrder( async setStrategiesSortOrder(
req: Request< req: IAuthRequest<
FeatureStrategyParams, FeatureStrategyParams,
any, any,
SetStrategySortOrderSchema, SetStrategySortOrderSchema,
@ -944,10 +944,18 @@ export default class ProjectFeaturesController extends Controller {
>, >,
res: Response, res: Response,
): Promise<void> { ): Promise<void> {
const { featureName } = req.params; const { featureName, projectId: project, environment } = req.params;
await this.featureService.updateStrategiesSortOrder( const createdBy = extractUsername(req);
featureName, await this.startTransaction(async (tx) =>
req.body, this.transactionalFeatureToggleService(
tx,
).updateStrategiesSortOrder(
featureName,
environment,
project,
createdBy,
req.body,
),
); );
res.status(200).send(); res.status(200).send();

View File

@ -369,7 +369,7 @@ class FeatureToggleService {
featureStrategy: IFeatureStrategy, featureStrategy: IFeatureStrategy,
segments: ISegment[] = [], segments: ISegment[] = [],
): Saved<IStrategyConfig> { ): Saved<IStrategyConfig> {
return { const result: Saved<IStrategyConfig> = {
id: featureStrategy.id, id: featureStrategy.id,
name: featureStrategy.strategyName, name: featureStrategy.strategyName,
title: featureStrategy.title, title: featureStrategy.title,
@ -378,16 +378,56 @@ class FeatureToggleService {
parameters: featureStrategy.parameters, parameters: featureStrategy.parameters,
segments: segments.map((segment) => segment.id) ?? [], segments: segments.map((segment) => segment.id) ?? [],
}; };
if (this.flagResolver.isEnabled('strategyVariant')) {
result.sortOrder = featureStrategy.sortOrder;
}
return result;
} }
async updateStrategiesSortOrder( async updateStrategiesSortOrder(
featureName: string, featureName: string,
environment: string,
project: string,
createdBy: string,
sortOrders: SetStrategySortOrderSchema, sortOrders: SetStrategySortOrderSchema,
): Promise<Saved<any>> { ): Promise<Saved<any>> {
await Promise.all( await Promise.all(
sortOrders.map(async ({ id, sortOrder }) => sortOrders.map(async ({ id, sortOrder }) => {
this.featureStrategiesStore.updateSortOrder(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( const segments = await this.segmentService.getByStrategy(
newFeatureStrategy.id, newFeatureStrategy.id,
); );
const strategy = this.featureStrategyToPublic( const strategy = this.featureStrategyToPublic(
newFeatureStrategy, newFeatureStrategy,
segments, segments,
); );
await this.eventStore.store(
new FeatureStrategyAddEvent({ if (this.flagResolver.isEnabled('strategyVariant')) {
project: projectId, const tags = await this.tagStore.getAllTagsForFeature(
featureName, featureName,
createdBy, );
environment,
data: strategy, await this.eventStore.store(
tags, new FeatureStrategyAddEvent({
}), project: projectId,
); featureName,
createdBy,
environment,
data: strategy,
tags,
}),
);
}
return strategy; return strategy;
} catch (e) { } catch (e) {
if (e.code === FOREIGN_KEY_VIOLATION) { if (e.code === FOREIGN_KEY_VIOLATION) {

View File

@ -26,6 +26,10 @@ import { v4 as uuidv4 } from 'uuid';
import supertest from 'supertest'; import supertest from 'supertest';
import { randomId } from '../../../../../lib/util/random-id'; import { randomId } from '../../../../../lib/util/random-id';
import { DEFAULT_PROJECT } from '../../../../../lib/types'; import { DEFAULT_PROJECT } from '../../../../../lib/types';
import {
FeatureStrategySchema,
SetStrategySortOrderSchema,
} from '../../../../../lib/openapi';
let app: IUnleashTest; let app: IUnleashTest;
let db: ITestDb; 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(); 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');
});