1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-01 13:47:27 +02:00

fix: Adds feature-variant-updated event. (#1189)

This triggers when we update or overwrite variants, and will include the
previous variants and the new variants.

Co-authored-by: Ivar Østhus <ivarconr@gmail.com>
This commit is contained in:
Christopher Kolstad 2021-12-16 11:07:19 +01:00 committed by GitHub
parent b05216bc76
commit 994db02f84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 77 additions and 10 deletions

View File

@ -259,12 +259,13 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
} }
async saveVariants( async saveVariants(
project: string,
featureName: string, featureName: string,
newVariants: IVariant[], newVariants: IVariant[],
): Promise<FeatureToggle> { ): Promise<FeatureToggle> {
const row = await this.db(TABLE) const row = await this.db(TABLE)
.update({ variants: JSON.stringify(newVariants) }) .update({ variants: JSON.stringify(newVariants) })
.where({ name: featureName }) .where({ project: project, name: featureName })
.returning(FEATURE_COLUMNS); .returning(FEATURE_COLUMNS);
return this.rowToFeature(row[0]); return this.rowToFeature(row[0]);
} }

View File

@ -7,6 +7,8 @@ import { Request, Response } from 'express';
import { Operation } from 'fast-json-patch'; import { Operation } from 'fast-json-patch';
import { UPDATE_FEATURE } from '../../../types/permissions'; import { UPDATE_FEATURE } from '../../../types/permissions';
import { IVariant } from '../../../types/model'; import { IVariant } from '../../../types/model';
import { extractUsername } from '../../../util/extract-user';
import { IAuthRequest } from '../../unleash-types';
const PREFIX = '/:projectId/features/:featureName/variants'; const PREFIX = '/:projectId/features/:featureName/variants';
@ -47,13 +49,17 @@ export default class VariantsController extends Controller {
} }
async patchVariants( async patchVariants(
req: Request<FeatureParams, any, Operation[], any>, req: IAuthRequest<FeatureParams, any, Operation[]>,
res: Response, res: Response,
): Promise<void> { ): Promise<void> {
const { featureName } = req.params; const { projectId, featureName } = req.params;
const userName = extractUsername(req);
const updatedFeature = await this.featureService.updateVariants( const updatedFeature = await this.featureService.updateVariants(
featureName, featureName,
projectId,
req.body, req.body,
userName,
); );
res.status(200).json({ res.status(200).json({
version: '1', version: '1',
@ -62,13 +68,16 @@ export default class VariantsController extends Controller {
} }
async overwriteVariants( async overwriteVariants(
req: Request<FeatureParams, any, IVariant[], any>, req: IAuthRequest<FeatureParams, any, IVariant[], any>,
res: Response, res: Response,
): Promise<void> { ): Promise<void> {
const { featureName } = req.params; const { projectId, featureName } = req.params;
const userName = extractUsername(req);
const updatedFeature = await this.featureService.saveVariants( const updatedFeature = await this.featureService.saveVariants(
featureName, featureName,
projectId,
req.body, req.body,
userName,
); );
res.status(200).json({ res.status(200).json({
version: '1', version: '1',

View File

@ -11,6 +11,7 @@ import {
variantsArraySchema, variantsArraySchema,
} from '../schema/feature-schema'; } from '../schema/feature-schema';
import { import {
FEATURE_UPDATED,
FeatureArchivedEvent, FeatureArchivedEvent,
FeatureChangeProjectEvent, FeatureChangeProjectEvent,
FeatureCreatedEvent, FeatureCreatedEvent,
@ -22,7 +23,7 @@ import {
FeatureStrategyAddEvent, FeatureStrategyAddEvent,
FeatureStrategyRemoveEvent, FeatureStrategyRemoveEvent,
FeatureStrategyUpdateEvent, FeatureStrategyUpdateEvent,
FEATURE_UPDATED, FeatureVariantEvent,
} from '../types/events'; } from '../types/events';
import NotFoundError from '../error/notfound-error'; import NotFoundError from '../error/notfound-error';
import { import {
@ -36,8 +37,8 @@ import { IFeatureToggleStore } from '../types/stores/feature-toggle-store';
import { import {
FeatureToggle, FeatureToggle,
FeatureToggleDTO, FeatureToggleDTO,
FeatureToggleWithEnvironment,
FeatureToggleLegacy, FeatureToggleLegacy,
FeatureToggleWithEnvironment,
IEnvironmentDetail, IEnvironmentDetail,
IFeatureEnvironmentInfo, IFeatureEnvironmentInfo,
IFeatureOverview, IFeatureOverview,
@ -232,6 +233,7 @@ class FeatureToggleService {
throw e; throw e;
} }
} }
/** /**
* PUT /api/admin/projects/:projectId/features/:featureName/strategies/:strategyId ? * PUT /api/admin/projects/:projectId/features/:featureName/strategies/:strategyId ?
* { * {
@ -910,20 +912,43 @@ class FeatureToggleService {
async updateVariants( async updateVariants(
featureName: string, featureName: string,
project: string,
newVariants: Operation[], newVariants: Operation[],
createdBy: string,
): Promise<FeatureToggle> { ): Promise<FeatureToggle> {
const oldVariants = await this.getVariants(featureName); const oldVariants = await this.getVariants(featureName);
const { newDocument } = await applyPatch(oldVariants, newVariants); const { newDocument } = await applyPatch(oldVariants, newVariants);
return this.saveVariants(featureName, newDocument); return this.saveVariants(featureName, project, newDocument, createdBy);
} }
async saveVariants( async saveVariants(
featureName: string, featureName: string,
project: string,
newVariants: IVariant[], newVariants: IVariant[],
createdBy: string,
): Promise<FeatureToggle> { ): Promise<FeatureToggle> {
await variantsArraySchema.validateAsync(newVariants); await variantsArraySchema.validateAsync(newVariants);
const fixedVariants = this.fixVariantWeights(newVariants); const fixedVariants = this.fixVariantWeights(newVariants);
return this.featureToggleStore.saveVariants(featureName, fixedVariants); const oldVariants = await this.featureToggleStore.getVariants(
featureName,
);
const featureToggle = await this.featureToggleStore.saveVariants(
project,
featureName,
fixedVariants,
);
const tags = await this.tagStore.getAllTagsForFeature(featureName);
await this.eventStore.store(
new FeatureVariantEvent({
project,
featureName,
createdBy,
tags,
oldVariants,
newVariants: featureToggle.variants,
}),
);
return featureToggle;
} }
fixVariantWeights(variants: IVariant[]): IVariant[] { fixVariantWeights(variants: IVariant[]): IVariant[] {

View File

@ -1,4 +1,4 @@
import { FeatureToggle, IStrategyConfig, ITag } from './model'; import { FeatureToggle, IStrategyConfig, ITag, IVariant } from './model';
export const APPLICATION_CREATED = 'application-created'; export const APPLICATION_CREATED = 'application-created';
@ -7,6 +7,7 @@ export const FEATURE_CREATED = 'feature-created';
export const FEATURE_DELETED = 'feature-deleted'; export const FEATURE_DELETED = 'feature-deleted';
export const FEATURE_UPDATED = 'feature-updated'; export const FEATURE_UPDATED = 'feature-updated';
export const FEATURE_METADATA_UPDATED = 'feature-metadata-updated'; export const FEATURE_METADATA_UPDATED = 'feature-metadata-updated';
export const FEATURE_VARIANTS_UPDATED = 'feature-variants-updated';
export const FEATURE_PROJECT_CHANGE = 'feature-project-change'; export const FEATURE_PROJECT_CHANGE = 'feature-project-change';
export const FEATURE_ARCHIVED = 'feature-archived'; export const FEATURE_ARCHIVED = 'feature-archived';
export const FEATURE_REVIVED = 'feature-revived'; export const FEATURE_REVIVED = 'feature-revived';
@ -140,6 +141,31 @@ export class FeatureEnvironmentEvent extends BaseEvent {
} }
} }
export class FeatureVariantEvent extends BaseEvent {
readonly project: string;
readonly featureName: string;
readonly data: {
oldVariants: IVariant[];
newVariants: IVariant[];
};
constructor(p: {
project: string;
featureName: string;
createdBy: string;
tags: ITag[];
oldVariants: IVariant[];
newVariants: IVariant[];
}) {
super(FEATURE_VARIANTS_UPDATED, p.createdBy, p.tags);
this.project = p.project;
this.featureName = p.featureName;
this.data = { oldVariants: p.oldVariants, newVariants: p.newVariants };
}
}
export class FeatureChangeProjectEvent extends BaseEvent { export class FeatureChangeProjectEvent extends BaseEvent {
readonly project: string; readonly project: string;

View File

@ -18,6 +18,7 @@ export interface IFeatureToggleStore extends Store<FeatureToggle, string> {
getAll(query?: Partial<IFeatureToggleQuery>): Promise<FeatureToggle[]>; getAll(query?: Partial<IFeatureToggleQuery>): Promise<FeatureToggle[]>;
getVariants(featureName: string): Promise<IVariant[]>; getVariants(featureName: string): Promise<IVariant[]>;
saveVariants( saveVariants(
project: string,
featureName: string, featureName: string,
newVariants: IVariant[], newVariants: IVariant[],
): Promise<FeatureToggle>; ): Promise<FeatureToggle>;

View File

@ -38,10 +38,14 @@ beforeAll(async () => {
const createVariants = async ( const createVariants = async (
featureName: string, featureName: string,
variants: IVariant[], variants: IVariant[],
projectId: string = 'default',
username: string = 'test',
) => { ) => {
await app.services.featureToggleServiceV2.saveVariants( await app.services.featureToggleServiceV2.saveVariants(
featureName, featureName,
projectId,
variants, variants,
username,
); );
}; };

View File

@ -134,6 +134,7 @@ export default class FakeFeatureToggleStore implements IFeatureToggleStore {
} }
async saveVariants( async saveVariants(
project: string,
featureName: string, featureName: string,
newVariants: IVariant[], newVariants: IVariant[],
): Promise<FeatureToggle> { ): Promise<FeatureToggle> {