2021-07-07 10:46:50 +02:00
|
|
|
import { Request, Response } from 'express';
|
2021-09-13 10:23:57 +02:00
|
|
|
import { applyPatch, Operation } from 'fast-json-patch';
|
2021-07-07 10:46:50 +02:00
|
|
|
import Controller from '../../controller';
|
|
|
|
import { IUnleashConfig } from '../../../types/option';
|
|
|
|
import { IUnleashServices } from '../../../types/services';
|
|
|
|
import FeatureToggleServiceV2 from '../../../services/feature-toggle-service-v2';
|
|
|
|
import { Logger } from '../../../logger';
|
2021-10-25 11:14:38 +02:00
|
|
|
import { CREATE_FEATURE, UPDATE_FEATURE } from '../../../types/permissions';
|
2021-07-07 10:46:50 +02:00
|
|
|
import {
|
|
|
|
FeatureToggleDTO,
|
|
|
|
IConstraint,
|
|
|
|
IStrategyConfig,
|
|
|
|
} from '../../../types/model';
|
2021-09-14 19:58:48 +02:00
|
|
|
import { extractUsername } from '../../../util/extract-user';
|
|
|
|
import { IAuthRequest } from '../../unleash-types';
|
2021-07-07 10:46:50 +02:00
|
|
|
|
|
|
|
interface FeatureStrategyParams {
|
|
|
|
projectId: string;
|
|
|
|
featureName: string;
|
|
|
|
environment: string;
|
2021-09-17 15:11:17 +02:00
|
|
|
sortOrder?: number;
|
2021-07-07 10:46:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
interface FeatureParams extends ProjectParam {
|
|
|
|
featureName: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface ProjectParam {
|
|
|
|
projectId: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface StrategyIdParams extends FeatureStrategyParams {
|
|
|
|
strategyId: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface StrategyUpdateBody {
|
|
|
|
name?: string;
|
|
|
|
constraints?: IConstraint[];
|
|
|
|
parameters?: object;
|
|
|
|
}
|
|
|
|
|
2021-09-13 10:23:57 +02:00
|
|
|
const PATH = '/:projectId/features';
|
|
|
|
const PATH_FEATURE = `${PATH}/:featureName`;
|
2021-10-08 09:37:27 +02:00
|
|
|
const PATH_FEATURE_CLONE = `${PATH_FEATURE}/clone`;
|
2021-09-13 10:23:57 +02:00
|
|
|
const PATH_ENV = `${PATH_FEATURE}/environments/:environment`;
|
|
|
|
const PATH_STRATEGIES = `${PATH_ENV}/strategies`;
|
|
|
|
const PATH_STRATEGY = `${PATH_STRATEGIES}/:strategyId`;
|
2021-07-07 10:46:50 +02:00
|
|
|
|
|
|
|
type ProjectFeaturesServices = Pick<
|
2021-08-12 15:04:37 +02:00
|
|
|
IUnleashServices,
|
2021-07-07 10:46:50 +02:00
|
|
|
'featureToggleServiceV2' | 'projectHealthService'
|
|
|
|
>;
|
|
|
|
|
|
|
|
export default class ProjectFeaturesController extends Controller {
|
|
|
|
private featureService: FeatureToggleServiceV2;
|
|
|
|
|
|
|
|
private readonly logger: Logger;
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
config: IUnleashConfig,
|
2021-09-13 10:23:57 +02:00
|
|
|
{ featureToggleServiceV2 }: ProjectFeaturesServices,
|
2021-07-07 10:46:50 +02:00
|
|
|
) {
|
|
|
|
super(config);
|
|
|
|
this.featureService = featureToggleServiceV2;
|
|
|
|
this.logger = config.getLogger('/admin-api/project/features.ts');
|
|
|
|
|
2021-09-13 10:23:57 +02:00
|
|
|
this.get(`${PATH_ENV}`, this.getEnvironment);
|
|
|
|
this.post(`${PATH_ENV}/on`, this.toggleEnvironmentOn, UPDATE_FEATURE);
|
|
|
|
this.post(`${PATH_ENV}/off`, this.toggleEnvironmentOff, UPDATE_FEATURE);
|
2021-07-07 10:46:50 +02:00
|
|
|
|
2021-09-13 10:23:57 +02:00
|
|
|
this.get(`${PATH_STRATEGIES}`, this.getStrategies);
|
|
|
|
this.post(`${PATH_STRATEGIES}`, this.addStrategy, UPDATE_FEATURE);
|
|
|
|
|
|
|
|
this.get(`${PATH_STRATEGY}`, this.getStrategy);
|
|
|
|
this.put(`${PATH_STRATEGY}`, this.updateStrategy, UPDATE_FEATURE);
|
|
|
|
this.patch(`${PATH_STRATEGY}`, this.patchStrategy, UPDATE_FEATURE);
|
2021-10-25 11:14:38 +02:00
|
|
|
this.delete(`${PATH_STRATEGY}`, this.deleteStrategy, UPDATE_FEATURE);
|
2021-09-13 10:23:57 +02:00
|
|
|
|
|
|
|
this.get(PATH, this.getFeatures);
|
|
|
|
this.post(PATH, this.createFeature, CREATE_FEATURE);
|
|
|
|
|
2021-10-08 09:37:27 +02:00
|
|
|
this.post(PATH_FEATURE_CLONE, this.cloneFeature, CREATE_FEATURE);
|
|
|
|
|
2021-09-13 10:23:57 +02:00
|
|
|
this.get(PATH_FEATURE, this.getFeature);
|
|
|
|
this.put(PATH_FEATURE, this.updateFeature);
|
|
|
|
this.patch(PATH_FEATURE, this.patchFeature);
|
|
|
|
this.delete(PATH_FEATURE, this.archiveFeature);
|
2021-07-07 10:46:50 +02:00
|
|
|
}
|
|
|
|
|
2021-09-13 10:23:57 +02:00
|
|
|
async getFeatures(
|
2021-07-07 10:46:50 +02:00
|
|
|
req: Request<ProjectParam, any, any, any>,
|
|
|
|
res: Response,
|
|
|
|
): Promise<void> {
|
|
|
|
const { projectId } = req.params;
|
2021-09-13 10:23:57 +02:00
|
|
|
const features = await this.featureService.getFeatureOverview(
|
|
|
|
projectId,
|
|
|
|
);
|
2021-08-13 10:36:19 +02:00
|
|
|
res.json({ version: 1, features });
|
2021-07-07 10:46:50 +02:00
|
|
|
}
|
|
|
|
|
2021-10-08 09:37:27 +02:00
|
|
|
async cloneFeature(
|
|
|
|
req: IAuthRequest<
|
|
|
|
FeatureParams,
|
|
|
|
any,
|
|
|
|
{ name: string; replaceGroupId: boolean },
|
|
|
|
any
|
|
|
|
>,
|
|
|
|
res: Response,
|
|
|
|
): Promise<void> {
|
|
|
|
const { projectId, featureName } = req.params;
|
|
|
|
const { name, replaceGroupId } = req.body;
|
|
|
|
const userName = extractUsername(req);
|
|
|
|
const created = await this.featureService.cloneFeatureToggle(
|
|
|
|
featureName,
|
|
|
|
projectId,
|
|
|
|
name,
|
|
|
|
replaceGroupId,
|
|
|
|
userName,
|
|
|
|
);
|
|
|
|
res.status(201).json(created);
|
|
|
|
}
|
|
|
|
|
2021-09-13 10:23:57 +02:00
|
|
|
async createFeature(
|
2021-10-08 09:37:27 +02:00
|
|
|
req: IAuthRequest<FeatureParams, any, FeatureToggleDTO, any>,
|
2021-07-07 10:46:50 +02:00
|
|
|
res: Response,
|
|
|
|
): Promise<void> {
|
|
|
|
const { projectId } = req.params;
|
2021-08-13 10:36:19 +02:00
|
|
|
const userName = extractUsername(req);
|
|
|
|
const created = await this.featureService.createFeatureToggle(
|
|
|
|
projectId,
|
|
|
|
req.body,
|
|
|
|
userName,
|
|
|
|
);
|
|
|
|
res.status(201).json(created);
|
2021-07-07 10:46:50 +02:00
|
|
|
}
|
|
|
|
|
2021-09-13 10:23:57 +02:00
|
|
|
async getFeature(
|
|
|
|
req: Request<FeatureParams, any, any, any>,
|
|
|
|
res: Response,
|
|
|
|
): Promise<void> {
|
|
|
|
const { featureName } = req.params;
|
|
|
|
const feature = await this.featureService.getFeature(featureName);
|
|
|
|
res.status(200).json(feature);
|
|
|
|
}
|
|
|
|
|
|
|
|
async updateFeature(
|
2021-09-14 19:58:48 +02:00
|
|
|
req: IAuthRequest<ProjectParam, any, FeatureToggleDTO, any>,
|
2021-09-13 10:23:57 +02:00
|
|
|
res: Response,
|
|
|
|
): Promise<void> {
|
|
|
|
const { projectId } = req.params;
|
|
|
|
const data = req.body;
|
|
|
|
const userName = extractUsername(req);
|
|
|
|
const created = await this.featureService.updateFeatureToggle(
|
|
|
|
projectId,
|
|
|
|
data,
|
|
|
|
userName,
|
|
|
|
);
|
|
|
|
res.status(200).json(created);
|
|
|
|
}
|
|
|
|
|
|
|
|
async patchFeature(
|
2021-09-14 19:58:48 +02:00
|
|
|
req: IAuthRequest<
|
2021-09-13 10:23:57 +02:00
|
|
|
{ projectId: string; featureName: string },
|
|
|
|
any,
|
|
|
|
Operation[],
|
|
|
|
any
|
|
|
|
>,
|
|
|
|
res: Response,
|
|
|
|
): Promise<void> {
|
|
|
|
const { projectId, featureName } = req.params;
|
2021-10-11 11:27:20 +02:00
|
|
|
const updated = await this.featureService.patchFeature(
|
2021-09-13 10:23:57 +02:00
|
|
|
projectId,
|
2021-10-11 11:27:20 +02:00
|
|
|
featureName,
|
|
|
|
extractUsername(req),
|
|
|
|
req.body,
|
2021-09-13 10:23:57 +02:00
|
|
|
);
|
|
|
|
res.status(200).json(updated);
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: validate projectId
|
|
|
|
async archiveFeature(
|
2021-09-14 19:58:48 +02:00
|
|
|
req: IAuthRequest<
|
|
|
|
{ projectId: string; featureName: string },
|
|
|
|
any,
|
|
|
|
any,
|
|
|
|
any
|
|
|
|
>,
|
2021-09-13 10:23:57 +02:00
|
|
|
res: Response,
|
|
|
|
): Promise<void> {
|
|
|
|
const { featureName } = req.params;
|
|
|
|
const userName = extractUsername(req);
|
|
|
|
await this.featureService.archiveToggle(featureName, userName);
|
|
|
|
res.status(202).send();
|
|
|
|
}
|
|
|
|
|
2021-07-07 10:46:50 +02:00
|
|
|
async getEnvironment(
|
|
|
|
req: Request<FeatureStrategyParams, any, any, any>,
|
|
|
|
res: Response,
|
|
|
|
): Promise<void> {
|
|
|
|
const { environment, featureName, projectId } = req.params;
|
2021-08-13 10:36:19 +02:00
|
|
|
const environmentInfo = await this.featureService.getEnvironmentInfo(
|
|
|
|
projectId,
|
|
|
|
environment,
|
|
|
|
featureName,
|
|
|
|
);
|
|
|
|
res.status(200).json(environmentInfo);
|
2021-07-07 10:46:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async toggleEnvironmentOn(
|
2021-09-14 19:58:48 +02:00
|
|
|
req: IAuthRequest<FeatureStrategyParams, any, any, any>,
|
2021-07-07 10:46:50 +02:00
|
|
|
res: Response,
|
|
|
|
): Promise<void> {
|
2021-09-13 10:23:57 +02:00
|
|
|
const { featureName, environment, projectId } = req.params;
|
2021-08-13 10:36:19 +02:00
|
|
|
await this.featureService.updateEnabled(
|
2021-09-13 10:23:57 +02:00
|
|
|
projectId,
|
2021-08-13 10:36:19 +02:00
|
|
|
featureName,
|
|
|
|
environment,
|
|
|
|
true,
|
|
|
|
extractUsername(req),
|
|
|
|
);
|
|
|
|
res.status(200).end();
|
2021-07-07 10:46:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async toggleEnvironmentOff(
|
2021-09-14 19:58:48 +02:00
|
|
|
req: IAuthRequest<FeatureStrategyParams, any, any, any>,
|
2021-07-07 10:46:50 +02:00
|
|
|
res: Response,
|
|
|
|
): Promise<void> {
|
2021-09-13 10:23:57 +02:00
|
|
|
const { featureName, environment, projectId } = req.params;
|
2021-08-13 10:36:19 +02:00
|
|
|
await this.featureService.updateEnabled(
|
2021-09-13 10:23:57 +02:00
|
|
|
projectId,
|
2021-08-13 10:36:19 +02:00
|
|
|
featureName,
|
|
|
|
environment,
|
|
|
|
false,
|
|
|
|
extractUsername(req),
|
|
|
|
);
|
|
|
|
res.status(200).end();
|
2021-07-07 10:46:50 +02:00
|
|
|
}
|
|
|
|
|
2021-09-13 10:23:57 +02:00
|
|
|
async addStrategy(
|
2021-09-20 12:13:38 +02:00
|
|
|
req: IAuthRequest<FeatureStrategyParams, any, IStrategyConfig, any>,
|
2021-07-07 10:46:50 +02:00
|
|
|
res: Response,
|
|
|
|
): Promise<void> {
|
|
|
|
const { projectId, featureName, environment } = req.params;
|
2021-09-20 12:13:38 +02:00
|
|
|
const userName = extractUsername(req);
|
2021-08-13 10:36:19 +02:00
|
|
|
const featureStrategy = await this.featureService.createStrategy(
|
|
|
|
req.body,
|
|
|
|
projectId,
|
|
|
|
featureName,
|
2021-09-20 12:13:38 +02:00
|
|
|
userName,
|
2021-08-13 10:36:19 +02:00
|
|
|
environment,
|
|
|
|
);
|
|
|
|
res.status(200).json(featureStrategy);
|
2021-07-07 10:46:50 +02:00
|
|
|
}
|
|
|
|
|
2021-09-13 10:23:57 +02:00
|
|
|
async getStrategies(
|
2021-07-07 10:46:50 +02:00
|
|
|
req: Request<FeatureStrategyParams, any, any, any>,
|
|
|
|
res: Response,
|
|
|
|
): Promise<void> {
|
|
|
|
const { projectId, featureName, environment } = req.params;
|
2021-08-13 10:36:19 +02:00
|
|
|
const featureStrategies =
|
|
|
|
await this.featureService.getStrategiesForEnvironment(
|
|
|
|
projectId,
|
|
|
|
featureName,
|
|
|
|
environment,
|
|
|
|
);
|
|
|
|
res.status(200).json(featureStrategies);
|
2021-07-07 10:46:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async updateStrategy(
|
2021-09-20 12:13:38 +02:00
|
|
|
req: IAuthRequest<StrategyIdParams, any, StrategyUpdateBody, any>,
|
2021-07-07 10:46:50 +02:00
|
|
|
res: Response,
|
|
|
|
): Promise<void> {
|
2021-09-20 12:13:38 +02:00
|
|
|
const { strategyId, environment, projectId } = req.params;
|
|
|
|
const userName = extractUsername(req);
|
2021-08-13 10:36:19 +02:00
|
|
|
const updatedStrategy = await this.featureService.updateStrategy(
|
|
|
|
strategyId,
|
2021-09-20 12:13:38 +02:00
|
|
|
environment,
|
|
|
|
projectId,
|
|
|
|
userName,
|
2021-08-13 10:36:19 +02:00
|
|
|
req.body,
|
|
|
|
);
|
|
|
|
res.status(200).json(updatedStrategy);
|
2021-07-07 10:46:50 +02:00
|
|
|
}
|
|
|
|
|
2021-09-13 10:23:57 +02:00
|
|
|
async patchStrategy(
|
2021-09-20 12:13:38 +02:00
|
|
|
req: IAuthRequest<StrategyIdParams, any, Operation[], any>,
|
2021-09-13 10:23:57 +02:00
|
|
|
res: Response,
|
|
|
|
): Promise<void> {
|
2021-09-20 12:13:38 +02:00
|
|
|
const { strategyId, projectId, environment } = req.params;
|
|
|
|
const userName = extractUsername(req);
|
2021-09-13 10:23:57 +02:00
|
|
|
const patch = req.body;
|
|
|
|
const strategy = await this.featureService.getStrategy(strategyId);
|
|
|
|
const { newDocument } = applyPatch(strategy, patch);
|
|
|
|
const updatedStrategy = await this.featureService.updateStrategy(
|
|
|
|
strategyId,
|
2021-09-20 12:13:38 +02:00
|
|
|
environment,
|
|
|
|
projectId,
|
|
|
|
userName,
|
2021-09-13 10:23:57 +02:00
|
|
|
newDocument,
|
|
|
|
);
|
|
|
|
res.status(200).json(updatedStrategy);
|
|
|
|
}
|
|
|
|
|
2021-07-07 10:46:50 +02:00
|
|
|
async getStrategy(
|
2021-09-20 12:13:38 +02:00
|
|
|
req: IAuthRequest<StrategyIdParams, any, any, any>,
|
2021-07-07 10:46:50 +02:00
|
|
|
res: Response,
|
|
|
|
): Promise<void> {
|
|
|
|
this.logger.info('Getting strategy');
|
|
|
|
const { strategyId } = req.params;
|
|
|
|
this.logger.info(strategyId);
|
2021-08-13 10:36:19 +02:00
|
|
|
const strategy = await this.featureService.getStrategy(strategyId);
|
|
|
|
res.status(200).json(strategy);
|
2021-07-07 10:46:50 +02:00
|
|
|
}
|
2021-09-13 10:23:57 +02:00
|
|
|
|
|
|
|
async deleteStrategy(
|
2021-09-20 12:13:38 +02:00
|
|
|
req: IAuthRequest<StrategyIdParams, any, any, any>,
|
2021-09-13 10:23:57 +02:00
|
|
|
res: Response,
|
|
|
|
): Promise<void> {
|
|
|
|
this.logger.info('Deleting strategy');
|
2021-10-06 09:25:34 +02:00
|
|
|
const { environment, projectId, featureName } = req.params;
|
2021-09-20 12:13:38 +02:00
|
|
|
const userName = extractUsername(req);
|
2021-09-13 10:23:57 +02:00
|
|
|
const { strategyId } = req.params;
|
|
|
|
this.logger.info(strategyId);
|
2021-09-20 12:13:38 +02:00
|
|
|
const strategy = await this.featureService.deleteStrategy(
|
|
|
|
strategyId,
|
2021-10-06 09:25:34 +02:00
|
|
|
featureName,
|
2021-09-20 12:13:38 +02:00
|
|
|
userName,
|
|
|
|
projectId,
|
|
|
|
environment,
|
|
|
|
);
|
2021-09-13 10:23:57 +02:00
|
|
|
res.status(200).json(strategy);
|
|
|
|
}
|
|
|
|
|
|
|
|
async updateStrategyParameter(
|
2021-09-20 12:13:38 +02:00
|
|
|
req: IAuthRequest<
|
2021-09-13 10:23:57 +02:00
|
|
|
StrategyIdParams,
|
|
|
|
any,
|
|
|
|
{ name: string; value: string | number },
|
|
|
|
any
|
|
|
|
>,
|
|
|
|
res: Response,
|
|
|
|
): Promise<void> {
|
2021-09-20 12:13:38 +02:00
|
|
|
const { strategyId, environment, projectId } = req.params;
|
|
|
|
const userName = extractUsername(req);
|
2021-09-13 10:23:57 +02:00
|
|
|
const { name, value } = req.body;
|
|
|
|
|
|
|
|
const updatedStrategy =
|
|
|
|
await this.featureService.updateStrategyParameter(
|
|
|
|
strategyId,
|
|
|
|
name,
|
|
|
|
value,
|
2021-09-20 12:13:38 +02:00
|
|
|
userName,
|
|
|
|
projectId,
|
|
|
|
environment,
|
2021-09-13 10:23:57 +02:00
|
|
|
);
|
|
|
|
res.status(200).json(updatedStrategy);
|
|
|
|
}
|
|
|
|
|
|
|
|
async getStrategyParameters(
|
|
|
|
req: Request<StrategyIdParams, any, any, any>,
|
|
|
|
res: Response,
|
|
|
|
): Promise<void> {
|
|
|
|
this.logger.info('Getting strategy parameters');
|
|
|
|
const { strategyId } = req.params;
|
|
|
|
const strategy = await this.featureService.getStrategy(strategyId);
|
|
|
|
res.status(200).json(strategy.parameters);
|
|
|
|
}
|
2021-07-07 10:46:50 +02:00
|
|
|
}
|