2021-07-07 10:46:50 +02:00
|
|
|
import { stateSchema } from './state-schema';
|
|
|
|
import {
|
2021-09-24 13:55:00 +02:00
|
|
|
DROP_ENVIRONMENTS,
|
2021-07-07 10:46:50 +02:00
|
|
|
DROP_FEATURE_TAGS,
|
2020-11-03 14:56:07 +01:00
|
|
|
DROP_FEATURES,
|
2021-07-07 10:46:50 +02:00
|
|
|
DROP_PROJECTS,
|
2020-11-03 14:56:07 +01:00
|
|
|
DROP_STRATEGIES,
|
2021-07-07 10:46:50 +02:00
|
|
|
DROP_TAG_TYPES,
|
2021-03-12 11:08:10 +01:00
|
|
|
DROP_TAGS,
|
2021-09-24 13:55:00 +02:00
|
|
|
ENVIRONMENT_IMPORT,
|
2021-07-07 10:46:50 +02:00
|
|
|
FEATURE_IMPORT,
|
2021-03-12 11:08:10 +01:00
|
|
|
FEATURE_TAG_IMPORT,
|
|
|
|
PROJECT_IMPORT,
|
2021-07-07 10:46:50 +02:00
|
|
|
STRATEGY_IMPORT,
|
|
|
|
TAG_IMPORT,
|
|
|
|
TAG_TYPE_IMPORT,
|
|
|
|
} from '../types/events';
|
|
|
|
|
|
|
|
import { filterEqual, filterExisting, parseFile, readFile } from './state-util';
|
2021-08-12 15:04:37 +02:00
|
|
|
|
2021-07-07 10:46:50 +02:00
|
|
|
import { IUnleashConfig } from '../types/option';
|
|
|
|
import {
|
|
|
|
FeatureToggle,
|
|
|
|
IEnvironment,
|
|
|
|
IFeatureEnvironment,
|
2021-08-12 15:04:37 +02:00
|
|
|
IFeatureStrategy,
|
|
|
|
IImportData,
|
2022-03-29 14:59:14 +02:00
|
|
|
IImportFile,
|
2021-08-19 13:25:36 +02:00
|
|
|
IProject,
|
2022-03-29 14:59:14 +02:00
|
|
|
ISegment,
|
2021-09-13 10:23:57 +02:00
|
|
|
IStrategyConfig,
|
2022-03-29 14:59:14 +02:00
|
|
|
ITag,
|
2021-07-07 10:46:50 +02:00
|
|
|
} from '../types/model';
|
2021-08-12 15:04:37 +02:00
|
|
|
import { Logger } from '../logger';
|
|
|
|
import {
|
|
|
|
IFeatureTag,
|
|
|
|
IFeatureTagStore,
|
|
|
|
} from '../types/stores/feature-tag-store';
|
2021-08-19 13:25:36 +02:00
|
|
|
import { IProjectStore } from '../types/stores/project-store';
|
2021-08-12 15:04:37 +02:00
|
|
|
import { ITagType, ITagTypeStore } from '../types/stores/tag-type-store';
|
|
|
|
import { ITagStore } from '../types/stores/tag-store';
|
|
|
|
import { IEventStore } from '../types/stores/event-store';
|
|
|
|
import { IStrategy, IStrategyStore } from '../types/stores/strategy-store';
|
|
|
|
import { IFeatureToggleStore } from '../types/stores/feature-toggle-store';
|
|
|
|
import { IFeatureStrategiesStore } from '../types/stores/feature-strategies-store';
|
|
|
|
import { IEnvironmentStore } from '../types/stores/environment-store';
|
|
|
|
import { IFeatureEnvironmentStore } from '../types/stores/feature-environment-store';
|
|
|
|
import { IUnleashStores } from '../types/stores';
|
2022-11-21 10:37:16 +01:00
|
|
|
import { ALL_ENVS, DEFAULT_ENV } from '../util/constants';
|
2021-09-24 13:55:00 +02:00
|
|
|
import { GLOBAL_ENV } from '../types/environment';
|
2022-03-29 14:59:14 +02:00
|
|
|
import { ISegmentStore } from '../types/stores/segment-store';
|
|
|
|
import { PartialSome } from '../types/partial';
|
2022-06-09 16:56:13 +02:00
|
|
|
import { IApiTokenStore } from 'lib/types/stores/api-token-store';
|
2022-12-01 12:13:49 +01:00
|
|
|
import { IFlagResolver } from 'lib/types';
|
2021-07-07 10:46:50 +02:00
|
|
|
|
|
|
|
export interface IBackupOption {
|
|
|
|
includeFeatureToggles: boolean;
|
|
|
|
includeStrategies: boolean;
|
|
|
|
includeProjects: boolean;
|
|
|
|
includeTags: boolean;
|
|
|
|
}
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
interface IExportIncludeOptions {
|
|
|
|
includeFeatureToggles?: boolean;
|
|
|
|
includeStrategies?: boolean;
|
|
|
|
includeProjects?: boolean;
|
|
|
|
includeTags?: boolean;
|
|
|
|
includeEnvironments?: boolean;
|
2022-03-29 14:59:14 +02:00
|
|
|
includeSegments?: boolean;
|
2021-07-07 10:46:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export default class StateService {
|
|
|
|
private logger: Logger;
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
private toggleStore: IFeatureToggleStore;
|
|
|
|
|
|
|
|
private featureStrategiesStore: IFeatureStrategiesStore;
|
2021-07-07 10:46:50 +02:00
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
private strategyStore: IStrategyStore;
|
2020-11-03 14:56:07 +01:00
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
private eventStore: IEventStore;
|
2020-11-03 14:56:07 +01:00
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
private tagStore: ITagStore;
|
2021-07-07 10:46:50 +02:00
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
private tagTypeStore: ITagTypeStore;
|
2021-07-07 10:46:50 +02:00
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
private projectStore: IProjectStore;
|
2021-07-07 10:46:50 +02:00
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
private featureEnvironmentStore: IFeatureEnvironmentStore;
|
2021-07-07 10:46:50 +02:00
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
private featureTagStore: IFeatureTagStore;
|
2021-07-07 10:46:50 +02:00
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
private environmentStore: IEnvironmentStore;
|
2021-07-07 10:46:50 +02:00
|
|
|
|
2022-03-29 14:59:14 +02:00
|
|
|
private segmentStore: ISegmentStore;
|
|
|
|
|
2022-06-09 16:56:13 +02:00
|
|
|
private apiTokenStore: IApiTokenStore;
|
|
|
|
|
2022-12-01 12:13:49 +01:00
|
|
|
private flagResolver: IFlagResolver;
|
|
|
|
|
2021-07-07 10:46:50 +02:00
|
|
|
constructor(
|
|
|
|
stores: IUnleashStores,
|
2022-12-01 12:13:49 +01:00
|
|
|
{
|
|
|
|
getLogger,
|
|
|
|
flagResolver,
|
|
|
|
}: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>,
|
2021-07-07 10:46:50 +02:00
|
|
|
) {
|
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;
|
2021-07-07 10:46:50 +02:00
|
|
|
this.featureStrategiesStore = stores.featureStrategiesStore;
|
2021-08-12 15:04:37 +02:00
|
|
|
this.featureEnvironmentStore = stores.featureEnvironmentStore;
|
2021-03-12 11:08:10 +01:00
|
|
|
this.tagTypeStore = stores.tagTypeStore;
|
|
|
|
this.projectStore = stores.projectStore;
|
2021-07-07 10:46:50 +02:00
|
|
|
this.featureTagStore = stores.featureTagStore;
|
|
|
|
this.environmentStore = stores.environmentStore;
|
2022-03-29 14:59:14 +02:00
|
|
|
this.segmentStore = stores.segmentStore;
|
2022-06-09 16:56:13 +02:00
|
|
|
this.apiTokenStore = stores.apiTokenStore;
|
2022-12-01 12:13:49 +01:00
|
|
|
this.flagResolver = flagResolver;
|
2020-11-03 14:56:07 +01:00
|
|
|
this.logger = getLogger('services/state-service.js');
|
|
|
|
}
|
|
|
|
|
2021-07-07 10:46:50 +02:00
|
|
|
async importFile({
|
|
|
|
file,
|
2021-08-12 15:04:37 +02:00
|
|
|
dropBeforeImport = false,
|
|
|
|
userName = 'import-user',
|
|
|
|
keepExisting = true,
|
|
|
|
}: IImportFile): Promise<void> {
|
2020-11-03 14:56:07 +01:00
|
|
|
return readFile(file)
|
2021-08-12 15:04:37 +02:00
|
|
|
.then((data) => parseFile(file, data))
|
|
|
|
.then((data) =>
|
|
|
|
this.import({
|
|
|
|
data,
|
|
|
|
userName,
|
|
|
|
dropBeforeImport,
|
|
|
|
keepExisting,
|
|
|
|
}),
|
2020-11-03 14:56:07 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-09-24 13:55:00 +02:00
|
|
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
|
|
replaceGlobalEnvWithDefaultEnv(data: any) {
|
|
|
|
data.environments?.forEach((e) => {
|
|
|
|
if (e.name === GLOBAL_ENV) {
|
|
|
|
e.name = DEFAULT_ENV;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
data.featureEnvironments?.forEach((fe) => {
|
|
|
|
if (fe.environment === GLOBAL_ENV) {
|
|
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
|
fe.environment = DEFAULT_ENV;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
data.featureStrategies?.forEach((fs) => {
|
|
|
|
if (fs.environment === GLOBAL_ENV) {
|
|
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
|
fs.environment = DEFAULT_ENV;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-11-21 10:37:16 +01:00
|
|
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
|
|
moveVariantsToFeatureEnvironments(data: any) {
|
|
|
|
data.featureEnvironments?.forEach((featureEnvironment) => {
|
|
|
|
let feature = data.features?.find(
|
|
|
|
(f) => f.name === featureEnvironment.featureName,
|
|
|
|
);
|
|
|
|
if (feature) {
|
|
|
|
featureEnvironment.variants = feature.variants || [];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-07-07 10:46:50 +02:00
|
|
|
async import({
|
|
|
|
data,
|
2021-08-12 15:04:37 +02:00
|
|
|
userName = 'importUser',
|
|
|
|
dropBeforeImport = false,
|
|
|
|
keepExisting = true,
|
|
|
|
}: IImportData): Promise<void> {
|
2021-09-24 13:55:00 +02:00
|
|
|
if (data.version === 2) {
|
|
|
|
this.replaceGlobalEnvWithDefaultEnv(data);
|
|
|
|
}
|
2022-11-21 10:37:16 +01:00
|
|
|
if (!data.version || data.version < 4) {
|
|
|
|
this.moveVariantsToFeatureEnvironments(data);
|
|
|
|
}
|
2020-11-03 14:56:07 +01:00
|
|
|
const importData = await stateSchema.validateAsync(data);
|
|
|
|
|
2022-10-19 14:05:07 +02:00
|
|
|
let importedEnvironments: IEnvironment[] = [];
|
2021-09-24 13:55:00 +02:00
|
|
|
if (importData.environments) {
|
2022-10-19 14:05:07 +02:00
|
|
|
importedEnvironments = await this.importEnvironments({
|
2021-09-24 13:55:00 +02:00
|
|
|
environments: data.environments,
|
|
|
|
userName,
|
|
|
|
dropBeforeImport,
|
|
|
|
keepExisting,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-10-19 14:05:07 +02:00
|
|
|
if (importData.projects) {
|
|
|
|
await this.importProjects({
|
|
|
|
projects: data.projects,
|
|
|
|
importedEnvironments,
|
|
|
|
userName,
|
|
|
|
dropBeforeImport,
|
|
|
|
keepExisting,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-11-03 14:56:07 +01:00
|
|
|
if (importData.features) {
|
2021-07-07 10:46:50 +02:00
|
|
|
let projectData;
|
|
|
|
if (!importData.version || importData.version === 1) {
|
|
|
|
projectData = await this.convertLegacyFeatures(importData);
|
|
|
|
} else {
|
|
|
|
projectData = importData;
|
|
|
|
}
|
2021-08-12 15:04:37 +02:00
|
|
|
const { features, featureStrategies, featureEnvironments } =
|
|
|
|
projectData;
|
2021-07-07 10:46:50 +02:00
|
|
|
|
2020-11-03 14:56:07 +01:00
|
|
|
await this.importFeatures({
|
2021-07-07 10:46:50 +02:00
|
|
|
features,
|
|
|
|
userName,
|
|
|
|
dropBeforeImport,
|
|
|
|
keepExisting,
|
2022-11-21 10:37:16 +01:00
|
|
|
featureEnvironments,
|
2021-07-07 10:46:50 +02:00
|
|
|
});
|
2022-11-17 14:05:57 +01:00
|
|
|
|
|
|
|
if (featureEnvironments) {
|
|
|
|
await this.importFeatureEnvironments({
|
|
|
|
featureEnvironments,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-07-07 10:46:50 +02:00
|
|
|
await this.importFeatureStrategies({
|
|
|
|
featureStrategies,
|
2020-11-03 14:56:07 +01:00
|
|
|
dropBeforeImport,
|
|
|
|
keepExisting,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (importData.strategies) {
|
|
|
|
await this.importStrategies({
|
|
|
|
strategies: data.strategies,
|
|
|
|
userName,
|
|
|
|
dropBeforeImport,
|
|
|
|
keepExisting,
|
|
|
|
});
|
|
|
|
}
|
2021-03-12 11:08:10 +01:00
|
|
|
|
2021-07-07 10:46:50 +02:00
|
|
|
if (importData.tagTypes && importData.tags) {
|
|
|
|
await this.importTagData({
|
|
|
|
tagTypes: data.tagTypes,
|
|
|
|
tags: data.tags,
|
|
|
|
featureTags:
|
2021-08-10 13:50:59 +02:00
|
|
|
(data.featureTags || [])
|
2021-08-12 15:04:37 +02:00
|
|
|
.filter((t) =>
|
2021-08-10 13:50:59 +02:00
|
|
|
(data.features || []).some(
|
2021-08-12 15:04:37 +02:00
|
|
|
(f) => f.name === t.featureName,
|
2021-08-10 13:50:59 +02:00
|
|
|
),
|
2021-07-30 13:38:28 +02:00
|
|
|
)
|
2021-08-12 15:04:37 +02:00
|
|
|
.map((t) => ({
|
2021-07-30 13:38:28 +02:00
|
|
|
featureName: t.featureName,
|
|
|
|
tagValue: t.tagValue || t.value,
|
|
|
|
tagType: t.tagType || t.type,
|
|
|
|
})) || [],
|
2021-03-12 11:08:10 +01:00
|
|
|
userName,
|
|
|
|
dropBeforeImport,
|
|
|
|
keepExisting,
|
|
|
|
});
|
|
|
|
}
|
2022-03-29 14:59:14 +02:00
|
|
|
|
|
|
|
if (importData.segments) {
|
|
|
|
await this.importSegments(
|
|
|
|
data.segments,
|
|
|
|
userName,
|
|
|
|
dropBeforeImport,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (importData.featureStrategySegments) {
|
|
|
|
await this.importFeatureStrategySegments(
|
|
|
|
data.featureStrategySegments,
|
|
|
|
);
|
|
|
|
}
|
2020-11-03 14:56:07 +01:00
|
|
|
}
|
|
|
|
|
2022-11-17 14:05:57 +01:00
|
|
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
2022-11-21 10:37:16 +01:00
|
|
|
enabledIn(feature: string, env) {
|
2022-11-17 14:05:57 +01:00
|
|
|
const config = {};
|
|
|
|
env.filter((e) => e.featureName === feature).forEach((e) => {
|
|
|
|
config[e.environment] = e.enabled || false;
|
|
|
|
});
|
|
|
|
return config;
|
|
|
|
}
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
2021-07-07 10:46:50 +02:00
|
|
|
async importFeatureEnvironments({ featureEnvironments }): Promise<void> {
|
|
|
|
await Promise.all(
|
2022-11-21 10:37:16 +01:00
|
|
|
featureEnvironments
|
|
|
|
.filter(async (env) => {
|
|
|
|
await this.environmentStore.exists(env.environment);
|
|
|
|
})
|
|
|
|
.map(async (featureEnvironment) =>
|
|
|
|
this.featureEnvironmentStore.addFeatureEnvironment(
|
|
|
|
featureEnvironment,
|
2022-10-19 14:05:07 +02:00
|
|
|
),
|
2022-11-21 10:37:16 +01:00
|
|
|
),
|
2021-07-07 10:46:50 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
2021-07-07 10:46:50 +02:00
|
|
|
async importFeatureStrategies({
|
|
|
|
featureStrategies,
|
|
|
|
dropBeforeImport,
|
|
|
|
keepExisting,
|
|
|
|
}): Promise<void> {
|
|
|
|
const oldFeatureStrategies = dropBeforeImport
|
|
|
|
? []
|
2021-09-13 10:23:57 +02:00
|
|
|
: await this.featureStrategiesStore.getAll();
|
2021-07-07 10:46:50 +02:00
|
|
|
if (dropBeforeImport) {
|
|
|
|
this.logger.info(
|
|
|
|
'Dropping existing strategies for feature toggles',
|
|
|
|
);
|
2021-08-12 15:04:37 +02:00
|
|
|
await this.featureStrategiesStore.deleteAll();
|
2021-07-07 10:46:50 +02:00
|
|
|
}
|
|
|
|
const strategiesToImport = keepExisting
|
|
|
|
? featureStrategies.filter(
|
2021-08-12 15:04:37 +02:00
|
|
|
(s) => !oldFeatureStrategies.some((o) => o.id === s.id),
|
|
|
|
)
|
2021-07-07 10:46:50 +02:00
|
|
|
: featureStrategies;
|
|
|
|
await Promise.all(
|
2021-08-12 15:04:37 +02:00
|
|
|
strategiesToImport.map((featureStrategy) =>
|
2021-09-13 10:23:57 +02:00
|
|
|
this.featureStrategiesStore.createStrategyFeatureEnv(
|
2021-07-07 10:46:50 +02:00
|
|
|
featureStrategy,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
2021-07-07 10:46:50 +02:00
|
|
|
async convertLegacyFeatures({
|
|
|
|
features,
|
|
|
|
}): Promise<{ features; featureStrategies; featureEnvironments }> {
|
2021-08-12 15:04:37 +02:00
|
|
|
const strategies = features.flatMap((f) =>
|
2021-09-13 10:23:57 +02:00
|
|
|
f.strategies.map((strategy: IStrategyConfig) => ({
|
2021-07-07 10:46:50 +02:00
|
|
|
featureName: f.name,
|
2021-09-13 10:23:57 +02:00
|
|
|
projectId: f.project,
|
2021-07-07 10:46:50 +02:00
|
|
|
constraints: strategy.constraints || [],
|
|
|
|
parameters: strategy.parameters || {},
|
2021-09-24 13:55:00 +02:00
|
|
|
environment: DEFAULT_ENV,
|
2021-07-07 10:46:50 +02:00
|
|
|
strategyName: strategy.name,
|
|
|
|
})),
|
|
|
|
);
|
|
|
|
const newFeatures = features;
|
2021-08-12 15:04:37 +02:00
|
|
|
const featureEnvironments = features.map((feature) => ({
|
2021-07-07 10:46:50 +02:00
|
|
|
featureName: feature.name,
|
2021-09-24 13:55:00 +02:00
|
|
|
environment: DEFAULT_ENV,
|
2021-07-07 10:46:50 +02:00
|
|
|
enabled: feature.enabled,
|
2022-11-21 10:37:16 +01:00
|
|
|
variants: feature.variants || [],
|
2021-07-07 10:46:50 +02:00
|
|
|
}));
|
|
|
|
return {
|
|
|
|
features: newFeatures,
|
|
|
|
featureStrategies: strategies,
|
|
|
|
featureEnvironments,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
2020-11-03 14:56:07 +01:00
|
|
|
async importFeatures({
|
|
|
|
features,
|
|
|
|
userName,
|
|
|
|
dropBeforeImport,
|
|
|
|
keepExisting,
|
2022-11-21 10:37:16 +01:00
|
|
|
featureEnvironments,
|
2021-07-07 10:46:50 +02:00
|
|
|
}): Promise<void> {
|
2020-11-03 14:56:07 +01:00
|
|
|
this.logger.info(`Importing ${features.length} feature toggles`);
|
|
|
|
const oldToggles = dropBeforeImport
|
|
|
|
? []
|
2021-08-12 15:04:37 +02:00
|
|
|
: await this.toggleStore.getAll();
|
2020-11-03 14:56:07 +01:00
|
|
|
|
|
|
|
if (dropBeforeImport) {
|
2021-02-23 06:20:10 +01:00
|
|
|
this.logger.info('Dropping existing feature toggles');
|
2021-08-12 15:04:37 +02:00
|
|
|
await this.toggleStore.deleteAll();
|
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))
|
2022-01-06 10:23:52 +01:00
|
|
|
.map(async (feature) => {
|
|
|
|
await this.toggleStore.create(feature.project, feature);
|
2022-11-21 10:37:16 +01:00
|
|
|
await this.featureEnvironmentStore.connectFeatureToEnvironmentsForProject(
|
|
|
|
feature.name,
|
|
|
|
feature.project,
|
|
|
|
this.enabledIn(feature.name, featureEnvironments),
|
2022-01-06 10:23:52 +01:00
|
|
|
);
|
|
|
|
await this.eventStore.store({
|
|
|
|
type: FEATURE_IMPORT,
|
|
|
|
createdBy: userName,
|
|
|
|
data: feature,
|
|
|
|
});
|
|
|
|
}),
|
2020-11-03 14:56:07 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
2020-11-03 14:56:07 +01:00
|
|
|
async importStrategies({
|
|
|
|
strategies,
|
|
|
|
userName,
|
|
|
|
dropBeforeImport,
|
|
|
|
keepExisting,
|
2021-07-07 10:46:50 +02:00
|
|
|
}): Promise<void> {
|
2020-11-03 14:56:07 +01:00
|
|
|
this.logger.info(`Importing ${strategies.length} strategies`);
|
|
|
|
const oldStrategies = dropBeforeImport
|
|
|
|
? []
|
2021-08-12 15:04:37 +02:00
|
|
|
: await this.strategyStore.getAll();
|
2020-11-03 14:56:07 +01:00
|
|
|
|
|
|
|
if (dropBeforeImport) {
|
2021-02-23 06:20:10 +01:00
|
|
|
this.logger.info('Dropping existing strategies');
|
2021-08-26 22:41:51 +02:00
|
|
|
await this.strategyStore.dropCustomStrategies();
|
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))
|
2021-08-12 15:04:37 +02:00
|
|
|
.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-09-24 13:55:00 +02:00
|
|
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
|
|
async importEnvironments({
|
|
|
|
environments,
|
|
|
|
userName,
|
|
|
|
dropBeforeImport,
|
|
|
|
keepExisting,
|
2022-10-19 14:05:07 +02:00
|
|
|
}): Promise<IEnvironment[]> {
|
2021-09-24 13:55:00 +02:00
|
|
|
this.logger.info(`Import ${environments.length} projects`);
|
|
|
|
const oldEnvs = dropBeforeImport
|
|
|
|
? []
|
|
|
|
: await this.environmentStore.getAll();
|
|
|
|
if (dropBeforeImport) {
|
|
|
|
this.logger.info('Dropping existing environments');
|
|
|
|
await this.environmentStore.deleteAll();
|
|
|
|
await this.eventStore.store({
|
|
|
|
type: DROP_ENVIRONMENTS,
|
|
|
|
createdBy: userName,
|
2022-06-07 12:33:30 +02:00
|
|
|
data: { name: 'all-environments' },
|
2021-09-24 13:55:00 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
const envsImport = environments.filter((env) =>
|
|
|
|
keepExisting ? !oldEnvs.some((old) => old.name === env.name) : true,
|
|
|
|
);
|
2022-10-19 14:05:07 +02:00
|
|
|
let importedEnvs = [];
|
2021-09-24 13:55:00 +02:00
|
|
|
if (envsImport.length > 0) {
|
2022-10-19 14:05:07 +02:00
|
|
|
importedEnvs = await this.environmentStore.importEnvironments(
|
2021-09-24 13:55:00 +02:00
|
|
|
envsImport,
|
|
|
|
);
|
|
|
|
const importedEnvironmentEvents = importedEnvs.map((env) => ({
|
|
|
|
type: ENVIRONMENT_IMPORT,
|
|
|
|
createdBy: userName,
|
|
|
|
data: env,
|
|
|
|
}));
|
|
|
|
await this.eventStore.batchStore(importedEnvironmentEvents);
|
2022-06-09 16:56:13 +02:00
|
|
|
|
|
|
|
const apiTokens = await this.apiTokenStore.getAll();
|
|
|
|
const envNames = importedEnvs.map((env) => env.name);
|
|
|
|
apiTokens
|
|
|
|
.filter((apiToken) => !(apiToken.environment === ALL_ENVS))
|
|
|
|
.filter((apiToken) => !envNames.includes(apiToken.environment))
|
|
|
|
.forEach((apiToken) =>
|
|
|
|
this.apiTokenStore.delete(apiToken.secret),
|
|
|
|
);
|
2021-09-24 13:55:00 +02:00
|
|
|
}
|
2022-10-19 14:05:07 +02:00
|
|
|
return importedEnvs;
|
2021-09-24 13:55:00 +02:00
|
|
|
}
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
2021-03-12 11:08:10 +01:00
|
|
|
async importProjects({
|
|
|
|
projects,
|
2022-10-19 14:05:07 +02:00
|
|
|
importedEnvironments,
|
2021-03-12 11:08:10 +01:00
|
|
|
userName,
|
|
|
|
dropBeforeImport,
|
|
|
|
keepExisting,
|
2021-07-07 10:46:50 +02:00
|
|
|
}): Promise<void> {
|
2021-03-12 11:08:10 +01:00
|
|
|
this.logger.info(`Import ${projects.length} projects`);
|
|
|
|
const oldProjects = dropBeforeImport
|
|
|
|
? []
|
|
|
|
: await this.projectStore.getAll();
|
|
|
|
if (dropBeforeImport) {
|
|
|
|
this.logger.info('Dropping existing projects');
|
2021-08-12 15:04:37 +02:00
|
|
|
await this.projectStore.deleteAll();
|
2021-03-12 11:08:10 +01:00
|
|
|
await this.eventStore.store({
|
|
|
|
type: DROP_PROJECTS,
|
|
|
|
createdBy: userName,
|
|
|
|
data: { name: 'all-projects' },
|
|
|
|
});
|
|
|
|
}
|
2021-08-12 15:04:37 +02:00
|
|
|
const projectsToImport = projects.filter((project) =>
|
2021-03-12 11:08:10 +01:00
|
|
|
keepExisting
|
2021-08-12 15:04:37 +02:00
|
|
|
? !oldProjects.some((old) => old.id === project.id)
|
2021-03-12 11:08:10 +01:00
|
|
|
: true,
|
|
|
|
);
|
|
|
|
if (projectsToImport.length > 0) {
|
|
|
|
const importedProjects = await this.projectStore.importProjects(
|
|
|
|
projectsToImport,
|
2022-10-19 14:05:07 +02:00
|
|
|
importedEnvironments,
|
2021-03-12 11:08:10 +01:00
|
|
|
);
|
2021-08-12 15:04:37 +02:00
|
|
|
const importedProjectEvents = importedProjects.map((project) => ({
|
2021-04-16 15:29:23 +02:00
|
|
|
type: PROJECT_IMPORT,
|
|
|
|
createdBy: userName,
|
|
|
|
data: project,
|
|
|
|
}));
|
2021-03-12 11:08:10 +01:00
|
|
|
await this.eventStore.batchStore(importedProjectEvents);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
2021-03-12 11:08:10 +01:00
|
|
|
async importTagData({
|
|
|
|
tagTypes,
|
|
|
|
tags,
|
|
|
|
featureTags,
|
|
|
|
userName,
|
|
|
|
dropBeforeImport,
|
|
|
|
keepExisting,
|
2021-07-07 10:46:50 +02:00
|
|
|
}): Promise<void> {
|
2021-03-12 11:08:10 +01:00
|
|
|
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
|
|
|
|
? []
|
2021-08-12 15:04:37 +02:00
|
|
|
: await this.featureTagStore.getAll();
|
2021-03-12 11:08:10 +01:00
|
|
|
if (dropBeforeImport) {
|
|
|
|
this.logger.info(
|
|
|
|
'Dropping all existing featuretags, tags and tagtypes',
|
|
|
|
);
|
2021-08-12 15:04:37 +02:00
|
|
|
await this.featureTagStore.deleteAll();
|
|
|
|
await this.tagStore.deleteAll();
|
|
|
|
await this.tagTypeStore.deleteAll();
|
2021-03-12 11:08:10 +01:00
|
|
|
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,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-07-07 10:46:50 +02:00
|
|
|
compareFeatureTags: (old: IFeatureTag, tag: IFeatureTag) => boolean = (
|
|
|
|
old,
|
|
|
|
tag,
|
|
|
|
) =>
|
2021-03-12 11:08:10 +01:00
|
|
|
old.featureName === tag.featureName &&
|
|
|
|
old.tagValue === tag.tagValue &&
|
|
|
|
old.tagType === tag.tagType;
|
|
|
|
|
|
|
|
async importFeatureTags(
|
2021-07-07 10:46:50 +02:00
|
|
|
featureTags: IFeatureTag[],
|
|
|
|
keepExisting: boolean,
|
|
|
|
oldFeatureTags: IFeatureTag[],
|
|
|
|
userName: string,
|
|
|
|
): Promise<void> {
|
2021-08-12 15:04:37 +02:00
|
|
|
const featureTagsToInsert = featureTags.filter((tag) =>
|
2021-03-12 11:08:10 +01:00
|
|
|
keepExisting
|
2021-08-12 15:04:37 +02:00
|
|
|
? !oldFeatureTags.some((old) =>
|
|
|
|
this.compareFeatureTags(old, tag),
|
|
|
|
)
|
2021-03-12 11:08:10 +01:00
|
|
|
: true,
|
|
|
|
);
|
|
|
|
if (featureTagsToInsert.length > 0) {
|
2021-08-12 15:04:37 +02:00
|
|
|
const importedFeatureTags =
|
|
|
|
await this.featureTagStore.importFeatureTags(
|
|
|
|
featureTagsToInsert,
|
|
|
|
);
|
|
|
|
const importedFeatureTagEvents = importedFeatureTags.map((tag) => ({
|
2021-04-16 15:29:23 +02:00
|
|
|
type: FEATURE_TAG_IMPORT,
|
|
|
|
createdBy: userName,
|
|
|
|
data: tag,
|
|
|
|
}));
|
2021-03-12 11:08:10 +01:00
|
|
|
await this.eventStore.batchStore(importedFeatureTagEvents);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-07 10:46:50 +02:00
|
|
|
compareTags = (old: ITag, tag: ITag): boolean =>
|
2021-03-12 11:08:10 +01:00
|
|
|
old.type === tag.type && old.value === tag.value;
|
|
|
|
|
2021-07-07 10:46:50 +02:00
|
|
|
async importTags(
|
|
|
|
tags: ITag[],
|
|
|
|
keepExisting: boolean,
|
|
|
|
oldTags: ITag[],
|
|
|
|
userName: string,
|
|
|
|
): Promise<void> {
|
2021-08-12 15:04:37 +02:00
|
|
|
const tagsToInsert = tags.filter((tag) =>
|
2021-03-12 11:08:10 +01:00
|
|
|
keepExisting
|
2021-08-12 15:04:37 +02:00
|
|
|
? !oldTags.some((old) => this.compareTags(old, tag))
|
2021-03-12 11:08:10 +01:00
|
|
|
: true,
|
|
|
|
);
|
|
|
|
if (tagsToInsert.length > 0) {
|
|
|
|
const importedTags = await this.tagStore.bulkImport(tagsToInsert);
|
2021-08-12 15:04:37 +02:00
|
|
|
const importedTagEvents = importedTags.map((tag) => ({
|
2021-04-16 15:29:23 +02:00
|
|
|
type: TAG_IMPORT,
|
|
|
|
createdBy: userName,
|
|
|
|
data: tag,
|
|
|
|
}));
|
2021-03-12 11:08:10 +01:00
|
|
|
await this.eventStore.batchStore(importedTagEvents);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-07 10:46:50 +02:00
|
|
|
async importTagTypes(
|
|
|
|
tagTypes: ITagType[],
|
|
|
|
keepExisting: boolean,
|
2022-04-25 13:14:43 +02:00
|
|
|
oldTagTypes: ITagType[] = [], // eslint-disable-line
|
2021-07-07 10:46:50 +02:00
|
|
|
userName: string,
|
|
|
|
): Promise<void> {
|
2021-08-12 15:04:37 +02:00
|
|
|
const tagTypesToInsert = tagTypes.filter((tagType) =>
|
2021-03-12 11:08:10 +01:00
|
|
|
keepExisting
|
2021-08-12 15:04:37 +02:00
|
|
|
? !oldTagTypes.some((t) => t.name === tagType.name)
|
2021-03-12 11:08:10 +01:00
|
|
|
: true,
|
|
|
|
);
|
|
|
|
if (tagTypesToInsert.length > 0) {
|
|
|
|
const importedTagTypes = await this.tagTypeStore.bulkImport(
|
|
|
|
tagTypesToInsert,
|
|
|
|
);
|
2021-08-12 15:04:37 +02:00
|
|
|
const importedTagTypeEvents = importedTagTypes.map((tagType) => ({
|
2021-04-16 15:29:23 +02:00
|
|
|
type: TAG_TYPE_IMPORT,
|
|
|
|
createdBy: userName,
|
|
|
|
data: tagType,
|
|
|
|
}));
|
2021-03-12 11:08:10 +01:00
|
|
|
await this.eventStore.batchStore(importedTagTypeEvents);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-29 14:59:14 +02:00
|
|
|
async importSegments(
|
|
|
|
segments: PartialSome<ISegment, 'id'>[],
|
|
|
|
userName: string,
|
|
|
|
dropBeforeImport: boolean,
|
|
|
|
): Promise<void> {
|
|
|
|
if (dropBeforeImport) {
|
|
|
|
await this.segmentStore.deleteAll();
|
|
|
|
}
|
|
|
|
|
|
|
|
await Promise.all(
|
|
|
|
segments.map((segment) =>
|
|
|
|
this.segmentStore.create(segment, { username: userName }),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
async importFeatureStrategySegments(
|
|
|
|
featureStrategySegments: {
|
|
|
|
featureStrategyId: string;
|
|
|
|
segmentId: number;
|
|
|
|
}[],
|
|
|
|
): Promise<void> {
|
|
|
|
await Promise.all(
|
|
|
|
featureStrategySegments.map(({ featureStrategyId, segmentId }) =>
|
|
|
|
this.segmentStore.addToStrategy(segmentId, featureStrategyId),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-12-01 12:13:49 +01:00
|
|
|
async export(opts: IExportIncludeOptions): Promise<{
|
|
|
|
features: FeatureToggle[];
|
|
|
|
strategies: IStrategy[];
|
|
|
|
version: number;
|
|
|
|
projects: IProject[];
|
|
|
|
tagTypes: ITagType[];
|
|
|
|
tags: ITag[];
|
|
|
|
featureTags: IFeatureTag[];
|
|
|
|
featureStrategies: IFeatureStrategy[];
|
|
|
|
environments: IEnvironment[];
|
|
|
|
featureEnvironments: IFeatureEnvironment[];
|
|
|
|
}> {
|
|
|
|
if (this.flagResolver.isEnabled('variantsPerEnvironment')) {
|
|
|
|
return this.exportV4(opts);
|
|
|
|
}
|
|
|
|
// adapt v4 to v3. We need includeEnvironments set to true to filter the
|
|
|
|
// best environment from where we'll pick variants (cause now they are stored
|
|
|
|
// per environment despite being displayed as if they belong to the feature)
|
|
|
|
const v4 = await this.exportV4({ ...opts, includeEnvironments: true });
|
|
|
|
// undefined defaults to true
|
|
|
|
if (opts.includeFeatureToggles !== false) {
|
|
|
|
const keepEnv = v4.environments
|
|
|
|
.filter((env) => env.enabled !== false)
|
|
|
|
.sort((e1, e2) => {
|
|
|
|
if (e1.type !== 'production' || e2.type !== 'production') {
|
|
|
|
if (e1.type === 'production') {
|
|
|
|
return -1;
|
|
|
|
} else if (e2.type === 'production') {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return e1.sortOrder - e2.sortOrder;
|
|
|
|
})[0];
|
|
|
|
|
|
|
|
const featureEnvs = v4.featureEnvironments.filter(
|
|
|
|
(fE) => fE.environment === keepEnv.name,
|
|
|
|
);
|
|
|
|
v4.features = v4.features.map((f) => {
|
|
|
|
const variants = featureEnvs.find(
|
|
|
|
(fe) => fe.enabled !== false && fe.featureName === f.name,
|
|
|
|
)?.variants;
|
|
|
|
return { ...f, variants };
|
|
|
|
});
|
|
|
|
v4.featureEnvironments = v4.featureEnvironments.map((fe) => {
|
|
|
|
delete fe.variants;
|
|
|
|
return fe;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
// only if explicitly set to false (i.e. undefined defaults to true)
|
|
|
|
if (opts.includeEnvironments === false) {
|
|
|
|
delete v4.environments;
|
|
|
|
}
|
|
|
|
v4.version = 3;
|
|
|
|
return v4;
|
|
|
|
}
|
|
|
|
|
|
|
|
async exportV4({
|
2021-03-12 11:08:10 +01:00
|
|
|
includeFeatureToggles = true,
|
|
|
|
includeStrategies = true,
|
|
|
|
includeProjects = true,
|
|
|
|
includeTags = true,
|
2021-07-07 10:46:50 +02:00
|
|
|
includeEnvironments = true,
|
2022-03-29 14:59:14 +02:00
|
|
|
includeSegments = true,
|
2021-08-12 15:04:37 +02:00
|
|
|
}: IExportIncludeOptions): Promise<{
|
|
|
|
features: FeatureToggle[];
|
|
|
|
strategies: IStrategy[];
|
|
|
|
version: number;
|
|
|
|
projects: IProject[];
|
|
|
|
tagTypes: ITagType[];
|
|
|
|
tags: ITag[];
|
|
|
|
featureTags: IFeatureTag[];
|
|
|
|
featureStrategies: IFeatureStrategy[];
|
|
|
|
environments: IEnvironment[];
|
|
|
|
featureEnvironments: IFeatureEnvironment[];
|
|
|
|
}> {
|
2020-11-03 14:56:07 +01:00
|
|
|
return Promise.all([
|
|
|
|
includeFeatureToggles
|
2021-09-24 08:55:53 +02:00
|
|
|
? this.toggleStore.getAll({ archived: false })
|
2021-07-07 10:46:50 +02:00
|
|
|
: Promise.resolve([]),
|
2020-11-03 14:56:07 +01:00
|
|
|
includeStrategies
|
|
|
|
? this.strategyStore.getEditableStrategies()
|
2021-07-07 10:46:50 +02:00
|
|
|
: Promise.resolve([]),
|
2021-03-12 11:08:10 +01:00
|
|
|
this.projectStore && includeProjects
|
|
|
|
? this.projectStore.getAll()
|
2021-07-07 10:46:50 +02:00
|
|
|
: Promise.resolve([]),
|
|
|
|
includeTags ? this.tagTypeStore.getAll() : Promise.resolve([]),
|
|
|
|
includeTags ? this.tagStore.getAll() : Promise.resolve([]),
|
2021-07-30 13:38:28 +02:00
|
|
|
includeTags && includeFeatureToggles
|
2021-08-12 15:04:37 +02:00
|
|
|
? this.featureTagStore.getAll()
|
2021-07-07 10:46:50 +02:00
|
|
|
: Promise.resolve([]),
|
|
|
|
includeFeatureToggles
|
|
|
|
? this.featureStrategiesStore.getAll()
|
|
|
|
: Promise.resolve([]),
|
|
|
|
includeEnvironments
|
|
|
|
? this.environmentStore.getAll()
|
|
|
|
: Promise.resolve([]),
|
|
|
|
includeFeatureToggles
|
2021-08-12 15:04:37 +02:00
|
|
|
? this.featureEnvironmentStore.getAll()
|
2021-07-07 10:46:50 +02:00
|
|
|
: Promise.resolve([]),
|
2022-03-29 14:59:14 +02:00
|
|
|
includeSegments ? this.segmentStore.getAll() : Promise.resolve([]),
|
|
|
|
includeSegments
|
|
|
|
? this.segmentStore.getAllFeatureStrategySegments()
|
|
|
|
: Promise.resolve([]),
|
2021-03-12 11:08:10 +01:00
|
|
|
]).then(
|
|
|
|
([
|
|
|
|
features,
|
|
|
|
strategies,
|
|
|
|
projects,
|
|
|
|
tagTypes,
|
|
|
|
tags,
|
|
|
|
featureTags,
|
2021-07-07 10:46:50 +02:00
|
|
|
featureStrategies,
|
|
|
|
environments,
|
|
|
|
featureEnvironments,
|
2022-03-29 14:59:14 +02:00
|
|
|
segments,
|
|
|
|
featureStrategySegments,
|
2021-03-12 11:08:10 +01:00
|
|
|
]) => ({
|
2022-11-21 10:37:16 +01:00
|
|
|
version: 4,
|
2021-03-12 11:08:10 +01:00
|
|
|
features,
|
|
|
|
strategies,
|
|
|
|
projects,
|
|
|
|
tagTypes,
|
|
|
|
tags,
|
|
|
|
featureTags,
|
2021-09-24 08:55:53 +02:00
|
|
|
featureStrategies: featureStrategies.filter((fS) =>
|
|
|
|
features.some((f) => fS.featureName === f.name),
|
|
|
|
),
|
2021-07-07 10:46:50 +02:00
|
|
|
environments,
|
2021-09-24 08:55:53 +02:00
|
|
|
featureEnvironments: featureEnvironments.filter((fE) =>
|
|
|
|
features.some((f) => fE.featureName === f.name),
|
|
|
|
),
|
2022-03-29 14:59:14 +02:00
|
|
|
segments,
|
|
|
|
featureStrategySegments,
|
2021-03-12 11:08:10 +01:00
|
|
|
}),
|
|
|
|
);
|
2020-11-03 14:56:07 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = StateService;
|