1
0
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:
Gastón Fournier 2024-05-24 09:53:46 +02:00 committed by GitHub
parent a744cdf6d8
commit 345c34a945
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 368 additions and 196 deletions

View File

@ -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,
}));

View File

@ -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);
}

View File

@ -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,
});
}

View File

@ -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[],

View File

@ -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',

View File

@ -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,
}),
),
);
}

View File

@ -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;

View File

@ -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 {

View File

@ -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();

View File

@ -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',