Skip to content

Commit

Permalink
fix: Adds feature-variant-updated event. (#1189)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Christopher Kolstad and ivarconr committed Dec 16, 2021
1 parent b05216b commit 994db02
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 10 deletions.
3 changes: 2 additions & 1 deletion src/lib/db/feature-toggle-store.ts
Expand Up @@ -259,12 +259,13 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
}

async saveVariants(
project: string,
featureName: string,
newVariants: IVariant[],
): Promise<FeatureToggle> {
const row = await this.db(TABLE)
.update({ variants: JSON.stringify(newVariants) })
.where({ name: featureName })
.where({ project: project, name: featureName })
.returning(FEATURE_COLUMNS);
return this.rowToFeature(row[0]);
}
Expand Down
17 changes: 13 additions & 4 deletions src/lib/routes/admin-api/project/variants.ts
Expand Up @@ -7,6 +7,8 @@ import { Request, Response } from 'express';
import { Operation } from 'fast-json-patch';
import { UPDATE_FEATURE } from '../../../types/permissions';
import { IVariant } from '../../../types/model';
import { extractUsername } from '../../../util/extract-user';
import { IAuthRequest } from '../../unleash-types';

const PREFIX = '/:projectId/features/:featureName/variants';

Expand Down Expand Up @@ -47,13 +49,17 @@ export default class VariantsController extends Controller {
}

async patchVariants(
req: Request<FeatureParams, any, Operation[], any>,
req: IAuthRequest<FeatureParams, any, Operation[]>,
res: Response,
): Promise<void> {
const { featureName } = req.params;
const { projectId, featureName } = req.params;
const userName = extractUsername(req);

const updatedFeature = await this.featureService.updateVariants(
featureName,
projectId,
req.body,
userName,
);
res.status(200).json({
version: '1',
Expand All @@ -62,13 +68,16 @@ export default class VariantsController extends Controller {
}

async overwriteVariants(
req: Request<FeatureParams, any, IVariant[], any>,
req: IAuthRequest<FeatureParams, any, IVariant[], any>,
res: Response,
): Promise<void> {
const { featureName } = req.params;
const { projectId, featureName } = req.params;
const userName = extractUsername(req);
const updatedFeature = await this.featureService.saveVariants(
featureName,
projectId,
req.body,
userName,
);
res.status(200).json({
version: '1',
Expand Down
33 changes: 29 additions & 4 deletions src/lib/services/feature-toggle-service.ts
Expand Up @@ -11,6 +11,7 @@ import {
variantsArraySchema,
} from '../schema/feature-schema';
import {
FEATURE_UPDATED,
FeatureArchivedEvent,
FeatureChangeProjectEvent,
FeatureCreatedEvent,
Expand All @@ -22,7 +23,7 @@ import {
FeatureStrategyAddEvent,
FeatureStrategyRemoveEvent,
FeatureStrategyUpdateEvent,
FEATURE_UPDATED,
FeatureVariantEvent,
} from '../types/events';
import NotFoundError from '../error/notfound-error';
import {
Expand All @@ -36,8 +37,8 @@ import { IFeatureToggleStore } from '../types/stores/feature-toggle-store';
import {
FeatureToggle,
FeatureToggleDTO,
FeatureToggleWithEnvironment,
FeatureToggleLegacy,
FeatureToggleWithEnvironment,
IEnvironmentDetail,
IFeatureEnvironmentInfo,
IFeatureOverview,
Expand Down Expand Up @@ -232,6 +233,7 @@ class FeatureToggleService {
throw e;
}
}

/**
* PUT /api/admin/projects/:projectId/features/:featureName/strategies/:strategyId ?
* {
Expand Down Expand Up @@ -910,20 +912,43 @@ class FeatureToggleService {

async updateVariants(
featureName: string,
project: string,
newVariants: Operation[],
createdBy: string,
): Promise<FeatureToggle> {
const oldVariants = await this.getVariants(featureName);
const { newDocument } = await applyPatch(oldVariants, newVariants);
return this.saveVariants(featureName, newDocument);
return this.saveVariants(featureName, project, newDocument, createdBy);
}

async saveVariants(
featureName: string,
project: string,
newVariants: IVariant[],
createdBy: string,
): Promise<FeatureToggle> {
await variantsArraySchema.validateAsync(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[] {
Expand Down
28 changes: 27 additions & 1 deletion src/lib/types/events.ts
@@ -1,4 +1,4 @@
import { FeatureToggle, IStrategyConfig, ITag } from './model';
import { FeatureToggle, IStrategyConfig, ITag, IVariant } from './model';

export const APPLICATION_CREATED = 'application-created';

Expand All @@ -7,6 +7,7 @@ export const FEATURE_CREATED = 'feature-created';
export const FEATURE_DELETED = 'feature-deleted';
export const FEATURE_UPDATED = 'feature-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_ARCHIVED = 'feature-archived';
export const FEATURE_REVIVED = 'feature-revived';
Expand Down Expand Up @@ -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 {
readonly project: string;

Expand Down
1 change: 1 addition & 0 deletions src/lib/types/stores/feature-toggle-store.ts
Expand Up @@ -18,6 +18,7 @@ export interface IFeatureToggleStore extends Store<FeatureToggle, string> {
getAll(query?: Partial<IFeatureToggleQuery>): Promise<FeatureToggle[]>;
getVariants(featureName: string): Promise<IVariant[]>;
saveVariants(
project: string,
featureName: string,
newVariants: IVariant[],
): Promise<FeatureToggle>;
Expand Down
4 changes: 4 additions & 0 deletions src/test/e2e/api/admin/feature.e2e.test.ts
Expand Up @@ -38,10 +38,14 @@ beforeAll(async () => {
const createVariants = async (
featureName: string,
variants: IVariant[],
projectId: string = 'default',
username: string = 'test',
) => {
await app.services.featureToggleServiceV2.saveVariants(
featureName,
projectId,
variants,
username,
);
};

Expand Down
1 change: 1 addition & 0 deletions src/test/fixtures/fake-feature-toggle-store.ts
Expand Up @@ -134,6 +134,7 @@ export default class FakeFeatureToggleStore implements IFeatureToggleStore {
}

async saveVariants(
project: string,
featureName: string,
newVariants: IVariant[],
): Promise<FeatureToggle> {
Expand Down

1 comment on commit 994db02

@vercel
Copy link

@vercel vercel bot commented on 994db02 Dec 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.