2020-11-03 14:56:07 +01:00
|
|
|
const { stateSchema } = require('./state-schema');
|
|
|
|
const {
|
|
|
|
FEATURE_IMPORT,
|
|
|
|
DROP_FEATURES,
|
|
|
|
STRATEGY_IMPORT,
|
|
|
|
DROP_STRATEGIES,
|
2021-03-12 11:08:10 +01:00
|
|
|
TAG_IMPORT,
|
|
|
|
DROP_TAGS,
|
|
|
|
FEATURE_TAG_IMPORT,
|
|
|
|
DROP_FEATURE_TAGS,
|
|
|
|
TAG_TYPE_IMPORT,
|
|
|
|
DROP_TAG_TYPES,
|
|
|
|
PROJECT_IMPORT,
|
|
|
|
DROP_PROJECTS,
|
2020-11-03 14:56:07 +01:00
|
|
|
} = require('../event-type');
|
|
|
|
|
|
|
|
const {
|
|
|
|
readFile,
|
|
|
|
parseFile,
|
2021-03-12 11:08:10 +01:00
|
|
|
filterExisting,
|
2020-11-03 14:56:07 +01:00
|
|
|
filterEqual,
|
|
|
|
} = require('./state-util');
|
|
|
|
|
|
|
|
class StateService {
|
2020-09-28 21:54:44 +02:00
|
|
|
constructor(stores, { getLogger }) {
|
2020-11-03 14:56:07 +01:00
|
|
|
this.eventStore = stores.eventStore;
|
|
|
|
this.toggleStore = stores.featureToggleStore;
|
|
|
|
this.strategyStore = stores.strategyStore;
|
2021-03-12 11:08:10 +01:00
|
|
|
this.tagStore = stores.tagStore;
|
|
|
|
this.tagTypeStore = stores.tagTypeStore;
|
|
|
|
this.projectStore = stores.projectStore;
|
2020-11-03 14:56:07 +01:00
|
|
|
this.logger = getLogger('services/state-service.js');
|
|
|
|
}
|
|
|
|
|
|
|
|
importFile({ file, dropBeforeImport, userName, keepExisting }) {
|
|
|
|
return readFile(file)
|
|
|
|
.then(data => parseFile(file, data))
|
|
|
|
.then(data =>
|
|
|
|
this.import({ data, userName, dropBeforeImport, keepExisting }),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
async import({ data, userName, dropBeforeImport, keepExisting }) {
|
|
|
|
const importData = await stateSchema.validateAsync(data);
|
|
|
|
|
|
|
|
if (importData.features) {
|
|
|
|
await this.importFeatures({
|
|
|
|
features: data.features,
|
|
|
|
userName,
|
|
|
|
dropBeforeImport,
|
|
|
|
keepExisting,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (importData.strategies) {
|
|
|
|
await this.importStrategies({
|
|
|
|
strategies: data.strategies,
|
|
|
|
userName,
|
|
|
|
dropBeforeImport,
|
|
|
|
keepExisting,
|
|
|
|
});
|
|
|
|
}
|
2021-03-12 11:08:10 +01:00
|
|
|
|
|
|
|
if (importData.tagTypes && importData.tags) {
|
|
|
|
await this.importTagData({
|
|
|
|
tagTypes: data.tagTypes,
|
|
|
|
tags: data.tags,
|
|
|
|
featureTags: data.featureTags || [],
|
|
|
|
userName,
|
|
|
|
dropBeforeImport,
|
|
|
|
keepExisting,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (importData.projects) {
|
|
|
|
await this.importProjects({
|
|
|
|
projects: data.projects,
|
|
|
|
userName,
|
|
|
|
dropBeforeImport,
|
|
|
|
keepExisting,
|
|
|
|
});
|
|
|
|
}
|
2020-11-03 14:56:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async importFeatures({
|
|
|
|
features,
|
|
|
|
userName,
|
|
|
|
dropBeforeImport,
|
|
|
|
keepExisting,
|
|
|
|
}) {
|
|
|
|
this.logger.info(`Importing ${features.length} feature toggles`);
|
|
|
|
const oldToggles = dropBeforeImport
|
|
|
|
? []
|
|
|
|
: await this.toggleStore.getFeatures();
|
|
|
|
|
|
|
|
if (dropBeforeImport) {
|
2021-02-23 06:20:10 +01:00
|
|
|
this.logger.info('Dropping existing feature toggles');
|
2021-01-18 12:32:19 +01:00
|
|
|
await this.toggleStore.dropFeatures();
|
2020-11-03 14:56:07 +01:00
|
|
|
await this.eventStore.store({
|
|
|
|
type: DROP_FEATURES,
|
|
|
|
createdBy: userName,
|
|
|
|
data: { name: 'all-features' },
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
await Promise.all(
|
|
|
|
features
|
2021-03-12 11:08:10 +01:00
|
|
|
.filter(filterExisting(keepExisting, oldToggles))
|
2020-11-03 14:56:07 +01:00
|
|
|
.filter(filterEqual(oldToggles))
|
|
|
|
.map(feature =>
|
2021-01-18 12:32:19 +01:00
|
|
|
this.toggleStore.importFeature(feature).then(() =>
|
|
|
|
this.eventStore.store({
|
|
|
|
type: FEATURE_IMPORT,
|
|
|
|
createdBy: userName,
|
|
|
|
data: feature,
|
|
|
|
}),
|
|
|
|
),
|
2020-11-03 14:56:07 +01:00
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
async importStrategies({
|
|
|
|
strategies,
|
|
|
|
userName,
|
|
|
|
dropBeforeImport,
|
|
|
|
keepExisting,
|
|
|
|
}) {
|
|
|
|
this.logger.info(`Importing ${strategies.length} strategies`);
|
|
|
|
const oldStrategies = dropBeforeImport
|
|
|
|
? []
|
|
|
|
: await this.strategyStore.getStrategies();
|
|
|
|
|
|
|
|
if (dropBeforeImport) {
|
2021-02-23 06:20:10 +01:00
|
|
|
this.logger.info('Dropping existing strategies');
|
2021-01-18 12:32:19 +01:00
|
|
|
await this.strategyStore.dropStrategies();
|
2020-11-03 14:56:07 +01:00
|
|
|
await this.eventStore.store({
|
|
|
|
type: DROP_STRATEGIES,
|
|
|
|
createdBy: userName,
|
|
|
|
data: { name: 'all-strategies' },
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
await Promise.all(
|
|
|
|
strategies
|
2021-03-12 11:08:10 +01:00
|
|
|
.filter(filterExisting(keepExisting, oldStrategies))
|
2020-11-03 14:56:07 +01:00
|
|
|
.filter(filterEqual(oldStrategies))
|
|
|
|
.map(strategy =>
|
2021-01-18 12:32:19 +01:00
|
|
|
this.strategyStore.importStrategy(strategy).then(() => {
|
|
|
|
this.eventStore.store({
|
|
|
|
type: STRATEGY_IMPORT,
|
|
|
|
createdBy: userName,
|
|
|
|
data: strategy,
|
|
|
|
});
|
2020-11-03 14:56:07 +01:00
|
|
|
}),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-03-12 11:08:10 +01:00
|
|
|
async importProjects({
|
|
|
|
projects,
|
|
|
|
userName,
|
|
|
|
dropBeforeImport,
|
|
|
|
keepExisting,
|
|
|
|
}) {
|
|
|
|
this.logger.info(`Import ${projects.length} projects`);
|
|
|
|
const oldProjects = dropBeforeImport
|
|
|
|
? []
|
|
|
|
: await this.projectStore.getAll();
|
|
|
|
if (dropBeforeImport) {
|
|
|
|
this.logger.info('Dropping existing projects');
|
|
|
|
await this.projectStore.dropProjects();
|
|
|
|
await this.eventStore.store({
|
|
|
|
type: DROP_PROJECTS,
|
|
|
|
createdBy: userName,
|
|
|
|
data: { name: 'all-projects' },
|
|
|
|
});
|
|
|
|
}
|
|
|
|
const projectsToImport = projects.filter(project =>
|
|
|
|
keepExisting
|
|
|
|
? !oldProjects.some(old => old.id === project.id)
|
|
|
|
: true,
|
|
|
|
);
|
|
|
|
if (projectsToImport.length > 0) {
|
|
|
|
const importedProjects = await this.projectStore.importProjects(
|
|
|
|
projectsToImport,
|
|
|
|
);
|
2021-04-16 15:29:23 +02:00
|
|
|
const importedProjectEvents = importedProjects.map(project => ({
|
|
|
|
type: PROJECT_IMPORT,
|
|
|
|
createdBy: userName,
|
|
|
|
data: project,
|
|
|
|
}));
|
2021-03-12 11:08:10 +01:00
|
|
|
await this.eventStore.batchStore(importedProjectEvents);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async importTagData({
|
|
|
|
tagTypes,
|
|
|
|
tags,
|
|
|
|
featureTags,
|
|
|
|
userName,
|
|
|
|
dropBeforeImport,
|
|
|
|
keepExisting,
|
|
|
|
}) {
|
|
|
|
this.logger.info(
|
|
|
|
`Importing ${tagTypes.length} tagtypes, ${tags.length} tags and ${featureTags.length} feature tags`,
|
|
|
|
);
|
|
|
|
const oldTagTypes = dropBeforeImport
|
|
|
|
? []
|
|
|
|
: await this.tagTypeStore.getAll();
|
|
|
|
const oldTags = dropBeforeImport ? [] : await this.tagStore.getAll();
|
|
|
|
const oldFeatureTags = dropBeforeImport
|
|
|
|
? []
|
|
|
|
: await this.toggleStore.getAllFeatureTags();
|
|
|
|
if (dropBeforeImport) {
|
|
|
|
this.logger.info(
|
|
|
|
'Dropping all existing featuretags, tags and tagtypes',
|
|
|
|
);
|
|
|
|
await this.toggleStore.dropFeatureTags();
|
|
|
|
await this.tagStore.dropTags();
|
|
|
|
await this.tagTypeStore.dropTagTypes();
|
|
|
|
await this.eventStore.batchStore([
|
|
|
|
{
|
|
|
|
type: DROP_FEATURE_TAGS,
|
|
|
|
createdBy: userName,
|
|
|
|
data: { name: 'all-feature-tags' },
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: DROP_TAGS,
|
|
|
|
createdBy: userName,
|
|
|
|
data: { name: 'all-tags' },
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: DROP_TAG_TYPES,
|
|
|
|
createdBy: userName,
|
|
|
|
data: { name: 'all-tag-types' },
|
|
|
|
},
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
await this.importTagTypes(
|
|
|
|
tagTypes,
|
|
|
|
keepExisting,
|
|
|
|
oldTagTypes,
|
|
|
|
userName,
|
|
|
|
);
|
|
|
|
await this.importTags(tags, keepExisting, oldTags, userName);
|
|
|
|
await this.importFeatureTags(
|
|
|
|
featureTags,
|
|
|
|
keepExisting,
|
|
|
|
oldFeatureTags,
|
|
|
|
userName,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
compareFeatureTags = (old, tag) =>
|
|
|
|
old.featureName === tag.featureName &&
|
|
|
|
old.tagValue === tag.tagValue &&
|
|
|
|
old.tagType === tag.tagType;
|
|
|
|
|
|
|
|
async importFeatureTags(
|
|
|
|
featureTags,
|
|
|
|
keepExisting,
|
|
|
|
oldFeatureTags,
|
|
|
|
userName,
|
|
|
|
) {
|
|
|
|
const featureTagsToInsert = featureTags.filter(tag =>
|
|
|
|
keepExisting
|
|
|
|
? !oldFeatureTags.some(old => this.compareFeatureTags(old, tag))
|
|
|
|
: true,
|
|
|
|
);
|
|
|
|
if (featureTagsToInsert.length > 0) {
|
|
|
|
const importedFeatureTags = await this.toggleStore.importFeatureTags(
|
|
|
|
featureTagsToInsert,
|
|
|
|
);
|
2021-04-16 15:29:23 +02:00
|
|
|
const importedFeatureTagEvents = importedFeatureTags.map(tag => ({
|
|
|
|
type: FEATURE_TAG_IMPORT,
|
|
|
|
createdBy: userName,
|
|
|
|
data: tag,
|
|
|
|
}));
|
2021-03-12 11:08:10 +01:00
|
|
|
await this.eventStore.batchStore(importedFeatureTagEvents);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
compareTags = (old, tag) =>
|
|
|
|
old.type === tag.type && old.value === tag.value;
|
|
|
|
|
|
|
|
async importTags(tags, keepExisting, oldTags, userName) {
|
|
|
|
const tagsToInsert = tags.filter(tag =>
|
|
|
|
keepExisting
|
|
|
|
? !oldTags.some(old => this.compareTags(old, tag))
|
|
|
|
: true,
|
|
|
|
);
|
|
|
|
if (tagsToInsert.length > 0) {
|
|
|
|
const importedTags = await this.tagStore.bulkImport(tagsToInsert);
|
2021-04-16 15:29:23 +02:00
|
|
|
const importedTagEvents = importedTags.map(tag => ({
|
|
|
|
type: TAG_IMPORT,
|
|
|
|
createdBy: userName,
|
|
|
|
data: tag,
|
|
|
|
}));
|
2021-03-12 11:08:10 +01:00
|
|
|
await this.eventStore.batchStore(importedTagEvents);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async importTagTypes(tagTypes, keepExisting, oldTagTypes = [], userName) {
|
|
|
|
const tagTypesToInsert = tagTypes.filter(tagType =>
|
|
|
|
keepExisting
|
|
|
|
? !oldTagTypes.some(t => t.name === tagType.name)
|
|
|
|
: true,
|
|
|
|
);
|
|
|
|
if (tagTypesToInsert.length > 0) {
|
|
|
|
const importedTagTypes = await this.tagTypeStore.bulkImport(
|
|
|
|
tagTypesToInsert,
|
|
|
|
);
|
2021-04-16 15:29:23 +02:00
|
|
|
const importedTagTypeEvents = importedTagTypes.map(tagType => ({
|
|
|
|
type: TAG_TYPE_IMPORT,
|
|
|
|
createdBy: userName,
|
|
|
|
data: tagType,
|
|
|
|
}));
|
2021-03-12 11:08:10 +01:00
|
|
|
await this.eventStore.batchStore(importedTagTypeEvents);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async export({
|
|
|
|
includeFeatureToggles = true,
|
|
|
|
includeStrategies = true,
|
|
|
|
includeProjects = true,
|
|
|
|
includeTags = true,
|
|
|
|
}) {
|
2020-11-03 14:56:07 +01:00
|
|
|
return Promise.all([
|
|
|
|
includeFeatureToggles
|
|
|
|
? this.toggleStore.getFeatures()
|
|
|
|
: Promise.resolve(),
|
|
|
|
includeStrategies
|
|
|
|
? this.strategyStore.getEditableStrategies()
|
|
|
|
: Promise.resolve(),
|
2021-03-12 11:08:10 +01:00
|
|
|
this.projectStore && includeProjects
|
|
|
|
? this.projectStore.getAll()
|
|
|
|
: Promise.resolve(),
|
|
|
|
includeTags ? this.tagTypeStore.getAll() : Promise.resolve(),
|
|
|
|
includeTags ? this.tagStore.getAll() : Promise.resolve(),
|
|
|
|
includeTags
|
|
|
|
? this.toggleStore.getAllFeatureTags()
|
|
|
|
: Promise.resolve(),
|
|
|
|
]).then(
|
|
|
|
([
|
|
|
|
features,
|
|
|
|
strategies,
|
|
|
|
projects,
|
|
|
|
tagTypes,
|
|
|
|
tags,
|
|
|
|
featureTags,
|
|
|
|
]) => ({
|
|
|
|
version: 1,
|
|
|
|
features,
|
|
|
|
strategies,
|
|
|
|
projects,
|
|
|
|
tagTypes,
|
|
|
|
tags,
|
|
|
|
featureTags,
|
|
|
|
}),
|
|
|
|
);
|
2020-11-03 14:56:07 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = StateService;
|