diff --git a/src/lib/routes/admin-api/project/features.ts b/src/lib/routes/admin-api/project/features.ts index 65a24c6ded..c65dc812ea 100644 --- a/src/lib/routes/admin-api/project/features.ts +++ b/src/lib/routes/admin-api/project/features.ts @@ -17,6 +17,7 @@ import { } from '../../../types/model'; import { extractUsername } from '../../../util/extract-user'; import { IAuthRequest } from '../../unleash-types'; +import { projectSchema } from '../../../services/project-schema'; interface FeatureStrategyParams { projectId: string; @@ -220,14 +221,16 @@ export default class ProjectFeaturesController extends Controller { } async addStrategy( - req: Request, + req: IAuthRequest, res: Response, ): Promise { const { projectId, featureName, environment } = req.params; + const userName = extractUsername(req); const featureStrategy = await this.featureService.createStrategy( req.body, projectId, featureName, + userName, environment, ); res.status(200).json(featureStrategy); @@ -248,34 +251,42 @@ export default class ProjectFeaturesController extends Controller { } async updateStrategy( - req: Request, + req: IAuthRequest, res: Response, ): Promise { - const { strategyId } = req.params; + const { strategyId, environment, projectId } = req.params; + const userName = extractUsername(req); const updatedStrategy = await this.featureService.updateStrategy( strategyId, + environment, + projectId, + userName, req.body, ); res.status(200).json(updatedStrategy); } async patchStrategy( - req: Request, + req: IAuthRequest, res: Response, ): Promise { - const { strategyId } = req.params; + const { strategyId, projectId, environment } = req.params; + const userName = extractUsername(req); const patch = req.body; const strategy = await this.featureService.getStrategy(strategyId); const { newDocument } = applyPatch(strategy, patch); const updatedStrategy = await this.featureService.updateStrategy( strategyId, + environment, + projectId, + userName, newDocument, ); res.status(200).json(updatedStrategy); } async getStrategy( - req: Request, + req: IAuthRequest, res: Response, ): Promise { this.logger.info('Getting strategy'); @@ -286,18 +297,25 @@ export default class ProjectFeaturesController extends Controller { } async deleteStrategy( - req: Request, + req: IAuthRequest, res: Response, ): Promise { this.logger.info('Deleting strategy'); + const { environment, projectId } = req.params; + const userName = extractUsername(req); const { strategyId } = req.params; this.logger.info(strategyId); - const strategy = await this.featureService.deleteStrategy(strategyId); + const strategy = await this.featureService.deleteStrategy( + strategyId, + userName, + projectId, + environment, + ); res.status(200).json(strategy); } async updateStrategyParameter( - req: Request< + req: IAuthRequest< StrategyIdParams, any, { name: string; value: string | number }, @@ -305,7 +323,8 @@ export default class ProjectFeaturesController extends Controller { >, res: Response, ): Promise { - const { strategyId } = req.params; + const { strategyId, environment, projectId } = req.params; + const userName = extractUsername(req); const { name, value } = req.body; const updatedStrategy = @@ -313,6 +332,9 @@ export default class ProjectFeaturesController extends Controller { strategyId, name, value, + userName, + projectId, + environment, ); res.status(200).json(updatedStrategy); } diff --git a/src/lib/services/feature-toggle-service-v2.ts b/src/lib/services/feature-toggle-service-v2.ts index c38ff518a7..85ebca88e1 100644 --- a/src/lib/services/feature-toggle-service-v2.ts +++ b/src/lib/services/feature-toggle-service-v2.ts @@ -10,11 +10,14 @@ import { FEATURE_ARCHIVED, FEATURE_CREATED, FEATURE_DELETED, + FEATURE_METADATA_UPDATED, FEATURE_REVIVED, FEATURE_STALE_OFF, FEATURE_STALE_ON, + FEATURE_STRATEGY_ADD, + FEATURE_STRATEGY_REMOVE, + FEATURE_STRATEGY_UPDATE, FEATURE_UPDATED, - FEATURE_METADATA_UPDATED, } from '../types/events'; import { GLOBAL_ENV } from '../types/environment'; import NotFoundError from '../error/notfound-error'; @@ -88,16 +91,11 @@ class FeatureToggleServiceV2 { this.featureEnvironmentStore = featureEnvironmentStore; } - /* - TODO after 4.1.0 release: - - add FEATURE_STRATEGY_ADD event - - add FEATURE_STRATEGY_REMOVE event - - add FEATURE_STRATEGY_UPDATE event - */ async createStrategy( strategyConfig: Omit, projectId: string, featureName: string, + userName: string, environment: string = GLOBAL_ENV, ): Promise { try { @@ -111,12 +109,20 @@ class FeatureToggleServiceV2 { featureName, environment, }); - return { + const data = { id: newFeatureStrategy.id, name: newFeatureStrategy.strategyName, constraints: newFeatureStrategy.constraints, parameters: newFeatureStrategy.parameters, }; + await this.eventStore.store({ + type: FEATURE_STRATEGY_ADD, + project: projectId, + createdBy: userName, + environment, + data, + }); + return data; } catch (e) { if (e.code === FOREIGN_KEY_VIOLATION) { throw new BadDataError( @@ -126,6 +132,12 @@ class FeatureToggleServiceV2 { throw e; } } + /* + TODO after 4.1.0 release: + - add FEATURE_STRATEGY_ADD event + - add FEATURE_STRATEGY_REMOVE event + - add FEATURE_STRATEGY_UPDATE event + */ /** * PUT /api/admin/projects/:projectId/features/:featureName/strategies/:strategyId ? @@ -139,6 +151,9 @@ class FeatureToggleServiceV2 { // TODO: verify projectId is not changed from URL! async updateStrategy( id: string, + environment: string, + project: string, + userName: string, updates: Partial, ): Promise { const existingStrategy = await this.featureStrategiesStore.get(id); @@ -147,12 +162,20 @@ class FeatureToggleServiceV2 { id, updates, ); - return { + const data = { id: strategy.id, name: strategy.strategyName, constraints: strategy.constraints || [], parameters: strategy.parameters, }; + await this.eventStore.store({ + type: FEATURE_STRATEGY_UPDATE, + project, + environment, + createdBy: userName, + data, + }); + return data; } throw new NotFoundError(`Could not find strategy with id ${id}`); } @@ -162,6 +185,9 @@ class FeatureToggleServiceV2 { id: string, name: string, value: string | number, + userName: string, + project: string, + environment: string, ): Promise { const existingStrategy = await this.featureStrategiesStore.get(id); if (existingStrategy.id === id) { @@ -170,12 +196,20 @@ class FeatureToggleServiceV2 { id, existingStrategy, ); - return { + const data = { id: strategy.id, name: strategy.strategyName, constraints: strategy.constraints || [], parameters: strategy.parameters, }; + await this.eventStore.store({ + type: FEATURE_STRATEGY_UPDATE, + project, + environment, + createdBy: userName, + data, + }); + return data; } throw new NotFoundError(`Could not find strategy with id ${id}`); } @@ -188,8 +222,22 @@ class FeatureToggleServiceV2 { * @param id * @param updates */ - async deleteStrategy(id: string): Promise { - return this.featureStrategiesStore.delete(id); + async deleteStrategy( + id: string, + userName: string, + project: string = 'default', + environment: string = GLOBAL_ENV, + ): Promise { + await this.featureStrategiesStore.delete(id); + await this.eventStore.store({ + type: FEATURE_STRATEGY_REMOVE, + project, + environment, + createdBy: userName, + data: { + id, + }, + }); } async getStrategiesForEnvironment( @@ -519,6 +567,7 @@ class FeatureToggleServiceV2 { data, tags, project: projectId, + environment, }); return feature; } diff --git a/src/lib/types/events.ts b/src/lib/types/events.ts index 0a0b1f8f99..5facf26d35 100644 --- a/src/lib/types/events.ts +++ b/src/lib/types/events.ts @@ -9,6 +9,9 @@ export const FEATURE_REVIVED = 'feature-revived'; export const FEATURE_IMPORT = 'feature-import'; export const FEATURE_TAGGED = 'feature-tagged'; export const FEATURE_TAG_IMPORT = 'feature-tag-import'; +export const FEATURE_STRATEGY_UPDATE = 'feature-strategy-update'; +export const FEATURE_STRATEGY_ADD = 'feature-strategy-add'; +export const FEATURE_STRATEGY_REMOVE = 'feature-strategy-remove'; export const DROP_FEATURE_TAGS = 'drop-feature-tags'; export const FEATURE_UNTAGGED = 'feature-untagged'; export const FEATURE_STALE_ON = 'feature-stale-on';