1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-10-18 20:09:08 +02:00
unleash.unleash/lib/services/feature-toggle-service.js

211 lines
6.5 KiB
JavaScript
Raw Normal View History

const { FEATURE_TAGGED, FEATURE_UNTAGGED } = require('../event-type');
const { featureSchema, nameSchema, querySchema } = require('./feature-schema');
const { tagSchema } = require('./tag-schema');
const NameExistsError = require('../error/name-exists-error');
const NotFoundError = require('../error/notfound-error');
const {
FEATURE_ARCHIVED,
FEATURE_CREATED,
FEATURE_REVIVED,
FEATURE_UPDATED,
TAG_CREATED,
} = require('../event-type');
class FeatureToggleService {
constructor({ featureToggleStore, tagStore, eventStore }, { getLogger }) {
this.featureToggleStore = featureToggleStore;
this.tagStore = tagStore;
this.eventStore = eventStore;
this.logger = getLogger('services/feature-toggle-service.js');
}
async getFeatures(query, fields) {
const preppedQuery = await this.prepQuery(query);
return this.featureToggleStore.getFeatures(preppedQuery, fields);
}
paramToArray(param) {
if (!param) {
return param;
}
return Array.isArray(param) ? param : [param];
}
async prepQuery({ tag, project, namePrefix }) {
if (!tag && !project && !namePrefix) {
return null;
}
const tagQuery = this.paramToArray(tag);
const projectQuery = this.paramToArray(project);
const query = await querySchema.validateAsync({
tag: tagQuery,
project: projectQuery,
namePrefix,
});
if (query.tag) {
query.tag = query.tag.map(q => q.split(':'));
}
return query;
}
async getArchivedFeatures() {
return this.featureToggleStore.getArchivedFeatures();
}
async addArchivedFeature(feature) {
await this.featureToggleStore.addArchivedFeature(feature);
}
async getFeature(name) {
return this.featureToggleStore.getFeature(name);
}
async createFeatureToggle(value, userName) {
await this.validateName(value);
const feature = await featureSchema.validateAsync(value);
await this.featureToggleStore.createFeature(feature);
await this.eventStore.store({
type: FEATURE_CREATED,
createdBy: userName,
data: feature,
});
}
async updateToggle(updatedFeature, userName) {
await this.featureToggleStore.getFeature(updatedFeature.name);
const value = await featureSchema.validateAsync(updatedFeature);
await this.featureToggleStore.updateFeature(value);
const tags =
(await this.featureToggleStore.getAllTagsForFeature(
updatedFeature.name,
)) || [];
await this.eventStore.store({
type: FEATURE_UPDATED,
createdBy: userName,
data: updatedFeature,
tags,
});
}
async archiveToggle(name, userName) {
await this.featureToggleStore.getFeature(name);
await this.featureToggleStore.archiveFeature(name);
const tags =
(await this.featureToggleStore.getAllTagsForFeature(name)) || [];
await this.eventStore.store({
type: FEATURE_ARCHIVED,
createdBy: userName,
data: { name },
tags,
});
}
async reviveToggle(name, userName) {
await this.featureToggleStore.reviveFeature({ name });
const tags =
(await this.featureToggleStore.getAllTagsForFeature(name)) || [];
await this.eventStore.store({
type: FEATURE_REVIVED,
createdBy: userName,
data: { name },
tags,
});
}
async toggle(featureName, userName) {
const feature = await this.featureToggleStore.getFeature(featureName);
const toggle = !feature.enabled;
return this.updateField(feature.name, 'enabled', toggle, userName);
}
/** Tag releated */
async listTags(featureName) {
return this.featureToggleStore.getAllTagsForFeature(featureName);
}
async addTag(featureName, tag, userName) {
await nameSchema.validateAsync({ name: featureName });
const validatedTag = await tagSchema.validateAsync(tag);
await this.createTagIfNeeded(validatedTag, userName);
await this.featureToggleStore.tagFeature(featureName, validatedTag);
await this.eventStore.store({
type: FEATURE_TAGGED,
createdBy: userName,
data: {
featureName,
tag: validatedTag,
},
});
return validatedTag;
}
async createTagIfNeeded(tag, userName) {
try {
await this.tagStore.getTag(tag.type, tag.value);
} catch (error) {
if (error instanceof NotFoundError) {
await this.tagStore.createTag(tag);
await this.eventStore.store({
type: TAG_CREATED,
createdBy: userName,
data: {
tag,
},
});
}
}
}
async removeTag(featureName, tag, userName) {
await this.featureToggleStore.untagFeature(featureName, tag);
await this.eventStore.store({
type: FEATURE_UNTAGGED,
createdBy: userName,
data: {
featureName,
tag,
},
});
}
/** Validations */
async validateName({ name }) {
await nameSchema.validateAsync({ name });
await this.validateUniqueFeatureName(name);
return name;
}
async validateUniqueFeatureName(name) {
let msg;
try {
const feature = await this.featureToggleStore.hasFeature(name);
msg = feature.archived
? 'An archived toggle with that name already exists'
: 'A toggle with that name already exists';
} catch (error) {
return;
}
throw new NameExistsError(msg);
}
async updateField(featureName, field, value, userName) {
const feature = await this.featureToggleStore.getFeature(featureName);
feature[field] = value;
await this.featureToggleStore.updateFeature(feature);
const tags =
(await this.featureToggleStore.getAllTagsForFeature(featureName)) ||
[];
await this.eventStore.store({
type: FEATURE_UPDATED,
createdBy: userName,
data: feature,
tags,
});
return feature;
}
}
module.exports = FeatureToggleService;