mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01: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:
		
							parent
							
								
									b05216bc76
								
							
						
					
					
						commit
						994db02f84
					
				@ -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]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -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',
 | 
				
			||||||
 | 
				
			|||||||
@ -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[] {
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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>;
 | 
				
			||||||
 | 
				
			|||||||
@ -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,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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> {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user