mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-16 00:06:40 +01:00
cdfba8f7b1
When an application updates metrics for a toggle we now stores the timestamp on the toggle when it was last seen used by an application. This will make it much easier to detect toggles not in use anymore. closes #642
194 lines
5.9 KiB
JavaScript
194 lines
5.9 KiB
JavaScript
const Controller = require('../controller');
|
|
|
|
const {
|
|
FEATURE_CREATED,
|
|
FEATURE_UPDATED,
|
|
FEATURE_ARCHIVED,
|
|
} = require('../../event-type');
|
|
const NameExistsError = require('../../error/name-exists-error');
|
|
const { handleErrors } = require('./util');
|
|
const extractUser = require('../../extract-user');
|
|
const {
|
|
UPDATE_FEATURE,
|
|
DELETE_FEATURE,
|
|
CREATE_FEATURE,
|
|
} = require('../../permissions');
|
|
const { featureShema, nameSchema } = require('./feature-schema');
|
|
|
|
const version = 1;
|
|
|
|
class FeatureController extends Controller {
|
|
constructor(config) {
|
|
super(config);
|
|
this.featureToggleStore = config.stores.featureToggleStore;
|
|
this.eventStore = config.stores.eventStore;
|
|
this.logger = config.getLogger('/admin-api/feature.js');
|
|
|
|
this.get('/', this.getAllToggles);
|
|
this.post('/', this.createToggle, CREATE_FEATURE);
|
|
this.get('/:featureName', this.getToggle);
|
|
this.put('/:featureName', this.updateToggle, UPDATE_FEATURE);
|
|
this.delete('/:featureName', this.deleteToggle, DELETE_FEATURE);
|
|
this.post('/validate', this.validate);
|
|
this.post('/:featureName/toggle', this.toggle, UPDATE_FEATURE);
|
|
this.post('/:featureName/toggle/on', this.toggleOn, UPDATE_FEATURE);
|
|
this.post('/:featureName/toggle/off', this.toggleOff, UPDATE_FEATURE);
|
|
this.post('/:featureName/stale/on', this.staleOn, UPDATE_FEATURE);
|
|
this.post('/:featureName/stale/off', this.staleOff, UPDATE_FEATURE);
|
|
}
|
|
|
|
async getAllToggles(req, res) {
|
|
const features = await this.featureToggleStore.getFeatures();
|
|
res.json({ version, features });
|
|
}
|
|
|
|
async getToggle(req, res) {
|
|
try {
|
|
const name = req.params.featureName;
|
|
const feature = await this.featureToggleStore.getFeature(name);
|
|
res.json(feature).end();
|
|
} catch (err) {
|
|
res.status(404).json({ error: 'Could not find feature' });
|
|
}
|
|
}
|
|
|
|
async validate(req, res) {
|
|
const { name } = req.body;
|
|
|
|
try {
|
|
await nameSchema.validateAsync({ name });
|
|
await this.validateUniqueName(name);
|
|
res.status(201).end();
|
|
} catch (error) {
|
|
handleErrors(res, this.logger, error);
|
|
}
|
|
}
|
|
|
|
// TODO: cleanup this validation
|
|
async validateUniqueName(name) {
|
|
let msg;
|
|
try {
|
|
const definition = await this.featureToggleStore.hasFeature(name);
|
|
msg = definition.archived
|
|
? 'An archived toggle with that name already exist'
|
|
: 'A toggle with that name already exist';
|
|
} catch (error) {
|
|
// No conflict, everything ok!
|
|
return;
|
|
}
|
|
|
|
// Interntional throw here!
|
|
throw new NameExistsError(msg);
|
|
}
|
|
|
|
async createToggle(req, res) {
|
|
const toggleName = req.body.name;
|
|
const userName = extractUser(req);
|
|
|
|
try {
|
|
await this.validateUniqueName(toggleName);
|
|
const value = await featureShema.validateAsync(req.body);
|
|
|
|
await this.eventStore.store({
|
|
type: FEATURE_CREATED,
|
|
createdBy: userName,
|
|
data: value,
|
|
});
|
|
res.status(201).end();
|
|
} catch (error) {
|
|
handleErrors(res, this.logger, error);
|
|
}
|
|
}
|
|
|
|
async updateToggle(req, res) {
|
|
const { featureName } = req.params;
|
|
const userName = extractUser(req);
|
|
const updatedFeature = req.body;
|
|
|
|
updatedFeature.name = featureName;
|
|
|
|
try {
|
|
await this.featureToggleStore.getFeature(featureName);
|
|
const value = await featureShema.validateAsync(updatedFeature);
|
|
await this.eventStore.store({
|
|
type: FEATURE_UPDATED,
|
|
createdBy: userName,
|
|
data: value,
|
|
});
|
|
res.status(200).end();
|
|
} catch (error) {
|
|
handleErrors(res, this.logger, error);
|
|
}
|
|
}
|
|
|
|
// Kept to keep backward compatability
|
|
async toggle(req, res) {
|
|
try {
|
|
const name = req.params.featureName;
|
|
const feature = await this.featureToggleStore.getFeature(name);
|
|
const enabled = !feature.enabled;
|
|
this._updateField('enabled', enabled, req, res);
|
|
} catch (error) {
|
|
handleErrors(res, this.logger, error);
|
|
}
|
|
}
|
|
|
|
async toggleOn(req, res) {
|
|
this._updateField('enabled', true, req, res);
|
|
}
|
|
|
|
async toggleOff(req, res) {
|
|
this._updateField('enabled', false, req, res);
|
|
}
|
|
|
|
async staleOn(req, res) {
|
|
this._updateField('stale', true, req, res);
|
|
}
|
|
|
|
async staleOff(req, res) {
|
|
this._updateField('stale', false, req, res);
|
|
}
|
|
|
|
async _updateField(field, value, req, res) {
|
|
const { featureName } = req.params;
|
|
const userName = extractUser(req);
|
|
|
|
try {
|
|
const feature = await this.featureToggleStore.getFeature(
|
|
featureName,
|
|
);
|
|
feature[field] = value;
|
|
const validFeature = await featureShema.validateAsync(feature);
|
|
await this.eventStore.store({
|
|
type: FEATURE_UPDATED,
|
|
createdBy: userName,
|
|
data: validFeature,
|
|
});
|
|
res.json(feature).end();
|
|
} catch (error) {
|
|
handleErrors(res, this.logger, error);
|
|
}
|
|
}
|
|
|
|
async deleteToggle(req, res) {
|
|
const { featureName } = req.params;
|
|
const userName = extractUser(req);
|
|
|
|
try {
|
|
await this.featureToggleStore.getFeature(featureName);
|
|
await this.eventStore.store({
|
|
type: FEATURE_ARCHIVED,
|
|
createdBy: userName,
|
|
data: {
|
|
name: featureName,
|
|
},
|
|
});
|
|
res.status(200).end();
|
|
} catch (error) {
|
|
handleErrors(res, this.logger, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = FeatureController;
|