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