mirror of
https://github.com/Unleash/unleash.git
synced 2025-03-18 00:19:49 +01:00
feat: add ip to state-service and group-service (#7120)
The add ip to two services. Despite state is being deprecated, I think we better get it out of the way.
This commit is contained in:
parent
a744cdf6d8
commit
345c34a945
@ -115,7 +115,7 @@ export default class ClientInstanceService {
|
||||
if (appsToAnnounce.length > 0) {
|
||||
const events = appsToAnnounce.map((app) => ({
|
||||
type: APPLICATION_CREATED,
|
||||
createdBy: app.createdBy || SYSTEM_USER.username,
|
||||
createdBy: app.createdBy || SYSTEM_USER.username!,
|
||||
data: app,
|
||||
createdByUserId: app.createdByUserId || SYSTEM_USER.id,
|
||||
}));
|
||||
|
@ -123,7 +123,7 @@ class StateController extends Controller {
|
||||
userName,
|
||||
dropBeforeImport: paramToBool(drop, false),
|
||||
keepExisting: paramToBool(keep, true),
|
||||
userId: req.user.id,
|
||||
auditUser: req.audit,
|
||||
});
|
||||
res.sendStatus(202);
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import {
|
||||
type IUnleashOptions,
|
||||
type IUnleashServices,
|
||||
RoleName,
|
||||
SYSTEM_USER,
|
||||
SYSTEM_USER_AUDIT,
|
||||
} from './types';
|
||||
|
||||
import User, { type IAuditUser, type IUser } from './types/user';
|
||||
@ -101,7 +101,7 @@ async function createApp(
|
||||
dropBeforeImport: config.import.dropBeforeImport,
|
||||
userName: 'import',
|
||||
keepExisting: config.import.keepExisting,
|
||||
userId: SYSTEM_USER.id,
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,8 @@ import {
|
||||
GROUP_CREATED,
|
||||
GROUP_USER_ADDED,
|
||||
GROUP_USER_REMOVED,
|
||||
GroupUserAdded,
|
||||
GroupUserRemoved,
|
||||
type IBaseEvent,
|
||||
} from '../types/events';
|
||||
import NameExistsError from '../error/name-exists-error';
|
||||
@ -235,6 +237,7 @@ export class GroupService {
|
||||
return this.groupStore.getProjectGroupRoles(projectId);
|
||||
}
|
||||
|
||||
/** @deprecated use syncExternalGroupsWithAudit */
|
||||
async syncExternalGroups(
|
||||
userId: number,
|
||||
externalGroups: string[],
|
||||
@ -286,6 +289,52 @@ export class GroupService {
|
||||
}
|
||||
}
|
||||
|
||||
async syncExternalGroupsWithAudit(
|
||||
userId: number,
|
||||
externalGroups: string[],
|
||||
auditUser: IAuditUser,
|
||||
): Promise<void> {
|
||||
if (Array.isArray(externalGroups)) {
|
||||
const newGroups = await this.groupStore.getNewGroupsForExternalUser(
|
||||
userId,
|
||||
externalGroups,
|
||||
);
|
||||
await this.groupStore.addUserToGroups(
|
||||
userId,
|
||||
newGroups.map((g) => g.id),
|
||||
auditUser.username,
|
||||
);
|
||||
const oldGroups = await this.groupStore.getOldGroupsForExternalUser(
|
||||
userId,
|
||||
externalGroups,
|
||||
);
|
||||
await this.groupStore.deleteUsersFromGroup(oldGroups);
|
||||
|
||||
const events: IBaseEvent[] = [];
|
||||
for (const group of newGroups) {
|
||||
events.push(
|
||||
new GroupUserAdded({
|
||||
userId,
|
||||
groupId: group.id,
|
||||
auditUser,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
for (const group of oldGroups) {
|
||||
events.push(
|
||||
new GroupUserRemoved({
|
||||
userId,
|
||||
groupId: group.groupId,
|
||||
auditUser,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await this.eventService.storeEvents(events);
|
||||
}
|
||||
}
|
||||
|
||||
private mapGroupWithUsers(
|
||||
group: IGroup,
|
||||
allGroupUsers: IGroupUser[],
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
import { GLOBAL_ENV } from '../types/environment';
|
||||
import variantsExportV3 from '../../test/examples/variantsexport_v3.json';
|
||||
import EventService from '../features/events/event-service';
|
||||
import { SYSTEM_USER_ID } from '../types';
|
||||
import { SYSTEM_USER_AUDIT } from '../types';
|
||||
import { EventEmitter } from 'stream';
|
||||
const oldExportExample = require('./state-service-export-v1.json');
|
||||
const TESTUSERID = 3333;
|
||||
@ -103,7 +103,7 @@ test('should import a feature', async () => {
|
||||
],
|
||||
};
|
||||
|
||||
await stateService.import({ userId: SYSTEM_USER_ID, data });
|
||||
await stateService.import({ auditUser: SYSTEM_USER_AUDIT, data });
|
||||
|
||||
const events = await stores.eventStore.getEvents();
|
||||
expect(events).toHaveLength(1);
|
||||
@ -130,7 +130,7 @@ test('should not import an existing feature', async () => {
|
||||
await stateService.import({
|
||||
data,
|
||||
keepExisting: true,
|
||||
userId: SYSTEM_USER_ID,
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
});
|
||||
|
||||
const events = await stores.eventStore.getEvents();
|
||||
@ -157,7 +157,7 @@ test('should not keep existing feature if drop-before-import', async () => {
|
||||
data,
|
||||
keepExisting: true,
|
||||
dropBeforeImport: true,
|
||||
userId: SYSTEM_USER_ID,
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
});
|
||||
|
||||
const events = await stores.eventStore.getEvents();
|
||||
@ -182,7 +182,7 @@ test('should drop feature before import if specified', async () => {
|
||||
await stateService.import({
|
||||
data,
|
||||
dropBeforeImport: true,
|
||||
userId: SYSTEM_USER_ID,
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
});
|
||||
|
||||
const events = await stores.eventStore.getEvents();
|
||||
@ -204,7 +204,7 @@ test('should import a strategy', async () => {
|
||||
],
|
||||
};
|
||||
|
||||
await stateService.import({ userId: SYSTEM_USER_ID, data });
|
||||
await stateService.import({ auditUser: SYSTEM_USER_AUDIT, data });
|
||||
|
||||
const events = await stores.eventStore.getEvents();
|
||||
expect(events).toHaveLength(1);
|
||||
@ -228,7 +228,7 @@ test('should not import an existing strategy', async () => {
|
||||
|
||||
await stateService.import({
|
||||
data,
|
||||
userId: SYSTEM_USER_ID,
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
keepExisting: true,
|
||||
});
|
||||
|
||||
@ -250,7 +250,7 @@ test('should drop strategies before import if specified', async () => {
|
||||
|
||||
await stateService.import({
|
||||
data,
|
||||
userId: SYSTEM_USER_ID,
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
dropBeforeImport: true,
|
||||
});
|
||||
|
||||
@ -268,7 +268,7 @@ test('should drop neither features nor strategies when neither is imported', asy
|
||||
|
||||
await stateService.import({
|
||||
data,
|
||||
userId: SYSTEM_USER_ID,
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
dropBeforeImport: true,
|
||||
});
|
||||
|
||||
@ -286,11 +286,11 @@ test('should not accept gibberish', async () => {
|
||||
const data2 = '{somerandomtext/';
|
||||
|
||||
await expect(async () =>
|
||||
stateService.import({ userId: SYSTEM_USER_ID, data: data1 }),
|
||||
stateService.import({ auditUser: SYSTEM_USER_AUDIT, data: data1 }),
|
||||
).rejects.toThrow();
|
||||
|
||||
await expect(async () =>
|
||||
stateService.import({ userId: SYSTEM_USER_ID, data: data2 }),
|
||||
stateService.import({ auditUser: SYSTEM_USER_AUDIT, data: data2 }),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
@ -386,7 +386,7 @@ test('should import a tag and tag type', async () => {
|
||||
tags: [{ type: 'simple', value: 'test' }],
|
||||
};
|
||||
|
||||
await stateService.import({ userId: SYSTEM_USER_ID, data });
|
||||
await stateService.import({ auditUser: SYSTEM_USER_AUDIT, data });
|
||||
|
||||
const events = await stores.eventStore.getEvents();
|
||||
expect(events).toHaveLength(2);
|
||||
@ -423,7 +423,7 @@ test('Should not import an existing tag', async () => {
|
||||
);
|
||||
await stateService.import({
|
||||
data,
|
||||
userId: SYSTEM_USER_ID,
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
keepExisting: true,
|
||||
});
|
||||
const events = await stores.eventStore.getEvents();
|
||||
@ -460,7 +460,7 @@ test('Should not keep existing tags if drop-before-import', async () => {
|
||||
};
|
||||
await stateService.import({
|
||||
data,
|
||||
userId: SYSTEM_USER_ID,
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
dropBeforeImport: true,
|
||||
});
|
||||
const tagTypes = await stores.tagTypeStore.getAll();
|
||||
@ -570,7 +570,7 @@ test('should import a project', async () => {
|
||||
],
|
||||
};
|
||||
|
||||
await stateService.import({ userId: SYSTEM_USER_ID, data });
|
||||
await stateService.import({ auditUser: SYSTEM_USER_AUDIT, data });
|
||||
|
||||
const events = await stores.eventStore.getEvents();
|
||||
expect(events).toHaveLength(1);
|
||||
@ -595,13 +595,13 @@ test('Should not import an existing project', async () => {
|
||||
|
||||
await stateService.import({
|
||||
data,
|
||||
userId: SYSTEM_USER_ID,
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
keepExisting: true,
|
||||
});
|
||||
const events = await stores.eventStore.getEvents();
|
||||
expect(events).toHaveLength(0);
|
||||
|
||||
await stateService.import({ userId: SYSTEM_USER_ID, data });
|
||||
await stateService.import({ auditUser: SYSTEM_USER_AUDIT, data });
|
||||
});
|
||||
|
||||
test('Should drop projects before import if specified', async () => {
|
||||
@ -624,7 +624,7 @@ test('Should drop projects before import if specified', async () => {
|
||||
});
|
||||
await stateService.import({
|
||||
data,
|
||||
userId: SYSTEM_USER_ID,
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
dropBeforeImport: true,
|
||||
});
|
||||
const hasProject = await stores.projectStore.hasProject('fancy');
|
||||
@ -773,8 +773,7 @@ test('featureStrategies can keep existing', async () => {
|
||||
const exported = await stateService.export({});
|
||||
await stateService.import({
|
||||
data: exported,
|
||||
userId: SYSTEM_USER_ID,
|
||||
userName: 'testing',
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
keepExisting: true,
|
||||
});
|
||||
expect(await stores.featureStrategiesStore.getAll()).toHaveLength(1);
|
||||
@ -830,8 +829,7 @@ test('featureStrategies should not keep existing if dropBeforeImport', async ()
|
||||
exported.featureStrategies = [];
|
||||
await stateService.import({
|
||||
data: exported,
|
||||
userId: SYSTEM_USER_ID,
|
||||
userName: 'testing',
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
keepExisting: true,
|
||||
dropBeforeImport: true,
|
||||
});
|
||||
@ -842,9 +840,8 @@ test('Import v1 and exporting v2 should work', async () => {
|
||||
const { stateService } = getSetup();
|
||||
await stateService.import({
|
||||
data: oldExportExample,
|
||||
userId: SYSTEM_USER_ID,
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
dropBeforeImport: true,
|
||||
userName: 'testing',
|
||||
});
|
||||
const exported = await stateService.export({});
|
||||
const strategiesCount = oldExportExample.features.reduce(
|
||||
@ -879,8 +876,7 @@ test('Importing states with deprecated strategies should keep their deprecated s
|
||||
};
|
||||
await stateService.import({
|
||||
data: deprecatedStrategyExample,
|
||||
userId: SYSTEM_USER_ID,
|
||||
userName: 'strategy-importer',
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
dropBeforeImport: true,
|
||||
keepExisting: false,
|
||||
});
|
||||
@ -894,9 +890,8 @@ test('Exporting a deprecated strategy and then importing it should keep correct
|
||||
await stateService.import({
|
||||
data: variantsExportV3,
|
||||
keepExisting: false,
|
||||
userId: SYSTEM_USER_ID,
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
dropBeforeImport: true,
|
||||
userName: 'strategy importer',
|
||||
});
|
||||
const rolloutRandom = await stores.strategyStore.get(
|
||||
'gradualRolloutRandom',
|
||||
|
@ -1,19 +1,19 @@
|
||||
import { stateSchema } from './state-schema';
|
||||
import {
|
||||
DROP_ENVIRONMENTS,
|
||||
DROP_FEATURE_TAGS,
|
||||
DROP_FEATURES,
|
||||
DROP_PROJECTS,
|
||||
DROP_STRATEGIES,
|
||||
DROP_TAG_TYPES,
|
||||
DROP_TAGS,
|
||||
ENVIRONMENT_IMPORT,
|
||||
FEATURE_IMPORT,
|
||||
FEATURE_TAG_IMPORT,
|
||||
PROJECT_IMPORT,
|
||||
STRATEGY_IMPORT,
|
||||
TAG_IMPORT,
|
||||
TAG_TYPE_IMPORT,
|
||||
DropEnvironmentsEvent,
|
||||
DropFeaturesEvent,
|
||||
DropFeatureTagsEvent,
|
||||
DropProjectsEvent,
|
||||
DropStrategiesEvent,
|
||||
DropTagsEvent,
|
||||
DropTagTypesEvent,
|
||||
EnvironmentImport,
|
||||
FeatureImport,
|
||||
FeatureTagImport,
|
||||
ProjectImport,
|
||||
StrategyImport,
|
||||
TagImport,
|
||||
TagTypeImport,
|
||||
} from '../types/events';
|
||||
|
||||
import { filterEqual, filterExisting, parseFile, readFile } from './state-util';
|
||||
@ -53,6 +53,7 @@ import { GLOBAL_ENV } from '../types/environment';
|
||||
import type { ISegmentStore } from '../features/segment/segment-store-type';
|
||||
import type { PartialSome } from '../types/partial';
|
||||
import type EventService from '../features/events/event-service';
|
||||
import type { IAuditUser } from '../server-impl';
|
||||
|
||||
export interface IBackupOption {
|
||||
includeFeatureToggles: boolean;
|
||||
@ -117,8 +118,7 @@ export default class StateService {
|
||||
async importFile({
|
||||
file,
|
||||
dropBeforeImport = false,
|
||||
userName = 'import-user',
|
||||
userId,
|
||||
auditUser,
|
||||
keepExisting = true,
|
||||
}: IImportFile): Promise<void> {
|
||||
return readFile(file)
|
||||
@ -126,10 +126,9 @@ export default class StateService {
|
||||
.then((data) =>
|
||||
this.import({
|
||||
data,
|
||||
userName,
|
||||
auditUser,
|
||||
dropBeforeImport,
|
||||
keepExisting,
|
||||
userId,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@ -169,8 +168,7 @@ export default class StateService {
|
||||
|
||||
async import({
|
||||
data,
|
||||
userName = 'importUser',
|
||||
userId,
|
||||
auditUser,
|
||||
dropBeforeImport = false,
|
||||
keepExisting = true,
|
||||
}: IImportData): Promise<void> {
|
||||
@ -186,10 +184,9 @@ export default class StateService {
|
||||
if (importData.environments) {
|
||||
importedEnvironments = await this.importEnvironments({
|
||||
environments: data.environments,
|
||||
userName,
|
||||
dropBeforeImport,
|
||||
keepExisting,
|
||||
userId,
|
||||
auditUser,
|
||||
});
|
||||
}
|
||||
|
||||
@ -197,10 +194,9 @@ export default class StateService {
|
||||
await this.importProjects({
|
||||
projects: data.projects,
|
||||
importedEnvironments,
|
||||
userName,
|
||||
dropBeforeImport,
|
||||
keepExisting,
|
||||
userId,
|
||||
auditUser,
|
||||
});
|
||||
}
|
||||
|
||||
@ -217,11 +213,10 @@ export default class StateService {
|
||||
|
||||
await this.importFeatures({
|
||||
features,
|
||||
userName,
|
||||
dropBeforeImport,
|
||||
keepExisting,
|
||||
featureEnvironments,
|
||||
userId,
|
||||
auditUser,
|
||||
});
|
||||
|
||||
if (featureEnvironments) {
|
||||
@ -240,10 +235,9 @@ export default class StateService {
|
||||
if (importData.strategies) {
|
||||
await this.importStrategies({
|
||||
strategies: data.strategies,
|
||||
userName,
|
||||
dropBeforeImport,
|
||||
keepExisting,
|
||||
userId,
|
||||
auditUser,
|
||||
});
|
||||
}
|
||||
|
||||
@ -263,18 +257,16 @@ export default class StateService {
|
||||
tagValue: t.tagValue || t.value,
|
||||
tagType: t.tagType || t.type,
|
||||
})) || [],
|
||||
userName,
|
||||
dropBeforeImport,
|
||||
keepExisting,
|
||||
userId,
|
||||
auditUser,
|
||||
});
|
||||
}
|
||||
|
||||
if (importData.segments) {
|
||||
await this.importSegments(
|
||||
data.segments,
|
||||
userName,
|
||||
userId,
|
||||
auditUser,
|
||||
dropBeforeImport,
|
||||
);
|
||||
}
|
||||
@ -365,14 +357,12 @@ export default class StateService {
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async importFeatures({
|
||||
features,
|
||||
userName,
|
||||
userId,
|
||||
dropBeforeImport,
|
||||
keepExisting,
|
||||
featureEnvironments,
|
||||
auditUser,
|
||||
}): Promise<void> {
|
||||
this.logger.info(`Importing ${features.length} feature flags`);
|
||||
const oldToggles = dropBeforeImport
|
||||
@ -382,12 +372,9 @@ export default class StateService {
|
||||
if (dropBeforeImport) {
|
||||
this.logger.info('Dropping existing feature flags');
|
||||
await this.toggleStore.deleteAll();
|
||||
await this.eventService.storeEvent({
|
||||
type: DROP_FEATURES,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: { name: 'all-features' },
|
||||
});
|
||||
await this.eventService.storeEvent(
|
||||
new DropFeaturesEvent({ auditUser }),
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
@ -396,7 +383,7 @@ export default class StateService {
|
||||
.filter(filterEqual(oldToggles))
|
||||
.map(async (feature) => {
|
||||
await this.toggleStore.create(feature.project, {
|
||||
createdByUserId: userId,
|
||||
createdByUserId: auditUser.id,
|
||||
...feature,
|
||||
});
|
||||
await this.featureEnvironmentStore.connectFeatureToEnvironmentsForProject(
|
||||
@ -404,12 +391,12 @@ export default class StateService {
|
||||
feature.project,
|
||||
this.enabledIn(feature.name, featureEnvironments),
|
||||
);
|
||||
await this.eventService.storeEvent({
|
||||
type: FEATURE_IMPORT,
|
||||
createdByUserId: userId,
|
||||
createdBy: userName,
|
||||
data: feature,
|
||||
});
|
||||
await this.eventService.storeEvent(
|
||||
new FeatureImport({
|
||||
feature,
|
||||
auditUser,
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
@ -417,10 +404,9 @@ export default class StateService {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async importStrategies({
|
||||
strategies,
|
||||
userName,
|
||||
userId,
|
||||
dropBeforeImport,
|
||||
keepExisting,
|
||||
auditUser,
|
||||
}): Promise<void> {
|
||||
this.logger.info(`Importing ${strategies.length} strategies`);
|
||||
const oldStrategies = dropBeforeImport
|
||||
@ -430,12 +416,9 @@ export default class StateService {
|
||||
if (dropBeforeImport) {
|
||||
this.logger.info('Dropping existing strategies');
|
||||
await this.strategyStore.dropCustomStrategies();
|
||||
await this.eventService.storeEvent({
|
||||
type: DROP_STRATEGIES,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: { name: 'all-strategies' },
|
||||
});
|
||||
await this.eventService.storeEvent(
|
||||
new DropStrategiesEvent({ auditUser }),
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
@ -444,12 +427,9 @@ export default class StateService {
|
||||
.filter(filterEqual(oldStrategies))
|
||||
.map((strategy) =>
|
||||
this.strategyStore.importStrategy(strategy).then(() => {
|
||||
this.eventService.storeEvent({
|
||||
type: STRATEGY_IMPORT,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: strategy,
|
||||
});
|
||||
this.eventService.storeEvent(
|
||||
new StrategyImport({ strategy, auditUser }),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
@ -458,8 +438,7 @@ export default class StateService {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async importEnvironments({
|
||||
environments,
|
||||
userName,
|
||||
userId,
|
||||
auditUser,
|
||||
dropBeforeImport,
|
||||
keepExisting,
|
||||
}): Promise<IEnvironment[]> {
|
||||
@ -470,12 +449,9 @@ export default class StateService {
|
||||
if (dropBeforeImport) {
|
||||
this.logger.info('Dropping existing environments');
|
||||
await this.environmentStore.deleteAll();
|
||||
await this.eventService.storeEvent({
|
||||
type: DROP_ENVIRONMENTS,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: { name: 'all-environments' },
|
||||
});
|
||||
await this.eventService.storeEvent(
|
||||
new DropEnvironmentsEvent({ auditUser }),
|
||||
);
|
||||
}
|
||||
const envsImport = environments.filter((env) =>
|
||||
keepExisting ? !oldEnvs.some((old) => old.name === env.name) : true,
|
||||
@ -484,12 +460,9 @@ export default class StateService {
|
||||
if (envsImport.length > 0) {
|
||||
importedEnvs =
|
||||
await this.environmentStore.importEnvironments(envsImport);
|
||||
const importedEnvironmentEvents = importedEnvs.map((env) => ({
|
||||
type: ENVIRONMENT_IMPORT,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: env,
|
||||
}));
|
||||
const importedEnvironmentEvents = importedEnvs.map(
|
||||
(env) => new EnvironmentImport({ auditUser, env }),
|
||||
);
|
||||
await this.eventService.storeEvents(importedEnvironmentEvents);
|
||||
}
|
||||
return importedEnvs;
|
||||
@ -499,8 +472,7 @@ export default class StateService {
|
||||
async importProjects({
|
||||
projects,
|
||||
importedEnvironments,
|
||||
userName,
|
||||
userId,
|
||||
auditUser,
|
||||
dropBeforeImport,
|
||||
keepExisting,
|
||||
}): Promise<void> {
|
||||
@ -511,12 +483,9 @@ export default class StateService {
|
||||
if (dropBeforeImport) {
|
||||
this.logger.info('Dropping existing projects');
|
||||
await this.projectStore.deleteAll();
|
||||
await this.eventService.storeEvent({
|
||||
type: DROP_PROJECTS,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: { name: 'all-projects' },
|
||||
});
|
||||
await this.eventService.storeEvent(
|
||||
new DropProjectsEvent({ auditUser }),
|
||||
);
|
||||
}
|
||||
const projectsToImport = projects.filter((project) =>
|
||||
keepExisting
|
||||
@ -528,12 +497,9 @@ export default class StateService {
|
||||
projectsToImport,
|
||||
importedEnvironments,
|
||||
);
|
||||
const importedProjectEvents = importedProjects.map((project) => ({
|
||||
type: PROJECT_IMPORT,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: project,
|
||||
}));
|
||||
const importedProjectEvents = importedProjects.map(
|
||||
(project) => new ProjectImport({ project, auditUser }),
|
||||
);
|
||||
await this.eventService.storeEvents(importedProjectEvents);
|
||||
}
|
||||
}
|
||||
@ -543,8 +509,7 @@ export default class StateService {
|
||||
tagTypes,
|
||||
tags,
|
||||
featureTags,
|
||||
userName,
|
||||
userId,
|
||||
auditUser,
|
||||
dropBeforeImport,
|
||||
keepExisting,
|
||||
}): Promise<void> {
|
||||
@ -566,40 +531,23 @@ export default class StateService {
|
||||
await this.tagStore.deleteAll();
|
||||
await this.tagTypeStore.deleteAll();
|
||||
await this.eventService.storeEvents([
|
||||
{
|
||||
type: DROP_FEATURE_TAGS,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: { name: 'all-feature-tags' },
|
||||
},
|
||||
{
|
||||
type: DROP_TAGS,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: { name: 'all-tags' },
|
||||
},
|
||||
{
|
||||
type: DROP_TAG_TYPES,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: { name: 'all-tag-types' },
|
||||
},
|
||||
new DropFeatureTagsEvent({ auditUser }),
|
||||
new DropTagsEvent({ auditUser }),
|
||||
new DropTagTypesEvent({ auditUser }),
|
||||
]);
|
||||
}
|
||||
await this.importTagTypes(
|
||||
tagTypes,
|
||||
keepExisting,
|
||||
oldTagTypes,
|
||||
userName,
|
||||
userId,
|
||||
auditUser,
|
||||
);
|
||||
await this.importTags(tags, keepExisting, oldTags, userName, userId);
|
||||
await this.importTags(tags, keepExisting, oldTags, auditUser);
|
||||
await this.importFeatureTags(
|
||||
featureTags,
|
||||
keepExisting,
|
||||
oldFeatureTags,
|
||||
userName,
|
||||
userId,
|
||||
auditUser,
|
||||
);
|
||||
}
|
||||
|
||||
@ -615,8 +563,7 @@ export default class StateService {
|
||||
featureTags: IFeatureTag[],
|
||||
keepExisting: boolean,
|
||||
oldFeatureTags: IFeatureTag[],
|
||||
userName: string,
|
||||
userId: number,
|
||||
auditUser: IAuditUser,
|
||||
): Promise<void> {
|
||||
const featureTagsToInsert = featureTags
|
||||
.filter((tag) =>
|
||||
@ -627,18 +574,15 @@ export default class StateService {
|
||||
: true,
|
||||
)
|
||||
.map((tag) => ({
|
||||
createdByUserId: userId,
|
||||
createdByUserId: auditUser.id,
|
||||
...tag,
|
||||
}));
|
||||
if (featureTagsToInsert.length > 0) {
|
||||
const importedFeatureTags =
|
||||
await this.featureTagStore.tagFeatures(featureTagsToInsert);
|
||||
const importedFeatureTagEvents = importedFeatureTags.map((tag) => ({
|
||||
type: FEATURE_TAG_IMPORT,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: tag,
|
||||
}));
|
||||
const importedFeatureTagEvents = importedFeatureTags.map(
|
||||
(featureTag) => new FeatureTagImport({ featureTag, auditUser }),
|
||||
);
|
||||
await this.eventService.storeEvents(importedFeatureTagEvents);
|
||||
}
|
||||
}
|
||||
@ -650,8 +594,7 @@ export default class StateService {
|
||||
tags: ITag[],
|
||||
keepExisting: boolean,
|
||||
oldTags: ITag[],
|
||||
userName: string,
|
||||
userId: number,
|
||||
auditUser: IAuditUser,
|
||||
): Promise<void> {
|
||||
const tagsToInsert = tags.filter((tag) =>
|
||||
keepExisting
|
||||
@ -660,12 +603,9 @@ export default class StateService {
|
||||
);
|
||||
if (tagsToInsert.length > 0) {
|
||||
const importedTags = await this.tagStore.bulkImport(tagsToInsert);
|
||||
const importedTagEvents = importedTags.map((tag) => ({
|
||||
type: TAG_IMPORT,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: tag,
|
||||
}));
|
||||
const importedTagEvents = importedTags.map(
|
||||
(tag) => new TagImport({ tag, auditUser }),
|
||||
);
|
||||
await this.eventService.storeEvents(importedTagEvents);
|
||||
}
|
||||
}
|
||||
@ -674,8 +614,7 @@ export default class StateService {
|
||||
tagTypes: ITagType[],
|
||||
keepExisting: boolean,
|
||||
oldTagTypes: ITagType[],
|
||||
userName: string,
|
||||
userId: number,
|
||||
auditUser: IAuditUser,
|
||||
): Promise<void> {
|
||||
const tagTypesToInsert = tagTypes.filter((tagType) =>
|
||||
keepExisting
|
||||
@ -685,20 +624,16 @@ export default class StateService {
|
||||
if (tagTypesToInsert.length > 0) {
|
||||
const importedTagTypes =
|
||||
await this.tagTypeStore.bulkImport(tagTypesToInsert);
|
||||
const importedTagTypeEvents = importedTagTypes.map((tagType) => ({
|
||||
type: TAG_TYPE_IMPORT,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: tagType,
|
||||
}));
|
||||
const importedTagTypeEvents = importedTagTypes.map(
|
||||
(tagType) => new TagTypeImport({ tagType, auditUser }),
|
||||
);
|
||||
await this.eventService.storeEvents(importedTagTypeEvents);
|
||||
}
|
||||
}
|
||||
|
||||
async importSegments(
|
||||
segments: PartialSome<ISegment, 'id'>[],
|
||||
userName: string,
|
||||
userId: number,
|
||||
auditUser: IAuditUser,
|
||||
dropBeforeImport: boolean,
|
||||
): Promise<void> {
|
||||
if (dropBeforeImport) {
|
||||
@ -707,7 +642,9 @@ export default class StateService {
|
||||
|
||||
await Promise.all(
|
||||
segments.map((segment) =>
|
||||
this.segmentStore.create(segment, { username: userName }),
|
||||
this.segmentStore.create(segment, {
|
||||
username: auditUser.username,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,16 @@
|
||||
import type { FeatureToggle, IStrategyConfig, ITag, IVariant } from './model';
|
||||
import type {
|
||||
FeatureToggle,
|
||||
IEnvironment,
|
||||
IProject,
|
||||
IStrategyConfig,
|
||||
ITag,
|
||||
IVariant,
|
||||
} from './model';
|
||||
import type { IApiToken } from './models/api-token';
|
||||
import type { IAuditUser, IUserWithRootRole } from './user';
|
||||
import type { FeatureLifecycleCompletedSchema } from '../openapi';
|
||||
import type { ITagType } from '../features/tag-type/tag-type-store-type';
|
||||
import type { IFeatureAndTag } from './stores/feature-tag-store';
|
||||
|
||||
export const APPLICATION_CREATED = 'application-created' as const;
|
||||
|
||||
@ -356,7 +365,8 @@ export interface IBaseEvent {
|
||||
tags?: ITag[];
|
||||
}
|
||||
|
||||
export interface IEvent extends IBaseEvent {
|
||||
// This represents the read model for events
|
||||
export interface IEvent extends Omit<IBaseEvent, 'ip'> {
|
||||
id: number;
|
||||
createdAt: Date;
|
||||
}
|
||||
@ -366,7 +376,7 @@ export interface IEventList {
|
||||
events: IEvent[];
|
||||
}
|
||||
|
||||
class BaseEvent implements IBaseEvent {
|
||||
export class BaseEvent implements IBaseEvent {
|
||||
readonly type: IEventType;
|
||||
|
||||
readonly createdBy: string;
|
||||
@ -633,6 +643,83 @@ export class FeatureCreatedEvent extends BaseEvent {
|
||||
}
|
||||
}
|
||||
|
||||
export class ProjectImport extends BaseEvent {
|
||||
readonly data: IProject;
|
||||
constructor(p: {
|
||||
project: IProject;
|
||||
auditUser: IAuditUser;
|
||||
}) {
|
||||
super(PROJECT_IMPORT, p.auditUser);
|
||||
this.data = p.project;
|
||||
}
|
||||
}
|
||||
|
||||
export class FeatureImport extends BaseEvent {
|
||||
readonly data: any;
|
||||
constructor(p: {
|
||||
feature: any;
|
||||
auditUser: IAuditUser;
|
||||
}) {
|
||||
super(FEATURE_IMPORT, p.auditUser);
|
||||
this.data = p.feature;
|
||||
}
|
||||
}
|
||||
|
||||
export class StrategyImport extends BaseEvent {
|
||||
readonly data: any;
|
||||
constructor(p: {
|
||||
strategy: any;
|
||||
auditUser: IAuditUser;
|
||||
}) {
|
||||
super(STRATEGY_IMPORT, p.auditUser);
|
||||
this.data = p.strategy;
|
||||
}
|
||||
}
|
||||
|
||||
export class EnvironmentImport extends BaseEvent {
|
||||
readonly data: IEnvironment;
|
||||
constructor(p: {
|
||||
env: IEnvironment;
|
||||
auditUser: IAuditUser;
|
||||
}) {
|
||||
super(ENVIRONMENT_IMPORT, p.auditUser);
|
||||
this.data = p.env;
|
||||
}
|
||||
}
|
||||
|
||||
export class TagTypeImport extends BaseEvent {
|
||||
readonly data: ITagType;
|
||||
constructor(p: {
|
||||
tagType: ITagType;
|
||||
auditUser: IAuditUser;
|
||||
}) {
|
||||
super(TAG_TYPE_IMPORT, p.auditUser);
|
||||
this.data = p.tagType;
|
||||
}
|
||||
}
|
||||
|
||||
export class TagImport extends BaseEvent {
|
||||
readonly data: ITag;
|
||||
constructor(p: {
|
||||
tag: ITag;
|
||||
auditUser: IAuditUser;
|
||||
}) {
|
||||
super(TAG_IMPORT, p.auditUser);
|
||||
this.data = p.tag;
|
||||
}
|
||||
}
|
||||
|
||||
export class FeatureTagImport extends BaseEvent {
|
||||
readonly data: IFeatureAndTag;
|
||||
constructor(p: {
|
||||
featureTag: IFeatureAndTag;
|
||||
auditUser: IAuditUser;
|
||||
}) {
|
||||
super(FEATURE_TAG_IMPORT, p.auditUser);
|
||||
this.data = p.featureTag;
|
||||
}
|
||||
}
|
||||
|
||||
export class FeatureCompletedEvent extends BaseEvent {
|
||||
readonly featureName: string;
|
||||
readonly data: FeatureLifecycleCompletedSchema;
|
||||
@ -1202,6 +1289,36 @@ export class ProjectAccessGroupRolesUpdated extends BaseEvent {
|
||||
}
|
||||
}
|
||||
|
||||
export class GroupUserRemoved extends BaseEvent {
|
||||
readonly preData: any;
|
||||
constructor(p: {
|
||||
userId: number;
|
||||
groupId: number;
|
||||
auditUser: IAuditUser;
|
||||
}) {
|
||||
super(GROUP_USER_REMOVED, p.auditUser);
|
||||
this.preData = {
|
||||
groupId: p.groupId,
|
||||
userId: p.userId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class GroupUserAdded extends BaseEvent {
|
||||
readonly data: any;
|
||||
constructor(p: {
|
||||
userId: number;
|
||||
groupId: number;
|
||||
auditUser: IAuditUser;
|
||||
}) {
|
||||
super(GROUP_USER_ADDED, p.auditUser);
|
||||
this.data = {
|
||||
groupId: p.groupId,
|
||||
userId: p.userId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class ProjectAccessUserRolesDeleted extends BaseEvent {
|
||||
readonly project: string;
|
||||
|
||||
@ -1558,6 +1675,83 @@ export class FeaturesExportedEvent extends BaseEvent {
|
||||
}
|
||||
}
|
||||
|
||||
export class DropProjectsEvent extends BaseEvent {
|
||||
readonly data: any;
|
||||
|
||||
constructor(eventData: {
|
||||
auditUser: IAuditUser;
|
||||
}) {
|
||||
super(DROP_PROJECTS, eventData.auditUser);
|
||||
this.data = { name: 'all-projects' };
|
||||
}
|
||||
}
|
||||
|
||||
export class DropFeaturesEvent extends BaseEvent {
|
||||
readonly data: any;
|
||||
|
||||
constructor(eventData: {
|
||||
auditUser: IAuditUser;
|
||||
}) {
|
||||
super(DROP_FEATURES, eventData.auditUser);
|
||||
this.data = { name: 'all-features' };
|
||||
}
|
||||
}
|
||||
|
||||
export class DropStrategiesEvent extends BaseEvent {
|
||||
readonly data: any;
|
||||
|
||||
constructor(eventData: {
|
||||
auditUser: IAuditUser;
|
||||
}) {
|
||||
super(DROP_STRATEGIES, eventData.auditUser);
|
||||
this.data = { name: 'all-strategies' };
|
||||
}
|
||||
}
|
||||
|
||||
export class DropEnvironmentsEvent extends BaseEvent {
|
||||
readonly data: any;
|
||||
|
||||
constructor(eventData: {
|
||||
auditUser: IAuditUser;
|
||||
}) {
|
||||
super(DROP_ENVIRONMENTS, eventData.auditUser);
|
||||
this.data = { name: 'all-environments' };
|
||||
}
|
||||
}
|
||||
|
||||
export class DropFeatureTagsEvent extends BaseEvent {
|
||||
readonly data: any;
|
||||
|
||||
constructor(eventData: {
|
||||
auditUser: IAuditUser;
|
||||
}) {
|
||||
super(DROP_FEATURE_TAGS, eventData.auditUser);
|
||||
this.data = { name: 'all-feature-tags' };
|
||||
}
|
||||
}
|
||||
|
||||
export class DropTagsEvent extends BaseEvent {
|
||||
readonly data: any;
|
||||
|
||||
constructor(eventData: {
|
||||
auditUser: IAuditUser;
|
||||
}) {
|
||||
super(DROP_TAGS, eventData.auditUser);
|
||||
this.data = { name: 'all-tags' };
|
||||
}
|
||||
}
|
||||
|
||||
export class DropTagTypesEvent extends BaseEvent {
|
||||
readonly data: any;
|
||||
|
||||
constructor(eventData: {
|
||||
auditUser: IAuditUser;
|
||||
}) {
|
||||
super(DROP_TAG_TYPES, eventData.auditUser);
|
||||
this.data = { name: 'all-tag-types' };
|
||||
}
|
||||
}
|
||||
|
||||
export class RoleCreatedEvent extends BaseEvent {
|
||||
readonly data: any;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type { ITagType } from '../features/tag-type/tag-type-store-type';
|
||||
import type { LogProvider } from '../logger';
|
||||
import type { IRole } from './stores/access-store';
|
||||
import type { IUser } from './user';
|
||||
import type { IAuditUser, IUser } from './user';
|
||||
import type { ALL_OPERATORS } from '../util';
|
||||
import type { IProjectStats } from '../features/project/project-service';
|
||||
import type { CreateFeatureStrategySchema } from '../openapi';
|
||||
@ -479,8 +479,11 @@ export interface IImportFile extends ImportCommon {
|
||||
interface ImportCommon {
|
||||
dropBeforeImport?: boolean;
|
||||
keepExisting?: boolean;
|
||||
/** @deprecated should use auditUser instead */
|
||||
userName?: string;
|
||||
userId: number;
|
||||
/** @deprecated should use auditUser instead */
|
||||
userId?: number;
|
||||
auditUser: IAuditUser;
|
||||
}
|
||||
|
||||
export interface IImportData extends ImportCommon {
|
||||
|
@ -206,8 +206,7 @@ test('Can roundtrip. I.e. export and then import', async () => {
|
||||
data,
|
||||
dropBeforeImport: true,
|
||||
keepExisting: false,
|
||||
userName: 'export-tester',
|
||||
userId: -9999,
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
});
|
||||
});
|
||||
|
||||
@ -270,8 +269,7 @@ test('Roundtrip with tags works', async () => {
|
||||
data,
|
||||
dropBeforeImport: true,
|
||||
keepExisting: false,
|
||||
userName: 'export-tester',
|
||||
userId: -9999,
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
});
|
||||
|
||||
const f = await app.services.featureTagService.listTags(featureName);
|
||||
@ -341,8 +339,7 @@ test('Roundtrip with strategies in multiple environments works', async () => {
|
||||
data,
|
||||
dropBeforeImport: true,
|
||||
keepExisting: false,
|
||||
userName: 'export-tester',
|
||||
userId: -9999,
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
});
|
||||
const f = await app.services.featureToggleServiceV2.getFeature({
|
||||
featureName,
|
||||
@ -423,8 +420,7 @@ test(`should not delete api_tokens on import when drop-flag is set`, async () =>
|
||||
data,
|
||||
dropBeforeImport: true,
|
||||
keepExisting: false,
|
||||
userName: userName,
|
||||
userId: -9999,
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
});
|
||||
|
||||
const apiTokens = await app.services.apiTokenService.getAllTokens();
|
||||
|
@ -4,7 +4,7 @@ import StateService from '../../../lib/services/state-service';
|
||||
import oldFormat from '../../examples/variantsexport_v3.json';
|
||||
import { WeightType } from '../../../lib/types/model';
|
||||
import { EventService } from '../../../lib/services';
|
||||
import type { IUnleashStores } from '../../../lib/types';
|
||||
import { SYSTEM_USER_AUDIT, type IUnleashStores } from '../../../lib/types';
|
||||
|
||||
let stores: IUnleashStores;
|
||||
let db: ITestDb;
|
||||
@ -143,7 +143,7 @@ test('Should import variants from old format and convert to new format (per envi
|
||||
data: oldFormat,
|
||||
keepExisting: false,
|
||||
dropBeforeImport: true,
|
||||
userId: -9999,
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
});
|
||||
const featureEnvironments = await stores.featureEnvironmentStore.getAll();
|
||||
expect(featureEnvironments).toHaveLength(6); // There are 3 environments enabled and 2 features
|
||||
@ -158,14 +158,14 @@ test('Should import variants in new format (per environment)', async () => {
|
||||
data: oldFormat,
|
||||
keepExisting: false,
|
||||
dropBeforeImport: true,
|
||||
userId: -9999,
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
});
|
||||
const exportedJson = await stateService.export({});
|
||||
await stateService.import({
|
||||
data: exportedJson,
|
||||
keepExisting: false,
|
||||
dropBeforeImport: true,
|
||||
userId: -9999,
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
});
|
||||
const featureEnvironments = await stores.featureEnvironmentStore.getAll();
|
||||
expect(featureEnvironments).toHaveLength(6); // 3 environments, 2 features === 6 rows
|
||||
@ -190,10 +190,9 @@ test('Importing states with deprecated strategies should keep their deprecated s
|
||||
};
|
||||
await stateService.import({
|
||||
data: deprecatedStrategyExample,
|
||||
userName: 'strategy-importer',
|
||||
dropBeforeImport: true,
|
||||
keepExisting: false,
|
||||
userId: -9999,
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
});
|
||||
const deprecatedStrategy =
|
||||
await stores.strategyStore.get('deprecatedstrat');
|
||||
@ -205,8 +204,7 @@ test('Exporting a deprecated strategy and then importing it should keep correct
|
||||
data: oldFormat,
|
||||
keepExisting: false,
|
||||
dropBeforeImport: true,
|
||||
userName: 'strategy importer',
|
||||
userId: -9999,
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
});
|
||||
const rolloutRandom = await stores.strategyStore.get(
|
||||
'gradualRolloutRandom',
|
||||
|
Loading…
Reference in New Issue
Block a user