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:
parent
3e98e1743f
commit
16e3799b9a
@ -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:
|
||||||
|
@ -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();
|
||||||
|
@ -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) {
|
||||||
|
@ -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');
|
||||||
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user