mirror of
https://github.com/Unleash/unleash.git
synced 2025-10-27 11:02:16 +01:00
feat: adds created_by_user_id to all events (#5619)
### What Adds `createdByUserId` to all events exposed by unleash. In addition this PR updates all tests and usages of the methods in this codebase to include the required number.
This commit is contained in:
parent
772682176e
commit
bfa82d79bf
@ -45,6 +45,7 @@ test('Should call datadog webhook', async () => {
|
||||
createdAt: new Date(),
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
createdByUserId: -1337,
|
||||
featureName: 'some-toggle',
|
||||
data: {
|
||||
name: 'some-toggle',
|
||||
@ -74,6 +75,7 @@ test('Should call datadog webhook for archived toggle', async () => {
|
||||
createdAt: new Date(),
|
||||
type: FEATURE_ARCHIVED,
|
||||
createdBy: 'some@user.com',
|
||||
createdByUserId: -1337,
|
||||
featureName: 'some-toggle',
|
||||
data: {
|
||||
name: 'some-toggle',
|
||||
@ -102,6 +104,7 @@ test('Should call datadog webhook for archived toggle with project info', async
|
||||
type: FEATURE_ARCHIVED,
|
||||
createdBy: 'some@user.com',
|
||||
featureName: 'some-toggle',
|
||||
createdByUserId: -1337,
|
||||
project: 'some-project',
|
||||
data: {
|
||||
name: 'some-toggle',
|
||||
@ -129,6 +132,7 @@ test('Should call datadog webhook for toggled environment', async () => {
|
||||
createdAt: new Date(),
|
||||
type: FEATURE_ENVIRONMENT_DISABLED,
|
||||
createdBy: 'some@user.com',
|
||||
createdByUserId: -1337,
|
||||
environment: 'development',
|
||||
project: 'default',
|
||||
featureName: 'some-toggle',
|
||||
@ -160,6 +164,7 @@ test('Should include customHeaders in headers when calling service', async () =>
|
||||
type: FEATURE_ENVIRONMENT_DISABLED,
|
||||
createdBy: 'some@user.com',
|
||||
environment: 'development',
|
||||
createdByUserId: -1337,
|
||||
project: 'default',
|
||||
featureName: 'some-toggle',
|
||||
data: {
|
||||
@ -190,6 +195,7 @@ test('Should not include source_type_name when included in the config', async ()
|
||||
createdAt: new Date(),
|
||||
type: FEATURE_ENVIRONMENT_DISABLED,
|
||||
createdBy: 'some@user.com',
|
||||
createdByUserId: -1337,
|
||||
environment: 'development',
|
||||
project: 'default',
|
||||
featureName: 'some-toggle',
|
||||
@ -224,6 +230,7 @@ test('Should call datadog webhook with JSON when template set', async () => {
|
||||
createdAt: new Date(),
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
createdByUserId: -1337,
|
||||
featureName: 'some-toggle',
|
||||
data: {
|
||||
name: 'some-toggle',
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
FEATURE_STRATEGY_REMOVE,
|
||||
FEATURE_STRATEGY_UPDATE,
|
||||
IEvent,
|
||||
SYSTEM_USER_ID,
|
||||
} from '../types';
|
||||
|
||||
import { FeatureEventFormatterMd } from './feature-event-formatter-md';
|
||||
@ -34,6 +35,7 @@ const testCases: [string, IEvent][] = [
|
||||
id: 920,
|
||||
type: FEATURE_STRATEGY_UPDATE,
|
||||
createdBy: 'user@company.com',
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
createdAt: new Date('2022-06-01T10:03:11.549Z'),
|
||||
data: {
|
||||
id: '3f4bf713-696c-43a4-8ce7-d6c607108858',
|
||||
@ -67,6 +69,7 @@ const testCases: [string, IEvent][] = [
|
||||
id: 920,
|
||||
type: FEATURE_STRATEGY_UPDATE,
|
||||
createdBy: 'user@company.com',
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
createdAt: new Date('2022-06-01T10:03:11.549Z'),
|
||||
data: {
|
||||
id: '3f4bf713-696c-43a4-8ce7-d6c607108858',
|
||||
@ -100,6 +103,7 @@ const testCases: [string, IEvent][] = [
|
||||
id: 920,
|
||||
type: FEATURE_STRATEGY_UPDATE,
|
||||
createdBy: 'user@company.com',
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
createdAt: new Date('2022-06-01T10:03:11.549Z'),
|
||||
data: {
|
||||
id: '3f4bf713-696c-43a4-8ce7-d6c607108858',
|
||||
@ -133,6 +137,7 @@ const testCases: [string, IEvent][] = [
|
||||
id: 920,
|
||||
type: FEATURE_STRATEGY_UPDATE,
|
||||
createdBy: 'user@company.com',
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
createdAt: new Date('2022-06-01T10:03:11.549Z'),
|
||||
data: {
|
||||
id: '3f4bf713-696c-43a4-8ce7-d6c607108858',
|
||||
@ -174,6 +179,7 @@ const testCases: [string, IEvent][] = [
|
||||
id: 920,
|
||||
type: FEATURE_STRATEGY_UPDATE,
|
||||
createdBy: 'user@company.com',
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
createdAt: new Date('2022-06-01T10:03:11.549Z'),
|
||||
data: {
|
||||
id: '3f4bf713-696c-43a4-8ce7-d6c607108858',
|
||||
@ -207,6 +213,7 @@ const testCases: [string, IEvent][] = [
|
||||
id: 919,
|
||||
type: FEATURE_STRATEGY_ADD,
|
||||
createdBy: 'user@company.com',
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
createdAt: new Date('2022-06-01T10:03:08.290Z'),
|
||||
data: {
|
||||
id: '3f4bf713-696c-43a4-8ce7-d6c607108858',
|
||||
@ -231,6 +238,7 @@ const testCases: [string, IEvent][] = [
|
||||
id: 918,
|
||||
type: FEATURE_STRATEGY_REMOVE,
|
||||
createdBy: 'user@company.com',
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
createdAt: new Date('2022-06-01T10:03:00.229Z'),
|
||||
data: null,
|
||||
preData: {
|
||||
@ -253,6 +261,7 @@ const testCases: [string, IEvent][] = [
|
||||
id: 39,
|
||||
type: FEATURE_STRATEGY_UPDATE,
|
||||
createdBy: 'admin',
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
createdAt: new Date('2023-02-20T20:23:28.791Z'),
|
||||
data: {
|
||||
id: 'f2d34aac-52ec-49d2-82d3-08d710e89eaa',
|
||||
@ -310,6 +319,7 @@ const testCases: [string, IEvent][] = [
|
||||
type: FEATURE_STRATEGY_UPDATE,
|
||||
createdBy: 'admin',
|
||||
createdAt: new Date('2023-02-20T20:23:28.791Z'),
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
data: {
|
||||
id: 'f2d34aac-52ec-49d2-82d3-08d710e89eaa',
|
||||
name: 'default',
|
||||
@ -346,6 +356,7 @@ const testCases: [string, IEvent][] = [
|
||||
id: 920,
|
||||
type: FEATURE_STRATEGY_UPDATE,
|
||||
createdBy: 'user@company.com',
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
createdAt: new Date('2022-06-01T10:03:11.549Z'),
|
||||
data: {
|
||||
name: 'userWithId',
|
||||
@ -385,6 +396,7 @@ const testCases: [string, IEvent][] = [
|
||||
id: 920,
|
||||
type: FEATURE_STRATEGY_UPDATE,
|
||||
createdBy: 'user@company.com',
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
createdAt: new Date('2022-06-01T10:03:11.549Z'),
|
||||
data: {
|
||||
name: 'remoteAddress',
|
||||
@ -421,6 +433,7 @@ const testCases: [string, IEvent][] = [
|
||||
type: FEATURE_STRATEGY_UPDATE,
|
||||
createdBy: 'user@company.com',
|
||||
createdAt: new Date('2022-06-01T10:03:11.549Z'),
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
data: {
|
||||
name: 'applicationHostname',
|
||||
constraints: [
|
||||
@ -456,6 +469,7 @@ const testCases: [string, IEvent][] = [
|
||||
type: FEATURE_STRATEGY_UPDATE,
|
||||
createdBy: 'user@company.com',
|
||||
createdAt: new Date('2022-06-01T10:03:11.549Z'),
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
data: {
|
||||
name: 'newStrategy',
|
||||
constraints: [
|
||||
@ -491,6 +505,7 @@ const testCases: [string, IEvent][] = [
|
||||
type: CHANGE_REQUEST_SCHEDULED,
|
||||
createdBy: 'user@company.com',
|
||||
createdAt: new Date('2022-06-01T10:03:11.549Z'),
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
data: {
|
||||
changeRequestId: 1,
|
||||
},
|
||||
@ -508,6 +523,7 @@ const testCases: [string, IEvent][] = [
|
||||
type: CHANGE_REQUEST_SCHEDULED_APPLICATION_SUCCESS,
|
||||
createdBy: 'user@company.com',
|
||||
createdAt: new Date('2022-06-01T10:03:11.549Z'),
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
data: {
|
||||
changeRequestId: 1,
|
||||
},
|
||||
@ -524,6 +540,7 @@ const testCases: [string, IEvent][] = [
|
||||
id: 920,
|
||||
type: CHANGE_REQUEST_SCHEDULED_APPLICATION_FAILURE,
|
||||
createdBy: 'user@company.com',
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
createdAt: new Date('2022-06-01T10:03:11.549Z'),
|
||||
data: {
|
||||
changeRequestId: 1,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { IEvent, FEATURE_ENVIRONMENT_ENABLED } from '../types/events';
|
||||
import SlackAppAddon from './slack-app';
|
||||
import { ChatPostMessageArguments, ErrorCode } from '@slack/web-api';
|
||||
import { SYSTEM_USER_ID } from '../types';
|
||||
|
||||
const slackApiCalls: ChatPostMessageArguments[] = [];
|
||||
|
||||
@ -44,6 +45,7 @@ describe('SlackAppAddon', () => {
|
||||
id: 1,
|
||||
createdAt: new Date(),
|
||||
type: FEATURE_ENVIRONMENT_ENABLED,
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
createdBy: 'some@user.com',
|
||||
project: 'default',
|
||||
featureName: 'some-toggle',
|
||||
|
||||
@ -9,6 +9,7 @@ import { Logger } from '../logger';
|
||||
import SlackAddon from './slack';
|
||||
|
||||
import noLogger from '../../test/fixtures/no-logger';
|
||||
import { SYSTEM_USER_ID } from '../types';
|
||||
|
||||
let fetchRetryCalls: any[] = [];
|
||||
|
||||
@ -44,6 +45,7 @@ test('Should call slack webhook', async () => {
|
||||
id: 1,
|
||||
createdAt: new Date(),
|
||||
type: FEATURE_CREATED,
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
createdBy: 'some@user.com',
|
||||
project: 'default',
|
||||
featureName: 'some-toggle',
|
||||
@ -74,6 +76,7 @@ test('Should call slack webhook for archived toggle', async () => {
|
||||
const event: IEvent = {
|
||||
id: 2,
|
||||
createdAt: new Date(),
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
type: FEATURE_ARCHIVED,
|
||||
featureName: 'some-toggle',
|
||||
createdBy: 'some@user.com',
|
||||
@ -101,6 +104,7 @@ test('Should call slack webhook for archived toggle with project info', async ()
|
||||
const event: IEvent = {
|
||||
id: 2,
|
||||
createdAt: new Date(),
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
type: FEATURE_ARCHIVED,
|
||||
featureName: 'some-toggle',
|
||||
project: 'some-project',
|
||||
@ -129,6 +133,7 @@ test(`Should call webhook for toggled environment`, async () => {
|
||||
const event: IEvent = {
|
||||
id: 2,
|
||||
createdAt: new Date(),
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
type: FEATURE_ENVIRONMENT_DISABLED,
|
||||
createdBy: 'some@user.com',
|
||||
environment: 'development',
|
||||
@ -159,6 +164,7 @@ test('Should use default channel', async () => {
|
||||
const event: IEvent = {
|
||||
id: 3,
|
||||
createdAt: new Date(),
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
featureName: 'some-toggle',
|
||||
@ -189,6 +195,7 @@ test('Should override default channel with data from tag', async () => {
|
||||
const event: IEvent = {
|
||||
id: 4,
|
||||
createdAt: new Date(),
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
featureName: 'some-toggle',
|
||||
@ -225,6 +232,7 @@ test('Should post to all channels in tags', async () => {
|
||||
const event: IEvent = {
|
||||
id: 5,
|
||||
createdAt: new Date(),
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
featureName: 'some-toggle',
|
||||
@ -269,6 +277,7 @@ test('Should include custom headers from parameters in call to service', async (
|
||||
id: 2,
|
||||
createdAt: new Date(),
|
||||
type: FEATURE_ENVIRONMENT_DISABLED,
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
createdBy: 'some@user.com',
|
||||
environment: 'development',
|
||||
project: 'default',
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
import TeamsAddon from './teams';
|
||||
|
||||
import noLogger from '../../test/fixtures/no-logger';
|
||||
import { SYSTEM_USER_ID } from '../types';
|
||||
|
||||
let fetchRetryCalls: any[];
|
||||
|
||||
@ -45,6 +46,7 @@ test('Should call teams webhook', async () => {
|
||||
id: 1,
|
||||
createdAt: new Date(),
|
||||
type: FEATURE_CREATED,
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
createdBy: 'some@user.com',
|
||||
featureName: 'some-toggle',
|
||||
data: {
|
||||
@ -72,6 +74,7 @@ test('Should call teams webhook for archived toggle', async () => {
|
||||
const event: IEvent = {
|
||||
id: 1,
|
||||
createdAt: new Date(),
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
type: FEATURE_ARCHIVED,
|
||||
createdBy: 'some@user.com',
|
||||
featureName: 'some-toggle',
|
||||
@ -98,6 +101,7 @@ test('Should call teams webhook for archived toggle with project info', async ()
|
||||
const event: IEvent = {
|
||||
id: 1,
|
||||
createdAt: new Date(),
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
type: FEATURE_ARCHIVED,
|
||||
createdBy: 'some@user.com',
|
||||
featureName: 'some-toggle',
|
||||
@ -125,6 +129,7 @@ test(`Should call teams webhook for toggled environment`, async () => {
|
||||
const event: IEvent = {
|
||||
id: 2,
|
||||
createdAt: new Date(),
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
type: FEATURE_ENVIRONMENT_DISABLED,
|
||||
createdBy: 'some@user.com',
|
||||
environment: 'development',
|
||||
@ -154,6 +159,7 @@ test('Should include custom headers in call to teams', async () => {
|
||||
const event: IEvent = {
|
||||
id: 2,
|
||||
createdAt: new Date(),
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
type: FEATURE_ENVIRONMENT_DISABLED,
|
||||
createdBy: 'some@user.com',
|
||||
environment: 'development',
|
||||
|
||||
@ -5,6 +5,7 @@ import { FEATURE_CREATED, IEvent } from '../types/events';
|
||||
import WebhookAddon from './webhook';
|
||||
|
||||
import noLogger from '../../test/fixtures/no-logger';
|
||||
import { SYSTEM_USER_ID } from '../types';
|
||||
|
||||
let fetchRetryCalls: any[] = [];
|
||||
|
||||
@ -36,6 +37,7 @@ test('Should handle event without "bodyTemplate"', () => {
|
||||
const event: IEvent = {
|
||||
id: 1,
|
||||
createdAt: new Date(),
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
featureName: 'some-toggle',
|
||||
@ -61,6 +63,7 @@ test('Should format event with "bodyTemplate"', () => {
|
||||
const event: IEvent = {
|
||||
id: 1,
|
||||
createdAt: new Date(),
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
featureName: 'some-toggle',
|
||||
@ -90,6 +93,7 @@ test('Should format event with "authorization"', () => {
|
||||
const event: IEvent = {
|
||||
id: 1,
|
||||
createdAt: new Date(),
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
featureName: 'some-toggle',
|
||||
@ -120,6 +124,7 @@ test('Should handle custom headers', async () => {
|
||||
const event: IEvent = {
|
||||
id: 1,
|
||||
createdAt: new Date(),
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
featureName: 'some-toggle',
|
||||
|
||||
@ -20,6 +20,7 @@ const EVENT_COLUMNS = [
|
||||
'type',
|
||||
'created_by',
|
||||
'created_at',
|
||||
'created_by_user_id',
|
||||
'data',
|
||||
'pre_data',
|
||||
'tags',
|
||||
@ -74,6 +75,7 @@ export interface IEventTable {
|
||||
type: string;
|
||||
created_by: string;
|
||||
created_at: Date;
|
||||
created_by_user_id: number;
|
||||
data?: any;
|
||||
pre_data?: any;
|
||||
feature_name?: string;
|
||||
@ -364,6 +366,7 @@ class EventStore implements IEventStore {
|
||||
type: row.type as IEventType,
|
||||
createdBy: row.created_by,
|
||||
createdAt: row.created_at,
|
||||
createdByUserId: row.created_by_user_id,
|
||||
data: row.data,
|
||||
preData: row.pre_data,
|
||||
tags: row.tags || [],
|
||||
@ -377,6 +380,7 @@ class EventStore implements IEventStore {
|
||||
return {
|
||||
type: e.type,
|
||||
created_by: e.createdBy ?? 'admin',
|
||||
created_by_user_id: e.createdByUserId,
|
||||
data: Array.isArray(e.data) ? JSON.stringify(e.data) : e.data,
|
||||
pre_data: Array.isArray(e.preData)
|
||||
? JSON.stringify(e.preData)
|
||||
|
||||
@ -50,6 +50,7 @@ export class DependentFeaturesService {
|
||||
projectId,
|
||||
}: { featureName: string; newFeatureName: string; projectId: string },
|
||||
user: string,
|
||||
userId: number,
|
||||
) {
|
||||
const parents =
|
||||
await this.dependentFeaturesReadModel.getParents(featureName);
|
||||
@ -63,6 +64,7 @@ export class DependentFeaturesService {
|
||||
variants: parent.variants,
|
||||
},
|
||||
user,
|
||||
userId,
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -79,6 +81,7 @@ export class DependentFeaturesService {
|
||||
{ child, projectId },
|
||||
dependentFeature,
|
||||
extractUsernameFromUser(user),
|
||||
user.id,
|
||||
);
|
||||
}
|
||||
|
||||
@ -86,6 +89,7 @@ export class DependentFeaturesService {
|
||||
{ child, projectId }: { child: string; projectId: string },
|
||||
dependentFeature: CreateDependentFeatureSchema,
|
||||
user: string,
|
||||
userId: number,
|
||||
): Promise<void> {
|
||||
const { enabled, feature: parent, variants } = dependentFeature;
|
||||
|
||||
@ -146,6 +150,7 @@ export class DependentFeaturesService {
|
||||
project: projectId,
|
||||
featureName: child,
|
||||
createdBy: user,
|
||||
createdByUserId: userId,
|
||||
data: {
|
||||
feature: parent,
|
||||
enabled: featureDependency.enabled,
|
||||
@ -165,6 +170,7 @@ export class DependentFeaturesService {
|
||||
dependency,
|
||||
projectId,
|
||||
extractUsernameFromUser(user),
|
||||
user.id,
|
||||
);
|
||||
}
|
||||
|
||||
@ -172,6 +178,7 @@ export class DependentFeaturesService {
|
||||
dependency: FeatureDependencyId,
|
||||
projectId: string,
|
||||
user: string,
|
||||
userId: number,
|
||||
): Promise<void> {
|
||||
await this.dependentFeaturesStore.delete(dependency);
|
||||
await this.eventService.storeEvent({
|
||||
@ -179,6 +186,7 @@ export class DependentFeaturesService {
|
||||
project: projectId,
|
||||
featureName: dependency.child,
|
||||
createdBy: user,
|
||||
createdByUserId: userId,
|
||||
data: { feature: dependency.parent },
|
||||
});
|
||||
}
|
||||
@ -194,6 +202,7 @@ export class DependentFeaturesService {
|
||||
features,
|
||||
projectId,
|
||||
extractUsernameFromUser(user),
|
||||
user.id,
|
||||
);
|
||||
}
|
||||
|
||||
@ -201,6 +210,7 @@ export class DependentFeaturesService {
|
||||
features: string[],
|
||||
projectId: string,
|
||||
user: string,
|
||||
userId: number,
|
||||
): Promise<void> {
|
||||
await this.dependentFeaturesStore.deleteAll(features);
|
||||
await this.eventService.storeEvents(
|
||||
@ -209,6 +219,7 @@ export class DependentFeaturesService {
|
||||
project: projectId,
|
||||
featureName: feature,
|
||||
createdBy: user,
|
||||
createdByUserId: userId,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
@ -120,7 +120,11 @@ class ExportImportController extends Controller {
|
||||
const query = req.body;
|
||||
const userName = extractUsername(req);
|
||||
|
||||
const data = await this.exportService.export(query, userName);
|
||||
const data = await this.exportService.export(
|
||||
query,
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
|
||||
this.openApiService.respondWithValidation(
|
||||
200,
|
||||
|
||||
@ -67,6 +67,7 @@ export type IExportService = {
|
||||
export(
|
||||
query: ExportQuerySchema,
|
||||
userName: string,
|
||||
userId: number,
|
||||
): Promise<ExportResultSchema>;
|
||||
};
|
||||
|
||||
@ -287,6 +288,7 @@ export default class ExportImportService
|
||||
environment: cleanedDto.environment,
|
||||
type: FEATURES_IMPORTED,
|
||||
createdBy: extractUsernameFromUser(user),
|
||||
createdByUserId: user.id,
|
||||
});
|
||||
}
|
||||
|
||||
@ -387,6 +389,7 @@ export default class ExportImportService
|
||||
value: tag.tagValue,
|
||||
},
|
||||
extractUsernameFromUser(user),
|
||||
user.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -404,6 +407,7 @@ export default class ExportImportService
|
||||
stickiness: contextField.stickiness,
|
||||
},
|
||||
extractUsernameFromUser(user),
|
||||
user.id,
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -417,6 +421,7 @@ export default class ExportImportService
|
||||
? this.tagTypeService.createTagType(
|
||||
tagType,
|
||||
extractUsernameFromUser(user),
|
||||
user.id,
|
||||
)
|
||||
: Promise.resolve();
|
||||
}),
|
||||
@ -457,6 +462,7 @@ export default class ExportImportService
|
||||
rest as FeatureToggleDTO,
|
||||
username,
|
||||
feature.name,
|
||||
user.id,
|
||||
);
|
||||
} else {
|
||||
await this.featureToggleService.validateName(feature.name);
|
||||
@ -465,6 +471,7 @@ export default class ExportImportService
|
||||
dto.project,
|
||||
rest as FeatureToggleDTO,
|
||||
username,
|
||||
user.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -777,6 +784,7 @@ export default class ExportImportService
|
||||
async export(
|
||||
query: ExportQuerySchema,
|
||||
userName: string,
|
||||
userId: number,
|
||||
): Promise<ExportResultSchema> {
|
||||
const featureNames =
|
||||
typeof query.tag === 'string'
|
||||
@ -901,6 +909,7 @@ export default class ExportImportService
|
||||
await this.eventService.storeEvent({
|
||||
type: FEATURES_EXPORTED,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: result,
|
||||
});
|
||||
|
||||
|
||||
@ -20,7 +20,6 @@ import {
|
||||
import { DEFAULT_ENV } from '../../util';
|
||||
import {
|
||||
ContextFieldSchema,
|
||||
CreateDependentFeatureSchema,
|
||||
ImportTogglesSchema,
|
||||
UpsertSegmentSchema,
|
||||
VariantsSchema,
|
||||
@ -63,11 +62,13 @@ const createToggle = async (
|
||||
tags: string[] = [],
|
||||
projectId: string = 'default',
|
||||
username: string = 'test',
|
||||
userId: number = -9999,
|
||||
) => {
|
||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||
projectId,
|
||||
toggle,
|
||||
username,
|
||||
-9999,
|
||||
);
|
||||
if (strategy) {
|
||||
await app.services.featureToggleServiceV2.createStrategy(
|
||||
@ -89,6 +90,7 @@ const createToggle = async (
|
||||
value: tag,
|
||||
},
|
||||
username,
|
||||
userId,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
@ -176,7 +176,7 @@ export default class ArchiveController extends Controller {
|
||||
): Promise<void> {
|
||||
const { featureName } = req.params;
|
||||
const user = extractUsername(req);
|
||||
await this.featureService.deleteFeature(featureName, user);
|
||||
await this.featureService.deleteFeature(featureName, user, req.user.id);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
@ -191,6 +191,7 @@ export default class ArchiveController extends Controller {
|
||||
this.transactionalFeatureToggleService(tx).reviveFeature(
|
||||
featureName,
|
||||
userName,
|
||||
req.user.id,
|
||||
),
|
||||
);
|
||||
res.status(200).end();
|
||||
|
||||
@ -659,6 +659,7 @@ export default class ProjectFeaturesController extends Controller {
|
||||
projectId,
|
||||
name,
|
||||
userName,
|
||||
req.user.id,
|
||||
replaceGroupId,
|
||||
);
|
||||
|
||||
@ -684,6 +685,7 @@ export default class ProjectFeaturesController extends Controller {
|
||||
description: req.body.description || undefined,
|
||||
},
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
|
||||
this.openApiService.respondWithValidation(
|
||||
@ -733,6 +735,7 @@ export default class ProjectFeaturesController extends Controller {
|
||||
},
|
||||
userName,
|
||||
featureName,
|
||||
req.user.id,
|
||||
);
|
||||
|
||||
this.openApiService.respondWithValidation(
|
||||
@ -758,6 +761,7 @@ export default class ProjectFeaturesController extends Controller {
|
||||
featureName,
|
||||
extractUsername(req),
|
||||
req.body,
|
||||
req.user.id,
|
||||
);
|
||||
this.openApiService.respondWithValidation(
|
||||
200,
|
||||
@ -800,6 +804,7 @@ export default class ProjectFeaturesController extends Controller {
|
||||
stale,
|
||||
userName,
|
||||
projectId,
|
||||
req.user.id,
|
||||
);
|
||||
res.status(202).end();
|
||||
}
|
||||
@ -1108,6 +1113,7 @@ export default class ProjectFeaturesController extends Controller {
|
||||
value,
|
||||
{ environment, projectId, featureName },
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
res.status(200).json(updatedStrategy);
|
||||
}
|
||||
@ -1123,6 +1129,7 @@ export default class ProjectFeaturesController extends Controller {
|
||||
tags.addedTags,
|
||||
tags.removedTags,
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
@ -44,6 +44,7 @@ import {
|
||||
SKIP_CHANGE_REQUEST,
|
||||
StrategiesOrderChangedEvent,
|
||||
StrategyIds,
|
||||
SYSTEM_USER_ID,
|
||||
Unsaved,
|
||||
WeightType,
|
||||
} from '../../types';
|
||||
@ -403,6 +404,7 @@ class FeatureToggleService {
|
||||
featureName: string,
|
||||
createdBy: string,
|
||||
operations: Operation[],
|
||||
createdByUserId: number,
|
||||
): Promise<FeatureToggle> {
|
||||
const featureToggle = await this.getFeatureMetadata(featureName);
|
||||
|
||||
@ -421,6 +423,7 @@ class FeatureToggleService {
|
||||
newDocument,
|
||||
createdBy,
|
||||
featureName,
|
||||
createdByUserId,
|
||||
);
|
||||
|
||||
if (featureToggle.stale !== newDocument.stale) {
|
||||
@ -430,6 +433,7 @@ class FeatureToggleService {
|
||||
project,
|
||||
featureName,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@ -472,6 +476,7 @@ class FeatureToggleService {
|
||||
context,
|
||||
sortOrders,
|
||||
createdBy,
|
||||
user?.id || SYSTEM_USER_ID,
|
||||
);
|
||||
}
|
||||
|
||||
@ -479,6 +484,7 @@ class FeatureToggleService {
|
||||
context: IFeatureStrategyContext,
|
||||
sortOrders: SetStrategySortOrderSchema,
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<Saved<any>> {
|
||||
const { featureName, environment, projectId: project } = context;
|
||||
const existingOrder = (
|
||||
@ -536,6 +542,7 @@ class FeatureToggleService {
|
||||
createdBy,
|
||||
preData: eventPreData,
|
||||
data: eventData,
|
||||
createdByUserId,
|
||||
});
|
||||
await this.eventService.storeEvent(event);
|
||||
}
|
||||
@ -555,6 +562,7 @@ class FeatureToggleService {
|
||||
strategyConfig,
|
||||
context,
|
||||
createdBy,
|
||||
user?.id || SYSTEM_USER_ID,
|
||||
);
|
||||
}
|
||||
|
||||
@ -562,6 +570,7 @@ class FeatureToggleService {
|
||||
strategyConfig: Unsaved<IStrategyConfig>,
|
||||
context: IFeatureStrategyContext,
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<Saved<IStrategyConfig>> {
|
||||
const { featureName, projectId, environment } = context;
|
||||
await this.validateFeatureBelongsToProject(context);
|
||||
@ -638,6 +647,7 @@ class FeatureToggleService {
|
||||
createdBy,
|
||||
environment,
|
||||
data: strategy,
|
||||
createdByUserId,
|
||||
}),
|
||||
);
|
||||
return strategy;
|
||||
@ -674,7 +684,13 @@ class FeatureToggleService {
|
||||
context.environment,
|
||||
user,
|
||||
);
|
||||
return this.unprotectedUpdateStrategy(id, updates, context, userName);
|
||||
return this.unprotectedUpdateStrategy(
|
||||
id,
|
||||
updates,
|
||||
context,
|
||||
userName,
|
||||
user,
|
||||
);
|
||||
}
|
||||
|
||||
async optionallyDisableFeature(
|
||||
@ -682,6 +698,7 @@ class FeatureToggleService {
|
||||
environment: string,
|
||||
projectId: string,
|
||||
userName: string,
|
||||
user?: IUser,
|
||||
): Promise<void> {
|
||||
const feature = await this.getFeature({ featureName });
|
||||
|
||||
@ -696,6 +713,7 @@ class FeatureToggleService {
|
||||
environment,
|
||||
false,
|
||||
userName,
|
||||
user,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -705,6 +723,7 @@ class FeatureToggleService {
|
||||
updates: Partial<IStrategyConfig>,
|
||||
context: IFeatureStrategyContext,
|
||||
userName: string,
|
||||
user?: IUser,
|
||||
): Promise<Saved<IStrategyConfig>> {
|
||||
const { projectId, environment, featureName } = context;
|
||||
const existingStrategy = await this.featureStrategiesStore.get(id);
|
||||
@ -760,6 +779,7 @@ class FeatureToggleService {
|
||||
createdBy: userName,
|
||||
data,
|
||||
preData,
|
||||
createdByUserId: user?.id || SYSTEM_USER_ID,
|
||||
}),
|
||||
);
|
||||
await this.optionallyDisableFeature(
|
||||
@ -767,6 +787,7 @@ class FeatureToggleService {
|
||||
environment,
|
||||
projectId,
|
||||
userName,
|
||||
user,
|
||||
);
|
||||
return data;
|
||||
}
|
||||
@ -779,6 +800,7 @@ class FeatureToggleService {
|
||||
value: string | number,
|
||||
context: IFeatureStrategyContext,
|
||||
userName: string,
|
||||
createdByUserId: number,
|
||||
): Promise<Saved<IStrategyConfig>> {
|
||||
const { projectId, environment, featureName } = context;
|
||||
|
||||
@ -809,6 +831,7 @@ class FeatureToggleService {
|
||||
createdBy: userName,
|
||||
data,
|
||||
preData,
|
||||
createdByUserId,
|
||||
}),
|
||||
);
|
||||
return data;
|
||||
@ -837,13 +860,14 @@ class FeatureToggleService {
|
||||
context.environment,
|
||||
user,
|
||||
);
|
||||
return this.unprotectedDeleteStrategy(id, context, createdBy);
|
||||
return this.unprotectedDeleteStrategy(id, context, createdBy, user);
|
||||
}
|
||||
|
||||
async unprotectedDeleteStrategy(
|
||||
id: string,
|
||||
context: IFeatureStrategyContext,
|
||||
createdBy: string,
|
||||
createdByUser?: IUser,
|
||||
): Promise<void> {
|
||||
const existingStrategy = await this.featureStrategiesStore.get(id);
|
||||
const { featureName, projectId, environment } = context;
|
||||
@ -870,6 +894,7 @@ class FeatureToggleService {
|
||||
environment,
|
||||
false,
|
||||
createdBy,
|
||||
createdByUser,
|
||||
);
|
||||
}
|
||||
|
||||
@ -881,6 +906,7 @@ class FeatureToggleService {
|
||||
project: projectId,
|
||||
environment,
|
||||
createdBy,
|
||||
createdByUserId: createdByUser?.id || SYSTEM_USER_ID,
|
||||
preData,
|
||||
}),
|
||||
);
|
||||
@ -1053,6 +1079,7 @@ class FeatureToggleService {
|
||||
*
|
||||
* Used to retrieve metadata of all feature toggles defined in Unleash.
|
||||
* @param query - Allow you to limit search based on criteria such as project, tags, namePrefix. See @IFeatureToggleQuery
|
||||
* @param userId - Used to find / mark features as favorite based on users preferences
|
||||
* @param archived - Return archived or active toggles
|
||||
* @returns
|
||||
*/
|
||||
@ -1106,6 +1133,7 @@ class FeatureToggleService {
|
||||
projectId: string,
|
||||
value: FeatureToggleDTO,
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
isValidated: boolean = false,
|
||||
): Promise<FeatureToggle> {
|
||||
this.logger.info(`${createdBy} creates feature toggle ${value.name}`);
|
||||
@ -1155,6 +1183,7 @@ class FeatureToggleService {
|
||||
createdBy,
|
||||
project: projectId,
|
||||
data: createdToggle,
|
||||
createdByUserId,
|
||||
}),
|
||||
);
|
||||
|
||||
@ -1232,6 +1261,7 @@ class FeatureToggleService {
|
||||
projectId: string,
|
||||
newFeatureName: string,
|
||||
userName: string,
|
||||
userId: number,
|
||||
replaceGroupId: boolean = true,
|
||||
): Promise<FeatureToggle> {
|
||||
const changeRequestEnabled =
|
||||
@ -1262,6 +1292,7 @@ class FeatureToggleService {
|
||||
projectId,
|
||||
newToggle,
|
||||
userName,
|
||||
userId,
|
||||
);
|
||||
|
||||
const variantTasks = newToggle.environments.map((e) => {
|
||||
@ -1294,6 +1325,7 @@ class FeatureToggleService {
|
||||
this.dependentFeaturesService.cloneDependencies(
|
||||
{ featureName, newFeatureName, projectId },
|
||||
userName,
|
||||
userId,
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
@ -1310,6 +1342,7 @@ class FeatureToggleService {
|
||||
updatedFeature: FeatureToggleDTO,
|
||||
userName: string,
|
||||
featureName: string,
|
||||
userId: number,
|
||||
): Promise<FeatureToggle> {
|
||||
await this.validateFeatureBelongsToProject({ featureName, projectId });
|
||||
|
||||
@ -1328,6 +1361,7 @@ class FeatureToggleService {
|
||||
await this.eventService.storeEvent(
|
||||
new FeatureMetadataUpdateEvent({
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: featureToggle,
|
||||
preData,
|
||||
featureName,
|
||||
@ -1452,6 +1486,7 @@ class FeatureToggleService {
|
||||
featureName: string,
|
||||
isStale: boolean,
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<any> {
|
||||
const feature = await this.featureToggleStore.get(featureName);
|
||||
const { project } = feature;
|
||||
@ -1464,6 +1499,7 @@ class FeatureToggleService {
|
||||
project,
|
||||
featureName,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
}),
|
||||
);
|
||||
|
||||
@ -1485,6 +1521,7 @@ class FeatureToggleService {
|
||||
await this.unprotectedArchiveToggle(
|
||||
featureName,
|
||||
extractUsernameFromUser(user),
|
||||
user.id,
|
||||
projectId,
|
||||
);
|
||||
}
|
||||
@ -1492,6 +1529,7 @@ class FeatureToggleService {
|
||||
async unprotectedArchiveToggle(
|
||||
featureName: string,
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
projectId?: string,
|
||||
): Promise<void> {
|
||||
const feature = await this.featureToggleStore.get(featureName);
|
||||
@ -1512,6 +1550,7 @@ class FeatureToggleService {
|
||||
[featureName],
|
||||
projectId,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1519,6 +1558,7 @@ class FeatureToggleService {
|
||||
new FeatureArchivedEvent({
|
||||
featureName,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
project: feature.project,
|
||||
}),
|
||||
);
|
||||
@ -1534,6 +1574,7 @@ class FeatureToggleService {
|
||||
featureNames,
|
||||
extractUsernameFromUser(user),
|
||||
projectId,
|
||||
user.id,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1559,6 +1600,7 @@ class FeatureToggleService {
|
||||
featureNames: string[],
|
||||
createdBy: string,
|
||||
projectId: string,
|
||||
createdByUserId: number,
|
||||
): Promise<void> {
|
||||
await Promise.all([
|
||||
this.validateFeaturesContext(featureNames, projectId),
|
||||
@ -1572,6 +1614,7 @@ class FeatureToggleService {
|
||||
featureNames,
|
||||
projectId,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
);
|
||||
|
||||
await this.eventService.storeEvents(
|
||||
@ -1580,6 +1623,7 @@ class FeatureToggleService {
|
||||
new FeatureArchivedEvent({
|
||||
featureName: feature.name,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
project: feature.project,
|
||||
}),
|
||||
),
|
||||
@ -1591,6 +1635,7 @@ class FeatureToggleService {
|
||||
stale: boolean,
|
||||
createdBy: string,
|
||||
projectId: string,
|
||||
createdByUserId: number,
|
||||
): Promise<void> {
|
||||
await this.validateFeaturesContext(featureNames, projectId);
|
||||
|
||||
@ -1612,6 +1657,7 @@ class FeatureToggleService {
|
||||
project: projectId,
|
||||
featureName: feature.name,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
}),
|
||||
),
|
||||
);
|
||||
@ -1666,6 +1712,7 @@ class FeatureToggleService {
|
||||
environment,
|
||||
enabled,
|
||||
createdBy,
|
||||
user,
|
||||
shouldActivateDisabledStrategies,
|
||||
);
|
||||
}
|
||||
@ -1676,6 +1723,7 @@ class FeatureToggleService {
|
||||
environment: string,
|
||||
enabled: boolean,
|
||||
createdBy: string,
|
||||
user?: IUser,
|
||||
shouldActivateDisabledStrategies = false,
|
||||
): Promise<FeatureToggle> {
|
||||
const hasEnvironment =
|
||||
@ -1712,6 +1760,7 @@ class FeatureToggleService {
|
||||
featureName,
|
||||
},
|
||||
createdBy,
|
||||
user,
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -1746,6 +1795,7 @@ class FeatureToggleService {
|
||||
featureName,
|
||||
},
|
||||
createdBy,
|
||||
user?.id || SYSTEM_USER_ID,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1765,6 +1815,7 @@ class FeatureToggleService {
|
||||
featureName,
|
||||
environment,
|
||||
createdBy,
|
||||
createdByUserId: user?.id || SYSTEM_USER_ID,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@ -1775,6 +1826,7 @@ class FeatureToggleService {
|
||||
async storeFeatureUpdatedEventLegacy(
|
||||
featureName: string,
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<FeatureToggleLegacy> {
|
||||
const feature = await this.getFeatureToggleLegacy(featureName);
|
||||
|
||||
@ -1783,6 +1835,7 @@ class FeatureToggleService {
|
||||
await this.eventService.storeEvent({
|
||||
type: FEATURE_UPDATED,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
featureName,
|
||||
data: feature,
|
||||
project: feature.project,
|
||||
@ -1831,6 +1884,7 @@ class FeatureToggleService {
|
||||
featureName: string,
|
||||
newProject: string,
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<void> {
|
||||
const changeRequestEnabled =
|
||||
await this.changeRequestAccessReadModel.isChangeRequestsEnabledForProject(
|
||||
@ -1858,6 +1912,7 @@ class FeatureToggleService {
|
||||
await this.eventService.storeEvent(
|
||||
new FeatureChangeProjectEvent({
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
oldProject,
|
||||
newProject,
|
||||
featureName,
|
||||
@ -1870,7 +1925,11 @@ class FeatureToggleService {
|
||||
}
|
||||
|
||||
// TODO: add project id.
|
||||
async deleteFeature(featureName: string, createdBy: string): Promise<void> {
|
||||
async deleteFeature(
|
||||
featureName: string,
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<void> {
|
||||
await this.validateNoChildren(featureName);
|
||||
const toggle = await this.featureToggleStore.get(featureName);
|
||||
const tags = await this.tagStore.getAllTagsForFeature(featureName);
|
||||
@ -1881,6 +1940,7 @@ class FeatureToggleService {
|
||||
featureName,
|
||||
project: toggle.project,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
preData: toggle,
|
||||
tags,
|
||||
}),
|
||||
@ -1891,6 +1951,7 @@ class FeatureToggleService {
|
||||
featureNames: string[],
|
||||
projectId: string,
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<void> {
|
||||
await this.validateFeaturesContext(featureNames, projectId);
|
||||
await this.validateNoOrphanParents(featureNames);
|
||||
@ -1912,6 +1973,7 @@ class FeatureToggleService {
|
||||
new FeatureDeletedEvent({
|
||||
featureName: feature.name,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
project: feature.project,
|
||||
preData: feature,
|
||||
tags: tags
|
||||
@ -1929,6 +1991,7 @@ class FeatureToggleService {
|
||||
featureNames: string[],
|
||||
projectId: string,
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<void> {
|
||||
await this.validateFeaturesContext(featureNames, projectId);
|
||||
|
||||
@ -1952,6 +2015,7 @@ class FeatureToggleService {
|
||||
new FeatureRevivedEvent({
|
||||
featureName: feature.name,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
project: feature.project,
|
||||
}),
|
||||
),
|
||||
@ -1959,7 +2023,11 @@ class FeatureToggleService {
|
||||
}
|
||||
|
||||
// TODO: add project id.
|
||||
async reviveFeature(featureName: string, createdBy: string): Promise<void> {
|
||||
async reviveFeature(
|
||||
featureName: string,
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<void> {
|
||||
const toggle = await this.featureToggleStore.revive(featureName);
|
||||
await this.featureToggleStore.disableAllEnvironmentsForFeatures([
|
||||
featureName,
|
||||
@ -1967,6 +2035,7 @@ class FeatureToggleService {
|
||||
await this.eventService.storeEvent(
|
||||
new FeatureRevivedEvent({
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
featureName,
|
||||
project: toggle.project,
|
||||
}),
|
||||
@ -2072,6 +2141,7 @@ class FeatureToggleService {
|
||||
project: string,
|
||||
newVariants: IVariant[],
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<FeatureToggle> {
|
||||
await variantsArraySchema.validateAsync(newVariants);
|
||||
const fixedVariants = this.fixVariantWeights(newVariants);
|
||||
@ -2088,6 +2158,7 @@ class FeatureToggleService {
|
||||
project,
|
||||
featureName,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
oldVariants,
|
||||
newVariants: featureToggle.variants as IVariant[],
|
||||
}),
|
||||
@ -2119,6 +2190,7 @@ class FeatureToggleService {
|
||||
new EnvironmentVariantEvent({
|
||||
featureName,
|
||||
environment,
|
||||
createdByUserId: user.id,
|
||||
project: projectId,
|
||||
createdBy: user,
|
||||
oldVariants: theOldVariants,
|
||||
@ -2199,6 +2271,7 @@ class FeatureToggleService {
|
||||
createdBy: user,
|
||||
oldVariants: oldVariants[environment],
|
||||
newVariants: fixedVariants,
|
||||
createdByUserId: user.id,
|
||||
}),
|
||||
),
|
||||
);
|
||||
@ -2317,6 +2390,7 @@ class FeatureToggleService {
|
||||
({ name, project }) =>
|
||||
new PotentiallyStaleOnEvent({
|
||||
featureName: name,
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
project,
|
||||
}),
|
||||
),
|
||||
|
||||
@ -260,6 +260,7 @@ class FeatureController extends Controller {
|
||||
featureName,
|
||||
req.body,
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
res.status(201).header('location', `${featureName}/tags`).json(tag);
|
||||
}
|
||||
@ -279,13 +280,23 @@ class FeatureController extends Controller {
|
||||
|
||||
await Promise.all(
|
||||
addedTags.map((addedTag) =>
|
||||
this.tagService.addTag(featureName, addedTag, userName),
|
||||
this.tagService.addTag(
|
||||
featureName,
|
||||
addedTag,
|
||||
userName,
|
||||
req.user.id,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
removedTags.map((removedTag) =>
|
||||
this.tagService.removeTag(featureName, removedTag, userName),
|
||||
this.tagService.removeTag(
|
||||
featureName,
|
||||
removedTag,
|
||||
userName,
|
||||
req.user.id,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@ -300,7 +311,12 @@ class FeatureController extends Controller {
|
||||
): Promise<void> {
|
||||
const { featureName, type, value } = req.params;
|
||||
const userName = extractUsername(req);
|
||||
await this.tagService.removeTag(featureName, { type, value }, userName);
|
||||
await this.tagService.removeTag(
|
||||
featureName,
|
||||
{ type, value },
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
@ -328,6 +344,7 @@ class FeatureController extends Controller {
|
||||
project,
|
||||
validatedToggle,
|
||||
userName,
|
||||
req.user.id,
|
||||
true,
|
||||
);
|
||||
const strategies = await Promise.all(
|
||||
@ -351,7 +368,13 @@ class FeatureController extends Controller {
|
||||
enabled,
|
||||
userName,
|
||||
);
|
||||
await this.service.saveVariants(name, project, variants, userName);
|
||||
await this.service.saveVariants(
|
||||
name,
|
||||
project,
|
||||
variants,
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
...createdFeature,
|
||||
@ -376,6 +399,7 @@ class FeatureController extends Controller {
|
||||
value,
|
||||
userName,
|
||||
featureName,
|
||||
req.user.id,
|
||||
);
|
||||
|
||||
await this.service.removeAllStrategiesForEnv(featureName);
|
||||
@ -385,7 +409,11 @@ class FeatureController extends Controller {
|
||||
updatedFeature.strategies.map(async (s) =>
|
||||
this.service.createStrategy(
|
||||
s,
|
||||
{ projectId, featureName, environment: DEFAULT_ENV },
|
||||
{
|
||||
projectId: projectId!!,
|
||||
featureName,
|
||||
environment: DEFAULT_ENV,
|
||||
},
|
||||
userName,
|
||||
req.user,
|
||||
),
|
||||
@ -393,22 +421,25 @@ class FeatureController extends Controller {
|
||||
);
|
||||
}
|
||||
await this.service.updateEnabled(
|
||||
projectId,
|
||||
projectId!!,
|
||||
featureName,
|
||||
DEFAULT_ENV,
|
||||
updatedFeature.enabled,
|
||||
userName,
|
||||
req.user,
|
||||
);
|
||||
await this.service.saveVariants(
|
||||
featureName,
|
||||
projectId,
|
||||
projectId!!,
|
||||
value.variants || [],
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
|
||||
const feature = await this.service.storeFeatureUpdatedEventLegacy(
|
||||
featureName,
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
|
||||
res.status(200).json(feature);
|
||||
@ -432,6 +463,7 @@ class FeatureController extends Controller {
|
||||
await this.service.storeFeatureUpdatedEventLegacy(
|
||||
featureName,
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
res.status(200).json(feature);
|
||||
}
|
||||
@ -450,6 +482,7 @@ class FeatureController extends Controller {
|
||||
await this.service.storeFeatureUpdatedEventLegacy(
|
||||
featureName,
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
res.json(feature);
|
||||
}
|
||||
@ -468,6 +501,7 @@ class FeatureController extends Controller {
|
||||
await this.service.storeFeatureUpdatedEventLegacy(
|
||||
featureName,
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
res.json(feature);
|
||||
}
|
||||
@ -475,7 +509,12 @@ class FeatureController extends Controller {
|
||||
async staleOn(req: IAuthRequest, res: Response): Promise<void> {
|
||||
const { featureName } = req.params;
|
||||
const userName = extractUsername(req);
|
||||
await this.service.updateStale(featureName, true, userName);
|
||||
await this.service.updateStale(
|
||||
featureName,
|
||||
true,
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
const feature = await this.service.getFeatureToggleLegacy(featureName);
|
||||
res.json(feature);
|
||||
}
|
||||
@ -483,7 +522,12 @@ class FeatureController extends Controller {
|
||||
async staleOff(req: IAuthRequest, res: Response): Promise<void> {
|
||||
const { featureName } = req.params;
|
||||
const userName = extractUsername(req);
|
||||
await this.service.updateStale(featureName, false, userName);
|
||||
await this.service.updateStale(
|
||||
featureName,
|
||||
false,
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
const feature = await this.service.getFeatureToggleLegacy(featureName);
|
||||
res.json(feature);
|
||||
}
|
||||
|
||||
@ -9,6 +9,8 @@ import {
|
||||
IUnleashStores,
|
||||
IVariant,
|
||||
SKIP_CHANGE_REQUEST,
|
||||
SYSTEM_USER,
|
||||
SYSTEM_USER_ID,
|
||||
} from '../../../types';
|
||||
import EnvironmentService from '../../project-environments/environment-service';
|
||||
import { ForbiddenError, PatternError, PermissionError } from '../../../error';
|
||||
@ -27,7 +29,7 @@ let segmentService: ISegmentService;
|
||||
let eventService: EventService;
|
||||
let environmentService: EnvironmentService;
|
||||
let unleashConfig;
|
||||
|
||||
const TEST_USER_ID = -9999;
|
||||
const mockConstraints = (): IConstraint[] => {
|
||||
return Array.from({ length: 5 }).map(() => ({
|
||||
values: ['x', 'y', 'z'],
|
||||
@ -62,7 +64,6 @@ afterAll(async () => {
|
||||
beforeEach(async () => {
|
||||
await db.rawDatabase('change_request_settings').del();
|
||||
});
|
||||
|
||||
test('Should create feature toggle strategy configuration', async () => {
|
||||
const projectId = 'default';
|
||||
const username = 'feature-toggle';
|
||||
@ -78,6 +79,7 @@ test('Should create feature toggle strategy configuration', async () => {
|
||||
name: 'Demo',
|
||||
},
|
||||
'test',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
const createdConfig = await service.createStrategy(
|
||||
@ -106,6 +108,7 @@ test('Should be able to update existing strategy configuration', async () => {
|
||||
name: featureName,
|
||||
},
|
||||
'test',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
const createdConfig = await service.createStrategy(
|
||||
@ -142,6 +145,7 @@ test('Should be able to get strategy by id', async () => {
|
||||
name: featureName,
|
||||
},
|
||||
userName,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
const createdConfig = await service.createStrategy(
|
||||
@ -167,6 +171,7 @@ test('should ignore name in the body when updating feature toggle', async () =>
|
||||
description: 'First toggle',
|
||||
},
|
||||
userName,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
await service.createFeatureToggle(
|
||||
@ -176,6 +181,7 @@ test('should ignore name in the body when updating feature toggle', async () =>
|
||||
description: 'Second toggle',
|
||||
},
|
||||
userName,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
const update = {
|
||||
@ -183,7 +189,13 @@ test('should ignore name in the body when updating feature toggle', async () =>
|
||||
description: "I'm changed",
|
||||
};
|
||||
|
||||
await service.updateFeatureToggle(projectId, update, userName, featureName);
|
||||
await service.updateFeatureToggle(
|
||||
projectId,
|
||||
update,
|
||||
userName,
|
||||
featureName,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
const featureOne = await service.getFeature({ featureName });
|
||||
const featureTwo = await service.getFeature({
|
||||
featureName: secondFeatureName,
|
||||
@ -205,6 +217,7 @@ test('should not get empty rows as features', async () => {
|
||||
description: 'First toggle',
|
||||
},
|
||||
userName,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
await service.createFeatureToggle(
|
||||
@ -214,6 +227,7 @@ test('should not get empty rows as features', async () => {
|
||||
description: 'Second toggle',
|
||||
},
|
||||
userName,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
const user = { email: 'test@example.com' } as User;
|
||||
@ -254,6 +268,7 @@ test('adding and removing an environment preserves variants when variants per en
|
||||
],
|
||||
},
|
||||
'random_user',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
//force the variantEnvironments flag off so that we can test legacy behavior
|
||||
@ -269,9 +284,24 @@ test('adding and removing an environment preserves variants when variants per en
|
||||
eventService,
|
||||
);
|
||||
|
||||
await environmentService.addEnvironmentToProject(prodEnv, 'default');
|
||||
await environmentService.removeEnvironmentFromProject(prodEnv, 'default');
|
||||
await environmentService.addEnvironmentToProject(prodEnv, 'default');
|
||||
await environmentService.addEnvironmentToProject(
|
||||
prodEnv,
|
||||
'default',
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
await environmentService.removeEnvironmentFromProject(
|
||||
prodEnv,
|
||||
'default',
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
await environmentService.addEnvironmentToProject(
|
||||
prodEnv,
|
||||
'default',
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
|
||||
const toggle = await service.getFeature({
|
||||
featureName,
|
||||
@ -292,6 +322,7 @@ test('cloning a feature toggle copies variant environments correctly', async ()
|
||||
name: newToggleName,
|
||||
},
|
||||
'test',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
await stores.environmentStore.create({
|
||||
@ -322,6 +353,7 @@ test('cloning a feature toggle copies variant environments correctly', async ()
|
||||
'default',
|
||||
clonedToggleName,
|
||||
'test-user',
|
||||
SYSTEM_USER_ID,
|
||||
true,
|
||||
);
|
||||
|
||||
@ -335,8 +367,8 @@ test('cloning a feature toggle copies variant environments correctly', async ()
|
||||
);
|
||||
const newEnv = clonedToggle.environments.find((x) => x.name === targetEnv);
|
||||
|
||||
expect(defaultEnv.variants).toHaveLength(0);
|
||||
expect(newEnv.variants).toHaveLength(1);
|
||||
expect(defaultEnv!!.variants).toHaveLength(0);
|
||||
expect(newEnv!!.variants).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('cloning a feature toggle not allowed for change requests enabled', async () => {
|
||||
@ -350,6 +382,7 @@ test('cloning a feature toggle not allowed for change requests enabled', async (
|
||||
'default',
|
||||
'clonedToggleName',
|
||||
'test-user',
|
||||
SYSTEM_USER_ID,
|
||||
true,
|
||||
),
|
||||
).rejects.toEqual(
|
||||
@ -365,7 +398,7 @@ test('changing to a project with change requests enabled should not be allowed',
|
||||
environment: 'default',
|
||||
});
|
||||
await expect(
|
||||
service.changeProject('newToggleName', 'default', 'user'),
|
||||
service.changeProject('newToggleName', 'default', 'user', TEST_USER_ID),
|
||||
).rejects.toEqual(
|
||||
new ForbiddenError(
|
||||
`Changing project not allowed. Project default has change requests enabled.`,
|
||||
@ -393,6 +426,7 @@ test('Cloning a feature toggle also clones segments correctly', async () => {
|
||||
name: featureName,
|
||||
},
|
||||
'test-user',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
const config: Omit<FeatureStrategySchema, 'id'> = {
|
||||
@ -413,6 +447,7 @@ test('Cloning a feature toggle also clones segments correctly', async () => {
|
||||
'default',
|
||||
clonedFeatureName,
|
||||
'test-user',
|
||||
SYSTEM_USER_ID,
|
||||
true,
|
||||
);
|
||||
|
||||
@ -431,6 +466,7 @@ test('If change requests are enabled, cannot change variants without going via C
|
||||
'default',
|
||||
{ name: featureName },
|
||||
'test-user',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
// Force all feature flags on to make sure we have Change requests on
|
||||
@ -510,6 +546,7 @@ test('If CRs are protected for any environment in the project stops bulk update
|
||||
project.id,
|
||||
{ name: 'crOnVariantToggle' },
|
||||
user.username,
|
||||
user.id,
|
||||
);
|
||||
|
||||
const variant: IVariant = {
|
||||
@ -580,6 +617,7 @@ test('getPlaygroundFeatures should return ids and titles (if they exist) on clie
|
||||
name: featureName,
|
||||
},
|
||||
userName,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
await service.createStrategy(
|
||||
@ -673,6 +711,7 @@ test('Should return last seen at per environment', async () => {
|
||||
name: featureName,
|
||||
},
|
||||
userName,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
const date = await insertFeatureEnvironmentsLastSeen(
|
||||
|
||||
@ -88,6 +88,7 @@ test('Should not be possible auto-enable feature toggle without CREATE_FEATURE_S
|
||||
'default',
|
||||
{ name },
|
||||
'me',
|
||||
-9999,
|
||||
true,
|
||||
);
|
||||
|
||||
|
||||
@ -84,6 +84,7 @@ export default class MaintenanceController extends Controller {
|
||||
await this.maintenanceService.toggleMaintenanceMode(
|
||||
req.body,
|
||||
extractUsername(req),
|
||||
req.user.id,
|
||||
);
|
||||
res.status(204).end();
|
||||
}
|
||||
|
||||
@ -42,6 +42,7 @@ test('Scheduler should not run scheduled functions if maintenance mode is on', a
|
||||
await maintenanceService.toggleMaintenanceMode(
|
||||
{ enabled: true },
|
||||
'irrelevant user',
|
||||
-9999,
|
||||
);
|
||||
|
||||
const job = jest.fn();
|
||||
|
||||
@ -39,11 +39,13 @@ export default class MaintenanceService implements IMaintenanceStatus {
|
||||
async toggleMaintenanceMode(
|
||||
setting: MaintenanceSchema,
|
||||
user: string,
|
||||
toggledByUserId: number,
|
||||
): Promise<void> {
|
||||
return this.settingService.insert(
|
||||
maintenanceSettingsKey,
|
||||
setting,
|
||||
user,
|
||||
toggledByUserId,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import EnvironmentService from './environment-service';
|
||||
import { createTestConfig } from '../../../test/config/test-config';
|
||||
import dbInit from '../../../test/e2e/helpers/database-init';
|
||||
import NotFoundError from '../../error/notfound-error';
|
||||
import { IUnleashStores } from '../../types';
|
||||
import { IUnleashStores, SYSTEM_USER } from '../../types';
|
||||
import NameExistsError from '../../error/name-exists-error';
|
||||
import { EventService } from '../../services';
|
||||
|
||||
@ -53,7 +53,12 @@ test('Can connect environment to project', async () => {
|
||||
description: '',
|
||||
stale: false,
|
||||
});
|
||||
await service.addEnvironmentToProject('test-connection', 'default', 'user');
|
||||
await service.addEnvironmentToProject(
|
||||
'test-connection',
|
||||
'default',
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
const overview = await stores.featureStrategiesStore.getFeatureOverview({
|
||||
projectId: 'default',
|
||||
});
|
||||
@ -76,7 +81,8 @@ test('Can connect environment to project', async () => {
|
||||
type: 'project-environment-added',
|
||||
project: 'default',
|
||||
environment: 'test-connection',
|
||||
createdBy: 'user',
|
||||
createdBy: SYSTEM_USER.username,
|
||||
createdByUserId: SYSTEM_USER.id,
|
||||
});
|
||||
});
|
||||
|
||||
@ -88,8 +94,18 @@ test('Can remove environment from project', async () => {
|
||||
await stores.featureToggleStore.create('default', {
|
||||
name: 'removal-test',
|
||||
});
|
||||
await service.removeEnvironmentFromProject('test-connection', 'default');
|
||||
await service.addEnvironmentToProject('removal-test', 'default');
|
||||
await service.removeEnvironmentFromProject(
|
||||
'test-connection',
|
||||
'default',
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
await service.addEnvironmentToProject(
|
||||
'removal-test',
|
||||
'default',
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
let overview = await stores.featureStrategiesStore.getFeatureOverview({
|
||||
projectId: 'default',
|
||||
});
|
||||
@ -111,7 +127,8 @@ test('Can remove environment from project', async () => {
|
||||
await service.removeEnvironmentFromProject(
|
||||
'removal-test',
|
||||
'default',
|
||||
'user',
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
overview = await stores.featureStrategiesStore.getFeatureOverview({
|
||||
projectId: 'default',
|
||||
@ -125,7 +142,8 @@ test('Can remove environment from project', async () => {
|
||||
type: 'project-environment-removed',
|
||||
project: 'default',
|
||||
environment: 'removal-test',
|
||||
createdBy: 'user',
|
||||
createdBy: SYSTEM_USER.username,
|
||||
createdByUserId: SYSTEM_USER.id,
|
||||
});
|
||||
});
|
||||
|
||||
@ -134,13 +152,33 @@ test('Adding same environment twice should throw a NameExistsError', async () =>
|
||||
name: 'uniqueness-test',
|
||||
type: 'production',
|
||||
});
|
||||
await service.addEnvironmentToProject('uniqueness-test', 'default');
|
||||
await service.addEnvironmentToProject(
|
||||
'uniqueness-test',
|
||||
'default',
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
|
||||
await service.removeEnvironmentFromProject('test-connection', 'default');
|
||||
await service.removeEnvironmentFromProject('removal-test', 'default');
|
||||
await service.removeEnvironmentFromProject(
|
||||
'test-connection',
|
||||
'default',
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
await service.removeEnvironmentFromProject(
|
||||
'removal-test',
|
||||
'default',
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
|
||||
return expect(async () =>
|
||||
service.addEnvironmentToProject('uniqueness-test', 'default'),
|
||||
service.addEnvironmentToProject(
|
||||
'uniqueness-test',
|
||||
'default',
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
),
|
||||
).rejects.toThrow(
|
||||
new NameExistsError(
|
||||
'default already has the environment uniqueness-test enabled',
|
||||
@ -153,6 +191,8 @@ test('Removing environment not connected to project should be a noop', async ()
|
||||
service.removeEnvironmentFromProject(
|
||||
'some-non-existing-environment',
|
||||
'default',
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
),
|
||||
).resolves);
|
||||
|
||||
@ -247,7 +287,12 @@ test('When given overrides should remap projects to override environments', asyn
|
||||
stale: false,
|
||||
});
|
||||
|
||||
await service.addEnvironmentToProject(disabledEnvName, 'default');
|
||||
await service.addEnvironmentToProject(
|
||||
disabledEnvName,
|
||||
'default',
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
|
||||
await service.overrideEnabledProjects([enabledEnvName]);
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
IUnleashStores,
|
||||
PROJECT_ENVIRONMENT_ADDED,
|
||||
PROJECT_ENVIRONMENT_REMOVED,
|
||||
SYSTEM_USER,
|
||||
} from '../../types';
|
||||
import { Logger } from '../../logger';
|
||||
import { BadDataError, UNIQUE_CONSTRAINT_VIOLATION } from '../../error';
|
||||
@ -100,7 +101,8 @@ export default class EnvironmentService {
|
||||
async addEnvironmentToProject(
|
||||
environment: string,
|
||||
projectId: string,
|
||||
username = 'unknown',
|
||||
username: string,
|
||||
userId: number,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await this.featureEnvironmentStore.connectProject(
|
||||
@ -116,6 +118,7 @@ export default class EnvironmentService {
|
||||
project: projectId,
|
||||
environment,
|
||||
createdBy: username,
|
||||
createdByUserId: userId,
|
||||
});
|
||||
} catch (e) {
|
||||
if (e.code === UNIQUE_CONSTRAINT_VIOLATION) {
|
||||
@ -132,6 +135,7 @@ export default class EnvironmentService {
|
||||
projectId: string,
|
||||
strategy: CreateFeatureStrategySchema,
|
||||
username: string,
|
||||
userId: number,
|
||||
): Promise<CreateFeatureStrategySchema> {
|
||||
if (strategy.name !== 'flexibleRollout') {
|
||||
throw new BadDataError(
|
||||
@ -152,6 +156,7 @@ export default class EnvironmentService {
|
||||
createdBy: username,
|
||||
preData: previousDefaultStrategy,
|
||||
data: defaultStrategy,
|
||||
createdByUserId: userId,
|
||||
});
|
||||
|
||||
return defaultStrategy;
|
||||
@ -217,7 +222,12 @@ export default class EnvironmentService {
|
||||
|
||||
const linkTasks = uniqueProjects.flatMap((project) => {
|
||||
return toEnable.map((enabledEnv) => {
|
||||
return this.addEnvironmentToProject(enabledEnv.name, project);
|
||||
return this.addEnvironmentToProject(
|
||||
enabledEnv.name,
|
||||
project,
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -241,7 +251,8 @@ export default class EnvironmentService {
|
||||
async removeEnvironmentFromProject(
|
||||
environment: string,
|
||||
projectId: string,
|
||||
username = 'unknown',
|
||||
username: string,
|
||||
userId: number,
|
||||
): Promise<void> {
|
||||
const projectEnvs =
|
||||
await this.projectStore.getEnvironmentsForProject(projectId);
|
||||
@ -256,6 +267,7 @@ export default class EnvironmentService {
|
||||
project: projectId,
|
||||
environment,
|
||||
createdBy: username,
|
||||
createdByUserId: userId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
IUnleashConfig,
|
||||
IUnleashServices,
|
||||
serializeDates,
|
||||
SYSTEM_USER_ID,
|
||||
UPDATE_PROJECT,
|
||||
} from '../../types';
|
||||
import { Logger } from '../../logger';
|
||||
@ -145,6 +146,7 @@ export default class EnvironmentsController extends Controller {
|
||||
environment,
|
||||
projectId,
|
||||
extractUsername(req),
|
||||
req.user.id,
|
||||
),
|
||||
);
|
||||
|
||||
@ -162,6 +164,7 @@ export default class EnvironmentsController extends Controller {
|
||||
environment,
|
||||
projectId,
|
||||
extractUsername(req),
|
||||
req.user.id,
|
||||
),
|
||||
);
|
||||
|
||||
@ -184,6 +187,7 @@ export default class EnvironmentsController extends Controller {
|
||||
projectId,
|
||||
strategy,
|
||||
extractUsername(req),
|
||||
req.user.id || SYSTEM_USER_ID,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@ -35,6 +35,7 @@ const toggleMaintenanceMode = async (
|
||||
await maintenanceService.toggleMaintenanceMode(
|
||||
{ enabled },
|
||||
'irrelevant user',
|
||||
-9999,
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ import { Logger } from '../../logger';
|
||||
import { ITagType, ITagTypeStore } from './tag-type-store-type';
|
||||
import { IUnleashConfig } from '../../types/option';
|
||||
import EventService from '../../services/event-service';
|
||||
import { SYSTEM_USER } from '../../types';
|
||||
|
||||
export default class TagTypeService {
|
||||
private tagTypeStore: ITagTypeStore;
|
||||
@ -42,6 +43,7 @@ export default class TagTypeService {
|
||||
async createTagType(
|
||||
newTagType: ITagType,
|
||||
userName: string,
|
||||
userId: number,
|
||||
): Promise<ITagType> {
|
||||
const data = (await tagTypeSchema.validateAsync(
|
||||
newTagType,
|
||||
@ -50,7 +52,8 @@ export default class TagTypeService {
|
||||
await this.tagTypeStore.createTagType(data);
|
||||
await this.eventService.storeEvent({
|
||||
type: TAG_TYPE_CREATED,
|
||||
createdBy: userName || 'unleash-system',
|
||||
createdBy: userName || SYSTEM_USER.username,
|
||||
createdByUserId: userId,
|
||||
data,
|
||||
});
|
||||
return data;
|
||||
@ -73,12 +76,17 @@ export default class TagTypeService {
|
||||
}
|
||||
}
|
||||
|
||||
async deleteTagType(name: string, userName: string): Promise<void> {
|
||||
async deleteTagType(
|
||||
name: string,
|
||||
userName: string,
|
||||
userId: number,
|
||||
): Promise<void> {
|
||||
const tagType = await this.tagTypeStore.get(name);
|
||||
await this.tagTypeStore.delete(name);
|
||||
await this.eventService.storeEvent({
|
||||
type: TAG_TYPE_DELETED,
|
||||
createdBy: userName || 'unleash-system',
|
||||
createdBy: userName || SYSTEM_USER.username,
|
||||
createdByUserId: userId,
|
||||
preData: tagType,
|
||||
});
|
||||
}
|
||||
@ -86,12 +94,14 @@ export default class TagTypeService {
|
||||
async updateTagType(
|
||||
updatedTagType: ITagType,
|
||||
userName: string,
|
||||
userId: number,
|
||||
): Promise<ITagType> {
|
||||
const data = await tagTypeSchema.validateAsync(updatedTagType);
|
||||
await this.tagTypeStore.updateTagType(data);
|
||||
await this.eventService.storeEvent({
|
||||
type: TAG_TYPE_UPDATED,
|
||||
createdBy: userName || 'unleash-system',
|
||||
createdBy: userName || SYSTEM_USER.username,
|
||||
createdByUserId: userId,
|
||||
data,
|
||||
});
|
||||
return data;
|
||||
|
||||
@ -203,7 +203,7 @@ class TagTypeController extends Controller {
|
||||
): Promise<void> {
|
||||
const userName = extractUsername(req);
|
||||
const tagType = await this.tagTypeService.transactional((service) =>
|
||||
service.createTagType(req.body, userName),
|
||||
service.createTagType(req.body, userName, req.user.id),
|
||||
);
|
||||
res.status(201)
|
||||
.header('location', `tag-types/${tagType.name}`)
|
||||
@ -219,7 +219,11 @@ class TagTypeController extends Controller {
|
||||
const userName = extractUsername(req);
|
||||
|
||||
await this.tagTypeService.transactional((service) =>
|
||||
service.updateTagType({ name, description, icon }, userName),
|
||||
service.updateTagType(
|
||||
{ name, description, icon },
|
||||
userName,
|
||||
req.user.id,
|
||||
),
|
||||
);
|
||||
res.status(200).end();
|
||||
}
|
||||
@ -235,7 +239,7 @@ class TagTypeController extends Controller {
|
||||
const { name } = req.params;
|
||||
const userName = extractUsername(req);
|
||||
await this.tagTypeService.transactional((service) =>
|
||||
service.deleteTagType(name, userName),
|
||||
service.deleteTagType(name, userName, req.user.id),
|
||||
);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import { ISettingStore } from '../../lib/types';
|
||||
import { frontendSettingsKey } from '../../lib/types/settings/frontend-settings';
|
||||
import FakeFeatureTagStore from '../../test/fixtures/fake-feature-tag-store';
|
||||
|
||||
const TEST_USER_ID = -9999;
|
||||
const createSettingService = (
|
||||
frontendApiOrigins: string[],
|
||||
): { proxyService: ProxyService; settingStore: ISettingStore } => {
|
||||
@ -52,6 +53,7 @@ test('corsOriginMiddleware origin validation', async () => {
|
||||
proxyService.setFrontendSettings(
|
||||
{ frontendApiOrigins: ['a'] },
|
||||
userName,
|
||||
TEST_USER_ID,
|
||||
),
|
||||
).rejects.toThrow('Invalid origin: a');
|
||||
});
|
||||
@ -65,6 +67,7 @@ test('corsOriginMiddleware without config', async () => {
|
||||
await proxyService.setFrontendSettings(
|
||||
{ frontendApiOrigins: [] },
|
||||
userName,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
expect(await proxyService.getFrontendSettings(false)).toEqual({
|
||||
frontendApiOrigins: [],
|
||||
@ -72,6 +75,7 @@ test('corsOriginMiddleware without config', async () => {
|
||||
await proxyService.setFrontendSettings(
|
||||
{ frontendApiOrigins: ['*'] },
|
||||
userName,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
expect(await proxyService.getFrontendSettings(false)).toEqual({
|
||||
frontendApiOrigins: ['*'],
|
||||
@ -91,6 +95,7 @@ test('corsOriginMiddleware with config', async () => {
|
||||
await proxyService.setFrontendSettings(
|
||||
{ frontendApiOrigins: [] },
|
||||
userName,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
expect(await proxyService.getFrontendSettings(false)).toEqual({
|
||||
frontendApiOrigins: [],
|
||||
@ -98,6 +103,7 @@ test('corsOriginMiddleware with config', async () => {
|
||||
await proxyService.setFrontendSettings(
|
||||
{ frontendApiOrigins: ['https://example.com', 'https://example.org'] },
|
||||
userName,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
expect(await proxyService.getFrontendSettings(false)).toEqual({
|
||||
frontendApiOrigins: ['https://example.com', 'https://example.org'],
|
||||
@ -120,6 +126,7 @@ test('corsOriginMiddleware with caching enabled', async () => {
|
||||
await proxyService.setFrontendSettings(
|
||||
{ frontendApiOrigins: ['*'] },
|
||||
userName,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
//still get cached value
|
||||
|
||||
@ -52,6 +52,12 @@ export const eventSchema = {
|
||||
description: 'Which user created this event',
|
||||
example: 'johndoe',
|
||||
},
|
||||
createdByUserId: {
|
||||
type: 'number',
|
||||
description: 'The is of the user that created this event',
|
||||
example: 1337,
|
||||
nullable: true,
|
||||
},
|
||||
environment: {
|
||||
type: 'string',
|
||||
description:
|
||||
|
||||
@ -181,7 +181,12 @@ Note: passing \`null\` as a value for the description property will set it to an
|
||||
const createdBy = extractUsername(req);
|
||||
const data = req.body;
|
||||
|
||||
const addon = await this.addonService.updateAddon(id, data, createdBy);
|
||||
const addon = await this.addonService.updateAddon(
|
||||
id,
|
||||
data,
|
||||
createdBy,
|
||||
req.user.id,
|
||||
);
|
||||
|
||||
this.openApiService.respondWithValidation(
|
||||
200,
|
||||
@ -197,7 +202,11 @@ Note: passing \`null\` as a value for the description property will set it to an
|
||||
): Promise<void> {
|
||||
const createdBy = extractUsername(req);
|
||||
const data = req.body;
|
||||
const addon = await this.addonService.createAddon(data, createdBy);
|
||||
const addon = await this.addonService.createAddon(
|
||||
data,
|
||||
createdBy,
|
||||
req.user.id,
|
||||
);
|
||||
|
||||
this.openApiService.respondWithValidation(
|
||||
201,
|
||||
@ -213,7 +222,7 @@ Note: passing \`null\` as a value for the description property will set it to an
|
||||
): Promise<void> {
|
||||
const { id } = req.params;
|
||||
const username = extractUsername(req);
|
||||
await this.addonService.removeAddon(id, username);
|
||||
await this.addonService.removeAddon(id, username, req.user.id);
|
||||
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
@ -363,6 +363,7 @@ export class ApiTokenController extends Controller {
|
||||
token,
|
||||
new Date(expiresAt),
|
||||
extractUsername(req),
|
||||
req.user.id,
|
||||
);
|
||||
|
||||
return res.status(200).end();
|
||||
@ -393,7 +394,11 @@ export class ApiTokenController extends Controller {
|
||||
`You do not have the required access [${permissionRequired}] to perform this operation`,
|
||||
);
|
||||
}
|
||||
await this.apiTokenService.delete(token, extractUsername(req));
|
||||
await this.apiTokenService.delete(
|
||||
token,
|
||||
extractUsername(req),
|
||||
req.user.id,
|
||||
);
|
||||
await this.proxyService.deleteClientForProxyToken(token);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
@ -160,6 +160,7 @@ class ConfigController extends Controller {
|
||||
await this.proxyService.setFrontendSettings(
|
||||
req.body.frontendSettings,
|
||||
extractUsername(req),
|
||||
req.user.id,
|
||||
);
|
||||
res.sendStatus(204);
|
||||
return;
|
||||
|
||||
@ -248,6 +248,7 @@ export class ContextController extends Controller {
|
||||
const result = await this.contextService.createContextField(
|
||||
value,
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
|
||||
this.openApiService.respondWithValidation(
|
||||
@ -270,6 +271,7 @@ export class ContextController extends Controller {
|
||||
await this.contextService.updateContextField(
|
||||
{ ...contextField, name },
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
res.status(200).end();
|
||||
}
|
||||
@ -281,7 +283,11 @@ export class ContextController extends Controller {
|
||||
const name = req.params.contextField;
|
||||
const userName = extractUsername(req);
|
||||
|
||||
await this.contextService.deleteContextField(name, userName);
|
||||
await this.contextService.deleteContextField(
|
||||
name,
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ import {
|
||||
ProjectUserAddedEvent,
|
||||
ProjectUserRemovedEvent,
|
||||
} from '../../types/events';
|
||||
|
||||
const TEST_USER_ID = -9999;
|
||||
async function getSetup(anonymise: boolean = false) {
|
||||
const base = `/random${Math.round(Math.random() * 1000)}`;
|
||||
const stores = createStores();
|
||||
@ -49,6 +49,7 @@ test('should get events list via admin', async () => {
|
||||
data: { name: 'test', project: 'default' },
|
||||
featureName: 'test',
|
||||
project: 'default',
|
||||
createdByUserId: TEST_USER_ID,
|
||||
}),
|
||||
);
|
||||
const { body } = await request
|
||||
@ -68,6 +69,7 @@ test('should anonymise events list via admin', async () => {
|
||||
data: { name: 'test', project: 'default' },
|
||||
featureName: 'test',
|
||||
project: 'default',
|
||||
createdByUserId: TEST_USER_ID,
|
||||
}),
|
||||
);
|
||||
const { body } = await request
|
||||
@ -87,6 +89,7 @@ test('should also anonymise email fields in data and preData properties', async
|
||||
eventService.storeEvent(
|
||||
new ProjectUserAddedEvent({
|
||||
createdBy: 'some@email.com',
|
||||
createdByUserId: TEST_USER_ID,
|
||||
data: { name: 'test', project: 'default', email: email1 },
|
||||
project: 'default',
|
||||
}),
|
||||
@ -94,6 +97,7 @@ test('should also anonymise email fields in data and preData properties', async
|
||||
eventService.storeEvent(
|
||||
new ProjectUserRemovedEvent({
|
||||
createdBy: 'some@email.com',
|
||||
createdByUserId: TEST_USER_ID,
|
||||
preData: { name: 'test', project: 'default', email: email2 },
|
||||
project: 'default',
|
||||
}),
|
||||
@ -115,6 +119,7 @@ test('should anonymise any PII fields, no matter the depth', async () => {
|
||||
eventService.storeEvent(
|
||||
new ProjectAccessAddedEvent({
|
||||
createdBy: 'some@email.com',
|
||||
createdByUserId: TEST_USER_ID,
|
||||
data: {
|
||||
groups: [
|
||||
{
|
||||
|
||||
@ -220,7 +220,11 @@ export class ProjectApiTokenController extends Controller {
|
||||
(storedToken.projects.length === 1 &&
|
||||
storedToken.project[0] === projectId))
|
||||
) {
|
||||
await this.apiTokenService.delete(token, extractUsername(req));
|
||||
await this.apiTokenService.delete(
|
||||
token,
|
||||
extractUsername(req),
|
||||
user.id,
|
||||
);
|
||||
await this.proxyService.deleteClientForProxyToken(token);
|
||||
res.status(200).end();
|
||||
} else if (!storedToken) {
|
||||
|
||||
@ -166,7 +166,12 @@ export default class ProjectArchiveController extends Controller {
|
||||
const { projectId } = req.params;
|
||||
const { features } = req.body;
|
||||
const user = extractUsername(req);
|
||||
await this.featureService.deleteFeatures(features, projectId, user);
|
||||
await this.featureService.deleteFeatures(
|
||||
features,
|
||||
projectId,
|
||||
user,
|
||||
req.user.id,
|
||||
);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
@ -182,6 +187,7 @@ export default class ProjectArchiveController extends Controller {
|
||||
features,
|
||||
projectId,
|
||||
user,
|
||||
req.user.id,
|
||||
),
|
||||
);
|
||||
res.status(200).end();
|
||||
|
||||
@ -257,6 +257,7 @@ The backend will also distribute remaining weight up to 1000 after adding the va
|
||||
projectId,
|
||||
req.body,
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
res.status(200).json({
|
||||
version: 1,
|
||||
|
||||
@ -190,6 +190,7 @@ export class PublicSignupController extends Controller {
|
||||
await this.publicSignupTokenService.createNewPublicSignupToken(
|
||||
req.body,
|
||||
username,
|
||||
req.user.id,
|
||||
);
|
||||
this.openApiService.respondWithValidation(
|
||||
201,
|
||||
@ -219,6 +220,7 @@ export class PublicSignupController extends Controller {
|
||||
...(expiresAt ? { expiresAt: new Date(expiresAt) } : {}),
|
||||
},
|
||||
extractUsername(req),
|
||||
req.user.id,
|
||||
);
|
||||
|
||||
this.openApiService.respondWithValidation(
|
||||
|
||||
@ -122,6 +122,7 @@ class StateController extends Controller {
|
||||
userName,
|
||||
dropBeforeImport: paramToBool(drop, false),
|
||||
keepExisting: paramToBool(keep, true),
|
||||
userId: req.user.id,
|
||||
});
|
||||
res.sendStatus(202);
|
||||
}
|
||||
|
||||
@ -233,7 +233,11 @@ class StrategyController extends Controller {
|
||||
const strategyName = req.params.name;
|
||||
const userName = extractUsername(req);
|
||||
|
||||
await this.strategyService.removeStrategy(strategyName, userName);
|
||||
await this.strategyService.removeStrategy(
|
||||
strategyName,
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
@ -246,6 +250,7 @@ class StrategyController extends Controller {
|
||||
const strategy = await this.strategyService.createStrategy(
|
||||
req.body,
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
this.openApiService.respondWithValidation(
|
||||
201,
|
||||
@ -265,6 +270,7 @@ class StrategyController extends Controller {
|
||||
await this.strategyService.updateStrategy(
|
||||
{ ...req.body, name: req.params.name },
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
res.status(200).end();
|
||||
}
|
||||
@ -276,7 +282,11 @@ class StrategyController extends Controller {
|
||||
const userName = extractUsername(req);
|
||||
const { strategyName } = req.params;
|
||||
|
||||
await this.strategyService.deprecateStrategy(strategyName, userName);
|
||||
await this.strategyService.deprecateStrategy(
|
||||
strategyName,
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
@ -287,7 +297,11 @@ class StrategyController extends Controller {
|
||||
const userName = extractUsername(req);
|
||||
const { strategyName } = req.params;
|
||||
|
||||
await this.strategyService.reactivateStrategy(strategyName, userName);
|
||||
await this.strategyService.reactivateStrategy(
|
||||
strategyName,
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
res.status(200).end();
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,7 +200,11 @@ class TagController extends Controller {
|
||||
res: Response<TagWithVersionSchema>,
|
||||
): Promise<void> {
|
||||
const userName = extractUsername(req);
|
||||
const tag = await this.tagService.createTag(req.body, userName);
|
||||
const tag = await this.tagService.createTag(
|
||||
req.body,
|
||||
userName,
|
||||
req.user.id,
|
||||
);
|
||||
res.status(201)
|
||||
.header('location', `tags/${tag.type}/${tag.value}`)
|
||||
.json({ version, tag })
|
||||
@ -213,7 +217,7 @@ class TagController extends Controller {
|
||||
): Promise<void> {
|
||||
const { type, value } = req.params;
|
||||
const userName = extractUsername(req);
|
||||
await this.tagService.deleteTag({ type, value }, userName);
|
||||
await this.tagService.deleteTag({ type, value }, userName, req.user.id);
|
||||
res.status(200).end();
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
IUnleashServices,
|
||||
RoleName,
|
||||
CustomAuthHandler,
|
||||
SYSTEM_USER,
|
||||
} from './types';
|
||||
|
||||
import User, { IUser } from './types/user';
|
||||
@ -93,6 +94,7 @@ async function createApp(
|
||||
dropBeforeImport: config.import.dropBeforeImport,
|
||||
userName: 'import',
|
||||
keepExisting: config.import.keepExisting,
|
||||
userId: SYSTEM_USER.id,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ import AccessStoreMock from '../../test/fixtures/fake-access-store';
|
||||
import { GroupService } from '../services/group-service';
|
||||
import FakeEventStore from '../../test/fixtures/fake-event-store';
|
||||
import { IRole } from '../../lib/types/stores/access-store';
|
||||
import { IGroup, ROLE_CREATED } from '../../lib/types';
|
||||
import { IGroup, ROLE_CREATED, SYSTEM_USER } from '../../lib/types';
|
||||
import EventService from './event-service';
|
||||
import FakeFeatureTagStore from '../../test/fixtures/fake-feature-tag-store';
|
||||
import BadDataError from '../../lib/error/bad-data-error';
|
||||
@ -40,6 +40,7 @@ test('should fail when name exists', async () => {
|
||||
name: 'existing role',
|
||||
description: 'description',
|
||||
permissions: [],
|
||||
createdByUserId: -9999,
|
||||
});
|
||||
|
||||
expect(accessService.validateRole(existingRole)).rejects.toThrow(
|
||||
@ -172,6 +173,7 @@ test('user with custom root role should get a user root role', async () => {
|
||||
name: 'custom-root-role',
|
||||
description: 'test custom root role',
|
||||
type: CUSTOM_ROOT_ROLE_TYPE,
|
||||
createdByUserId: -9999,
|
||||
permissions: [
|
||||
{
|
||||
id: 1,
|
||||
@ -198,6 +200,7 @@ test('user with custom root role should get a user root role', async () => {
|
||||
expect(events[0]).toEqual({
|
||||
type: ROLE_CREATED,
|
||||
createdBy: 'unknown',
|
||||
createdByUserId: -9999,
|
||||
data: {
|
||||
id: 0,
|
||||
name: 'custom-root-role',
|
||||
@ -259,7 +262,7 @@ test('throws error when trying to delete a project role in use by group', async
|
||||
);
|
||||
|
||||
try {
|
||||
await accessService.deleteRole(1);
|
||||
await accessService.deleteRole(1, SYSTEM_USER.username, SYSTEM_USER.id);
|
||||
} catch (e) {
|
||||
expect(e.toString()).toBe(
|
||||
'RoleInUseError: Role is in use by users(0) or groups(1). You cannot delete a role that is in use without first removing the role from the users and groups.',
|
||||
|
||||
@ -46,6 +46,7 @@ import {
|
||||
ROLE_CREATED,
|
||||
ROLE_DELETED,
|
||||
ROLE_UPDATED,
|
||||
SYSTEM_USER,
|
||||
} from '../types';
|
||||
import EventService from './event-service';
|
||||
|
||||
@ -70,6 +71,7 @@ export interface IRoleCreation {
|
||||
type?: 'root-custom' | 'custom';
|
||||
permissions?: PermissionRef[];
|
||||
createdBy?: string;
|
||||
createdByUserId: number;
|
||||
}
|
||||
|
||||
export interface IRoleValidation {
|
||||
@ -85,6 +87,7 @@ export interface IRoleUpdate {
|
||||
type?: 'root-custom' | 'custom';
|
||||
permissions?: PermissionRef[];
|
||||
createdBy?: string;
|
||||
createdByUserId: number;
|
||||
}
|
||||
|
||||
export interface AccessWithRoles {
|
||||
@ -674,6 +677,7 @@ export class AccessService {
|
||||
this.eventService.storeEvent({
|
||||
type: ROLE_CREATED,
|
||||
createdBy: role.createdBy || 'unknown',
|
||||
createdByUserId: role.createdByUserId,
|
||||
data: {
|
||||
...newRole,
|
||||
permissions: this.sanitizePermissions(addedPermissions),
|
||||
@ -729,7 +733,8 @@ export class AccessService {
|
||||
);
|
||||
this.eventService.storeEvent({
|
||||
type: ROLE_UPDATED,
|
||||
createdBy: role.createdBy || 'unknown',
|
||||
createdBy: role.createdBy || SYSTEM_USER.username,
|
||||
createdByUserId: role.createdByUserId,
|
||||
data: {
|
||||
...updatedRole,
|
||||
permissions: this.sanitizePermissions(updatedPermissions),
|
||||
@ -754,7 +759,11 @@ export class AccessService {
|
||||
});
|
||||
}
|
||||
|
||||
async deleteRole(id: number, deletedBy = 'unknown'): Promise<void> {
|
||||
async deleteRole(
|
||||
id: number,
|
||||
deletedBy: string,
|
||||
deletedByUserId: number,
|
||||
): Promise<void> {
|
||||
await this.validateRoleIsNotBuiltIn(id);
|
||||
|
||||
const roleUsers = await this.getUsersForRole(id);
|
||||
@ -772,6 +781,7 @@ export class AccessService {
|
||||
this.eventService.storeEvent({
|
||||
type: ROLE_DELETED,
|
||||
createdBy: deletedBy,
|
||||
createdByUserId: deletedByUserId,
|
||||
preData: {
|
||||
...existingRole,
|
||||
permissions: this.sanitizePermissions(existingPermissions),
|
||||
|
||||
@ -15,9 +15,12 @@ import { IAddonDto } from '../types/stores/addon-store';
|
||||
import SimpleAddon from './addon-service-test-simple-addon';
|
||||
import { IAddonProviders } from '../addons';
|
||||
import EventService from './event-service';
|
||||
import { SYSTEM_USER } from '../types';
|
||||
|
||||
const MASKED_VALUE = '*****';
|
||||
|
||||
const TEST_USER_ID = -9999;
|
||||
|
||||
let addonProvider: IAddonProviders;
|
||||
|
||||
function getSetup() {
|
||||
@ -64,7 +67,7 @@ test('should load provider definitions', async () => {
|
||||
const simple = providerDefinitions.find((p) => p.name === 'simple');
|
||||
|
||||
expect(providerDefinitions.length).toBe(1);
|
||||
expect(simple.name).toBe('simple');
|
||||
expect(simple!.name).toBe('simple');
|
||||
});
|
||||
|
||||
test('should not allow addon-config for unknown provider', async () => {
|
||||
@ -80,6 +83,7 @@ test('should not allow addon-config for unknown provider', async () => {
|
||||
description: '',
|
||||
},
|
||||
'test',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
}).rejects.toThrow(ValidationError);
|
||||
});
|
||||
@ -98,12 +102,13 @@ test('should trigger simple-addon eventHandler', async () => {
|
||||
description: '',
|
||||
};
|
||||
|
||||
await addonService.createAddon(config, 'me@mail.com');
|
||||
await addonService.createAddon(config, 'me@mail.com', TEST_USER_ID);
|
||||
|
||||
// Feature toggle was created
|
||||
await eventService.storeEvent({
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
createdBy: SYSTEM_USER.username,
|
||||
createdByUserId: SYSTEM_USER.id,
|
||||
data: {
|
||||
name: 'some-toggle',
|
||||
enabled: false,
|
||||
@ -133,10 +138,11 @@ test('should not trigger event handler if project of event is different from add
|
||||
},
|
||||
};
|
||||
|
||||
await addonService.createAddon(config, 'me@mail.com');
|
||||
await addonService.createAddon(config, 'me@mail.com', TEST_USER_ID);
|
||||
await eventService.storeEvent({
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
createdBy: SYSTEM_USER.username,
|
||||
createdByUserId: SYSTEM_USER.id,
|
||||
project: 'someotherproject',
|
||||
data: {
|
||||
name: 'some-toggle',
|
||||
@ -166,10 +172,11 @@ test('should trigger event handler if project for event is one of the desired pr
|
||||
},
|
||||
};
|
||||
|
||||
await addonService.createAddon(config, 'me@mail.com');
|
||||
await addonService.createAddon(config, 'me@mail.com', TEST_USER_ID);
|
||||
await eventService.storeEvent({
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
createdBy: SYSTEM_USER.username,
|
||||
createdByUserId: SYSTEM_USER.id,
|
||||
project: desiredProject,
|
||||
data: {
|
||||
name: 'some-toggle',
|
||||
@ -179,7 +186,8 @@ test('should trigger event handler if project for event is one of the desired pr
|
||||
});
|
||||
await eventService.storeEvent({
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
createdBy: SYSTEM_USER.username,
|
||||
createdByUserId: SYSTEM_USER.id,
|
||||
project: otherProject,
|
||||
data: {
|
||||
name: 'other-toggle',
|
||||
@ -211,10 +219,11 @@ test('should trigger events for multiple projects if addon is setup to filter mu
|
||||
},
|
||||
};
|
||||
|
||||
await addonService.createAddon(config, 'me@mail.com');
|
||||
await addonService.createAddon(config, 'me@mail.com', TEST_USER_ID);
|
||||
await eventService.storeEvent({
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
createdBy: SYSTEM_USER.username,
|
||||
createdByUserId: SYSTEM_USER.id,
|
||||
project: desiredProjects[0],
|
||||
data: {
|
||||
name: 'some-toggle',
|
||||
@ -224,7 +233,8 @@ test('should trigger events for multiple projects if addon is setup to filter mu
|
||||
});
|
||||
await eventService.storeEvent({
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
createdBy: SYSTEM_USER.username,
|
||||
createdByUserId: SYSTEM_USER.id,
|
||||
project: otherProject,
|
||||
data: {
|
||||
name: 'other-toggle',
|
||||
@ -234,7 +244,8 @@ test('should trigger events for multiple projects if addon is setup to filter mu
|
||||
});
|
||||
await eventService.storeEvent({
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
createdBy: SYSTEM_USER.username,
|
||||
createdByUserId: SYSTEM_USER.id,
|
||||
project: desiredProjects[1],
|
||||
data: {
|
||||
name: 'third-toggle',
|
||||
@ -269,10 +280,11 @@ test('should filter events on environment if addon is setup to filter for it', a
|
||||
},
|
||||
};
|
||||
|
||||
await addonService.createAddon(config, 'me@mail.com');
|
||||
await addonService.createAddon(config, 'me@mail.com', TEST_USER_ID);
|
||||
await eventService.storeEvent({
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
createdBy: SYSTEM_USER.username,
|
||||
createdByUserId: SYSTEM_USER.id,
|
||||
project: desiredEnvironment,
|
||||
environment: desiredEnvironment,
|
||||
data: {
|
||||
@ -283,7 +295,8 @@ test('should filter events on environment if addon is setup to filter for it', a
|
||||
});
|
||||
await eventService.storeEvent({
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
createdBy: SYSTEM_USER.username,
|
||||
createdByUserId: SYSTEM_USER.id,
|
||||
environment: otherEnvironment,
|
||||
data: {
|
||||
name: 'other-toggle',
|
||||
@ -317,7 +330,8 @@ test('should not filter out global events (no specific environment) even if addo
|
||||
|
||||
const globalEventWithNoEnvironment = {
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
createdBy: SYSTEM_USER.username,
|
||||
createdByUserId: SYSTEM_USER.id,
|
||||
project: 'some-project',
|
||||
data: {
|
||||
name: 'some-toggle',
|
||||
@ -326,7 +340,7 @@ test('should not filter out global events (no specific environment) even if addo
|
||||
},
|
||||
};
|
||||
|
||||
await addonService.createAddon(config, 'me@mail.com');
|
||||
await addonService.createAddon(config, 'me@mail.com', TEST_USER_ID);
|
||||
await eventService.storeEvent(globalEventWithNoEnvironment);
|
||||
const simpleProvider = addonService.addonProviders.simple;
|
||||
// @ts-expect-error
|
||||
@ -354,7 +368,8 @@ test('should not filter out global events (no specific project) even if addon is
|
||||
|
||||
const globalEventWithNoProject = {
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
createdBy: SYSTEM_USER.username,
|
||||
createdByUserId: SYSTEM_USER.id,
|
||||
data: {
|
||||
name: 'some-toggle',
|
||||
enabled: false,
|
||||
@ -362,7 +377,7 @@ test('should not filter out global events (no specific project) even if addon is
|
||||
},
|
||||
};
|
||||
|
||||
await addonService.createAddon(config, 'me@mail.com');
|
||||
await addonService.createAddon(config, 'me@mail.com', TEST_USER_ID);
|
||||
await eventService.storeEvent(globalEventWithNoProject);
|
||||
const simpleProvider = addonService.addonProviders.simple;
|
||||
// @ts-expect-error
|
||||
@ -388,10 +403,11 @@ test('should support wildcard option for filtering addons', async () => {
|
||||
},
|
||||
};
|
||||
|
||||
await addonService.createAddon(config, 'me@mail.com');
|
||||
await addonService.createAddon(config, 'me@mail.com', TEST_USER_ID);
|
||||
await eventService.storeEvent({
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
createdBy: SYSTEM_USER.username,
|
||||
createdByUserId: SYSTEM_USER.id,
|
||||
project: desiredProjects[0],
|
||||
data: {
|
||||
name: 'some-toggle',
|
||||
@ -401,7 +417,8 @@ test('should support wildcard option for filtering addons', async () => {
|
||||
});
|
||||
await eventService.storeEvent({
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
createdBy: SYSTEM_USER.username,
|
||||
createdByUserId: SYSTEM_USER.id,
|
||||
project: otherProject,
|
||||
data: {
|
||||
name: 'other-toggle',
|
||||
@ -411,7 +428,8 @@ test('should support wildcard option for filtering addons', async () => {
|
||||
});
|
||||
await eventService.storeEvent({
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
createdBy: SYSTEM_USER.username,
|
||||
createdByUserId: SYSTEM_USER.id,
|
||||
project: desiredProjects[1],
|
||||
data: {
|
||||
name: 'third-toggle',
|
||||
@ -452,10 +470,11 @@ test('Should support filtering by both project and environment', async () => {
|
||||
'desired-toggle2',
|
||||
'desired-toggle3',
|
||||
];
|
||||
await addonService.createAddon(config, 'me@mail.com');
|
||||
await addonService.createAddon(config, 'me@mail.com', TEST_USER_ID);
|
||||
await eventService.storeEvent({
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
createdBy: SYSTEM_USER.username,
|
||||
createdByUserId: SYSTEM_USER.id,
|
||||
project: desiredProjects[0],
|
||||
environment: desiredEnvironments[0],
|
||||
data: {
|
||||
@ -466,7 +485,8 @@ test('Should support filtering by both project and environment', async () => {
|
||||
});
|
||||
await eventService.storeEvent({
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
createdBy: SYSTEM_USER.username,
|
||||
createdByUserId: SYSTEM_USER.id,
|
||||
project: desiredProjects[0],
|
||||
environment: 'wrongenvironment',
|
||||
data: {
|
||||
@ -477,7 +497,8 @@ test('Should support filtering by both project and environment', async () => {
|
||||
});
|
||||
await eventService.storeEvent({
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
createdBy: SYSTEM_USER.username,
|
||||
createdByUserId: SYSTEM_USER.id,
|
||||
project: desiredProjects[2],
|
||||
environment: desiredEnvironments[1],
|
||||
data: {
|
||||
@ -488,7 +509,8 @@ test('Should support filtering by both project and environment', async () => {
|
||||
});
|
||||
await eventService.storeEvent({
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
createdBy: SYSTEM_USER.username,
|
||||
createdByUserId: SYSTEM_USER.id,
|
||||
project: desiredProjects[2],
|
||||
environment: desiredEnvironments[2],
|
||||
data: {
|
||||
@ -499,7 +521,8 @@ test('Should support filtering by both project and environment', async () => {
|
||||
});
|
||||
await eventService.storeEvent({
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'some@user.com',
|
||||
createdBy: SYSTEM_USER.username,
|
||||
createdByUserId: SYSTEM_USER.id,
|
||||
project: 'wrongproject',
|
||||
environment: desiredEnvironments[0],
|
||||
data: {
|
||||
@ -536,7 +559,7 @@ test('should create simple-addon config', async () => {
|
||||
description: '',
|
||||
};
|
||||
|
||||
await addonService.createAddon(config, 'me@mail.com');
|
||||
await addonService.createAddon(config, 'me@mail.com', TEST_USER_ID);
|
||||
const addons = await addonService.getAddons();
|
||||
|
||||
expect(addons.length).toBe(1);
|
||||
@ -557,7 +580,7 @@ test('should create tag type for simple-addon', async () => {
|
||||
description: '',
|
||||
};
|
||||
|
||||
await addonService.createAddon(config, 'me@mail.com');
|
||||
await addonService.createAddon(config, 'me@mail.com', TEST_USER_ID);
|
||||
const tagType = await tagTypeService.getTagType('me');
|
||||
|
||||
expect(tagType.name).toBe('me');
|
||||
@ -577,7 +600,7 @@ test('should store ADDON_CONFIG_CREATE event', async () => {
|
||||
description: '',
|
||||
};
|
||||
|
||||
await addonService.createAddon(config, 'me@mail.com');
|
||||
await addonService.createAddon(config, 'me@mail.com', TEST_USER_ID);
|
||||
|
||||
const { events } = await eventService.getEvents();
|
||||
|
||||
@ -600,10 +623,19 @@ test('should store ADDON_CONFIG_UPDATE event', async () => {
|
||||
events: [FEATURE_CREATED],
|
||||
};
|
||||
|
||||
const addonConfig = await addonService.createAddon(config, 'me@mail.com');
|
||||
const addonConfig = await addonService.createAddon(
|
||||
config,
|
||||
'me@mail.com',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
const updated = { ...addonConfig, description: 'test' };
|
||||
await addonService.updateAddon(addonConfig.id, updated, 'me@mail.com');
|
||||
await addonService.updateAddon(
|
||||
addonConfig.id,
|
||||
updated,
|
||||
'me@mail.com',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
const { events } = await eventService.getEvents();
|
||||
|
||||
@ -626,9 +658,13 @@ test('should store ADDON_CONFIG_REMOVE event', async () => {
|
||||
events: [FEATURE_CREATED],
|
||||
};
|
||||
|
||||
const addonConfig = await addonService.createAddon(config, 'me@mail.com');
|
||||
const addonConfig = await addonService.createAddon(
|
||||
config,
|
||||
'me@mail.com',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
await addonService.removeAddon(addonConfig.id, 'me@mail.com');
|
||||
await addonService.removeAddon(addonConfig.id, 'me@mail.com', TEST_USER_ID);
|
||||
|
||||
const { events } = await eventService.getEvents();
|
||||
|
||||
@ -652,7 +688,11 @@ test('should hide sensitive fields when fetching', async () => {
|
||||
events: [FEATURE_CREATED],
|
||||
};
|
||||
|
||||
const createdConfig = await addonService.createAddon(config, 'me@mail.com');
|
||||
const createdConfig = await addonService.createAddon(
|
||||
config,
|
||||
'me@mail.com',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
const addons = await addonService.getAddons();
|
||||
const addonRetrieved = await addonService.getAddon(createdConfig.id);
|
||||
|
||||
@ -677,14 +717,23 @@ test('should not overwrite masked values when updating', async () => {
|
||||
description: '',
|
||||
};
|
||||
|
||||
const addonConfig = await addonService.createAddon(config, 'me@mail.com');
|
||||
const addonConfig = await addonService.createAddon(
|
||||
config,
|
||||
'me@mail.com',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
const updated = {
|
||||
...addonConfig,
|
||||
parameters: { url: MASKED_VALUE, var: 'some-new-value' },
|
||||
description: 'test',
|
||||
};
|
||||
await addonService.updateAddon(addonConfig.id, updated, 'me@mail.com');
|
||||
await addonService.updateAddon(
|
||||
addonConfig.id,
|
||||
updated,
|
||||
'me@mail.com',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
const updatedConfig = await stores.addonStore.get(addonConfig.id);
|
||||
// @ts-ignore
|
||||
@ -707,7 +756,7 @@ test('should reject addon config with missing required parameter when creating',
|
||||
};
|
||||
|
||||
await expect(async () =>
|
||||
addonService.createAddon(config, 'me@mail.com'),
|
||||
addonService.createAddon(config, 'me@mail.com', TEST_USER_ID),
|
||||
).rejects.toThrow(ValidationError);
|
||||
});
|
||||
|
||||
@ -725,14 +774,23 @@ test('should reject updating addon config with missing required parameter', asyn
|
||||
description: '',
|
||||
};
|
||||
|
||||
const config = await addonService.createAddon(addonConfig, 'me@mail.com');
|
||||
const config = await addonService.createAddon(
|
||||
addonConfig,
|
||||
'me@mail.com',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
const updated = {
|
||||
...config,
|
||||
parameters: { var: 'some-new-value' },
|
||||
description: 'test',
|
||||
};
|
||||
await expect(async () =>
|
||||
addonService.updateAddon(config.id, updated, 'me@mail.com'),
|
||||
addonService.updateAddon(
|
||||
config.id,
|
||||
updated,
|
||||
'me@mail.com',
|
||||
TEST_USER_ID,
|
||||
),
|
||||
).rejects.toThrow(ValidationError);
|
||||
});
|
||||
|
||||
@ -751,6 +809,6 @@ test('Should reject addon config if a required parameter is just the empty strin
|
||||
};
|
||||
|
||||
await expect(async () =>
|
||||
addonService.createAddon(config, 'me@mail.com'),
|
||||
addonService.createAddon(config, 'me@mail.com', TEST_USER_ID),
|
||||
).rejects.toThrow(ValidationError);
|
||||
});
|
||||
|
||||
@ -8,7 +8,7 @@ import { IFeatureToggleStore } from '../features/feature-toggle/types/feature-to
|
||||
import { Logger } from '../logger';
|
||||
import TagTypeService from '../features/tag-type/tag-type-service';
|
||||
import { IAddon, IAddonDto, IAddonStore } from '../types/stores/addon-store';
|
||||
import { IUnleashStores, IUnleashConfig } from '../types';
|
||||
import { IUnleashStores, IUnleashConfig, SYSTEM_USER } from '../types';
|
||||
import { IAddonDefinition } from '../types/model';
|
||||
import { minutesToMilliseconds } from 'date-fns';
|
||||
import EventService from './event-service';
|
||||
@ -179,6 +179,7 @@ export default class AddonService {
|
||||
await this.tagTypeService.createTagType(
|
||||
tagType,
|
||||
providerName,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
} catch (err) {
|
||||
if (!(err instanceof NameExistsError)) {
|
||||
@ -191,7 +192,11 @@ export default class AddonService {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
async createAddon(data: IAddonDto, userName: string): Promise<IAddon> {
|
||||
async createAddon(
|
||||
data: IAddonDto,
|
||||
userName: string,
|
||||
userId: number,
|
||||
): Promise<IAddon> {
|
||||
const addonConfig = await addonSchema.validateAsync(data);
|
||||
await this.validateKnownProvider(addonConfig);
|
||||
await this.validateRequiredParameters(addonConfig);
|
||||
@ -206,6 +211,7 @@ export default class AddonService {
|
||||
await this.eventService.storeEvent({
|
||||
type: events.ADDON_CONFIG_CREATED,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: omitKeys(createdAddon, 'parameters'),
|
||||
});
|
||||
|
||||
@ -216,6 +222,7 @@ export default class AddonService {
|
||||
id: number,
|
||||
data: IAddonDto,
|
||||
userName: string,
|
||||
userId: number,
|
||||
): Promise<IAddon> {
|
||||
const existingConfig = await this.addonStore.get(id); // because getting an early 404 here makes more sense
|
||||
const addonConfig = await addonSchema.validateAsync(data);
|
||||
@ -239,6 +246,7 @@ export default class AddonService {
|
||||
await this.eventService.storeEvent({
|
||||
type: events.ADDON_CONFIG_UPDATED,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
preData: omitKeys(existingConfig, 'parameters'),
|
||||
data: omitKeys(result, 'parameters'),
|
||||
});
|
||||
@ -246,12 +254,17 @@ export default class AddonService {
|
||||
return result;
|
||||
}
|
||||
|
||||
async removeAddon(id: number, userName: string): Promise<void> {
|
||||
async removeAddon(
|
||||
id: number,
|
||||
userName: string,
|
||||
removedByuserId: number,
|
||||
): Promise<void> {
|
||||
const existingConfig = await this.addonStore.get(id);
|
||||
await this.addonStore.delete(id);
|
||||
await this.eventService.storeEvent({
|
||||
type: events.ADDON_CONFIG_DELETED,
|
||||
createdBy: userName,
|
||||
createdByUserId: removedByuserId,
|
||||
preData: omitKeys(existingConfig, 'parameters'),
|
||||
});
|
||||
this.logger.info(`User ${userName} removed addon ${id}`);
|
||||
|
||||
@ -62,7 +62,7 @@ test("Shouldn't return frontend token when secret is undefined", async () => {
|
||||
secret: '*:*:some-random-string',
|
||||
type: ApiTokenType.FRONTEND,
|
||||
tokenName: 'front',
|
||||
expiresAt: null,
|
||||
expiresAt: undefined,
|
||||
};
|
||||
|
||||
const config: IUnleashConfig = createTestConfig({});
|
||||
@ -94,7 +94,6 @@ test("Shouldn't return frontend token when secret is undefined", async () => {
|
||||
await apiTokenService.createApiTokenWithProjects(token);
|
||||
await apiTokenService.fetchActiveTokens();
|
||||
|
||||
expect(apiTokenService.getUserForToken(undefined)).toEqual(undefined);
|
||||
expect(apiTokenService.getUserForToken('')).toEqual(undefined);
|
||||
});
|
||||
|
||||
@ -105,7 +104,7 @@ test('Api token operations should all have events attached', async () => {
|
||||
secret: '*:*:some-random-string',
|
||||
type: ApiTokenType.FRONTEND,
|
||||
tokenName: 'front',
|
||||
expiresAt: null,
|
||||
expiresAt: undefined,
|
||||
};
|
||||
|
||||
const config: IUnleashConfig = createTestConfig({});
|
||||
@ -135,8 +134,8 @@ test('Api token operations should all have events attached', async () => {
|
||||
);
|
||||
const saved = await apiTokenService.createApiTokenWithProjects(token);
|
||||
const newExpiry = addDays(new Date(), 30);
|
||||
await apiTokenService.updateExpiry(saved.secret, newExpiry, 'test');
|
||||
await apiTokenService.delete(saved.secret, 'test');
|
||||
await apiTokenService.updateExpiry(saved.secret, newExpiry, 'test', -9999);
|
||||
await apiTokenService.delete(saved.secret, 'test', -9999);
|
||||
const { events } = await eventService.getEvents();
|
||||
const createdApiTokenEvents = events.filter(
|
||||
(e) => e.type === API_TOKEN_CREATED,
|
||||
|
||||
@ -23,6 +23,8 @@ import {
|
||||
ApiTokenCreatedEvent,
|
||||
ApiTokenDeletedEvent,
|
||||
ApiTokenUpdatedEvent,
|
||||
SYSTEM_USER,
|
||||
SYSTEM_USER_ID,
|
||||
} from '../types';
|
||||
import { omitKeys } from '../util';
|
||||
import EventService from './event-service';
|
||||
@ -114,7 +116,13 @@ export class ApiTokenService {
|
||||
try {
|
||||
const createAll = tokens
|
||||
.map(mapLegacyTokenWithSecret)
|
||||
.map((t) => this.insertNewApiToken(t, 'init-api-tokens'));
|
||||
.map((t) =>
|
||||
this.insertNewApiToken(
|
||||
t,
|
||||
'init-api-tokens',
|
||||
SYSTEM_USER_ID,
|
||||
),
|
||||
);
|
||||
await Promise.all(createAll);
|
||||
} catch (e) {
|
||||
this.logger.error('Unable to create initial Admin API tokens');
|
||||
@ -162,12 +170,14 @@ export class ApiTokenService {
|
||||
secret: string,
|
||||
expiresAt: Date,
|
||||
updatedBy: string,
|
||||
updatedById: number,
|
||||
): Promise<IApiToken> {
|
||||
const previous = await this.store.get(secret);
|
||||
const token = await this.store.setExpiry(secret, expiresAt);
|
||||
await this.eventService.storeEvent(
|
||||
new ApiTokenUpdatedEvent({
|
||||
createdBy: updatedBy,
|
||||
createdByUserId: updatedById,
|
||||
previousToken: omitKeys(previous, 'secret'),
|
||||
apiToken: omitKeys(token, 'secret'),
|
||||
}),
|
||||
@ -175,13 +185,18 @@ export class ApiTokenService {
|
||||
return token;
|
||||
}
|
||||
|
||||
public async delete(secret: string, deletedBy: string): Promise<void> {
|
||||
public async delete(
|
||||
secret: string,
|
||||
deletedBy: string,
|
||||
deletedByUserId: number,
|
||||
): Promise<void> {
|
||||
if (await this.store.exists(secret)) {
|
||||
const token = await this.store.get(secret);
|
||||
await this.store.delete(secret);
|
||||
await this.eventService.storeEvent(
|
||||
new ApiTokenDeletedEvent({
|
||||
createdBy: deletedBy,
|
||||
createdByUserId: deletedByUserId,
|
||||
apiToken: omitKeys(token, 'secret'),
|
||||
}),
|
||||
);
|
||||
@ -193,15 +208,21 @@ export class ApiTokenService {
|
||||
*/
|
||||
public async createApiToken(
|
||||
newToken: Omit<ILegacyApiTokenCreate, 'secret'>,
|
||||
createdBy: string = 'unleash-system',
|
||||
createdBy: string = SYSTEM_USER.username,
|
||||
createdByUserId: number = SYSTEM_USER.id,
|
||||
): Promise<IApiToken> {
|
||||
const token = mapLegacyToken(newToken);
|
||||
return this.createApiTokenWithProjects(token, createdBy);
|
||||
return this.createApiTokenWithProjects(
|
||||
token,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
);
|
||||
}
|
||||
|
||||
public async createApiTokenWithProjects(
|
||||
newToken: Omit<IApiTokenCreate, 'secret'>,
|
||||
createdBy: string = 'unleash-system',
|
||||
createdBy: string = SYSTEM_USER.username,
|
||||
createdByUserId: number = SYSTEM_USER.id,
|
||||
): Promise<IApiToken> {
|
||||
validateApiToken(newToken);
|
||||
const environments = await this.environmentStore.getAll();
|
||||
@ -209,7 +230,11 @@ export class ApiTokenService {
|
||||
|
||||
const secret = this.generateSecretKey(newToken);
|
||||
const createNewToken = { ...newToken, secret };
|
||||
return this.insertNewApiToken(createNewToken, createdBy);
|
||||
return this.insertNewApiToken(
|
||||
createNewToken,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Remove this service method after embedded proxy has been released in
|
||||
@ -221,12 +246,17 @@ export class ApiTokenService {
|
||||
|
||||
const secret = this.generateSecretKey(newToken);
|
||||
const createNewToken = { ...newToken, secret };
|
||||
return this.insertNewApiToken(createNewToken, 'system-migration');
|
||||
return this.insertNewApiToken(
|
||||
createNewToken,
|
||||
'system-migration',
|
||||
SYSTEM_USER_ID,
|
||||
);
|
||||
}
|
||||
|
||||
private async insertNewApiToken(
|
||||
newApiToken: IApiTokenCreate,
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<IApiToken> {
|
||||
try {
|
||||
const token = await this.store.insert(newApiToken);
|
||||
@ -234,6 +264,7 @@ export class ApiTokenService {
|
||||
await this.eventService.storeEvent(
|
||||
new ApiTokenCreatedEvent({
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
apiToken: omitKeys(token, 'secret'),
|
||||
}),
|
||||
);
|
||||
|
||||
@ -14,18 +14,18 @@ import { IApplicationQuery } from '../../types/query';
|
||||
import { IClientApp } from '../../types/model';
|
||||
import { clientRegisterSchema } from './schema';
|
||||
|
||||
import { minutesToMilliseconds, secondsToMilliseconds } from 'date-fns';
|
||||
import { IClientMetricsStoreV2 } from '../../types/stores/client-metrics-store-v2';
|
||||
import { clientMetricsSchema } from './schema';
|
||||
import { PartialSome } from '../../types/partial';
|
||||
import { IPrivateProjectChecker } from '../../features/private-project/privateProjectCheckerType';
|
||||
import { IFlagResolver } from '../../types';
|
||||
import { IFlagResolver, SYSTEM_USER } from '../../types';
|
||||
import { ALL_PROJECTS } from '../../util';
|
||||
import { Logger } from '../../logger';
|
||||
|
||||
export default class ClientInstanceService {
|
||||
apps = {};
|
||||
|
||||
logger = null;
|
||||
logger: Logger;
|
||||
|
||||
seenClients: Record<string, IClientApp> = {};
|
||||
|
||||
@ -112,8 +112,9 @@ export default class ClientInstanceService {
|
||||
if (appsToAnnounce.length > 0) {
|
||||
const events = appsToAnnounce.map((app) => ({
|
||||
type: APPLICATION_CREATED,
|
||||
createdBy: app.createdBy || 'unknown',
|
||||
createdBy: app.createdBy || SYSTEM_USER.username,
|
||||
data: app,
|
||||
createdByUserId: app.createdByUserId || SYSTEM_USER.id,
|
||||
}));
|
||||
await this.eventStore.batchStore(events);
|
||||
}
|
||||
@ -132,7 +133,7 @@ export default class ClientInstanceService {
|
||||
this.clientInstanceStore
|
||||
) {
|
||||
const uniqueRegistrations = Object.values(this.seenClients);
|
||||
const uniqueApps = Object.values(
|
||||
const uniqueApps: Partial<IClientApplication>[] = Object.values(
|
||||
uniqueRegistrations.reduce((soFar, reg) => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
soFar[reg.appName] = reg;
|
||||
|
||||
@ -44,6 +44,7 @@ test('should clean unknown feature toggle names from last seen store', async ()
|
||||
'default',
|
||||
{ name: featureName },
|
||||
'user',
|
||||
-9999,
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -99,6 +100,7 @@ test('should clean unknown feature toggle environments from last seen store', as
|
||||
'default',
|
||||
{ name: feature.name },
|
||||
'user',
|
||||
-9999,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@ -110,6 +110,7 @@ class ContextService {
|
||||
async createContextField(
|
||||
value: IContextFieldDto,
|
||||
userName: string,
|
||||
createdByUserId: number,
|
||||
): Promise<IContextField> {
|
||||
// validations
|
||||
await this.validateUniqueName(value);
|
||||
@ -120,6 +121,7 @@ class ContextService {
|
||||
await this.eventService.storeEvent({
|
||||
type: CONTEXT_FIELD_CREATED,
|
||||
createdBy: userName,
|
||||
createdByUserId,
|
||||
data: contextField,
|
||||
});
|
||||
|
||||
@ -129,6 +131,7 @@ class ContextService {
|
||||
async updateContextField(
|
||||
updatedContextField: IContextFieldDto,
|
||||
userName: string,
|
||||
updatedByUserId: number,
|
||||
): Promise<void> {
|
||||
const contextField = await this.contextFieldStore.get(
|
||||
updatedContextField.name,
|
||||
@ -140,12 +143,17 @@ class ContextService {
|
||||
await this.eventService.storeEvent({
|
||||
type: CONTEXT_FIELD_UPDATED,
|
||||
createdBy: userName,
|
||||
createdByUserId: updatedByUserId,
|
||||
preData: contextField,
|
||||
data: value,
|
||||
});
|
||||
}
|
||||
|
||||
async deleteContextField(name: string, userName: string): Promise<void> {
|
||||
async deleteContextField(
|
||||
name: string,
|
||||
userName: string,
|
||||
deletedByUserId: number,
|
||||
): Promise<void> {
|
||||
const contextField = await this.contextFieldStore.get(name);
|
||||
|
||||
// delete
|
||||
@ -153,6 +161,7 @@ class ContextService {
|
||||
await this.eventService.storeEvent({
|
||||
type: CONTEXT_FIELD_DELETED,
|
||||
createdBy: userName,
|
||||
createdByUserId: deletedByUserId,
|
||||
preData: contextField,
|
||||
});
|
||||
}
|
||||
|
||||
@ -65,6 +65,7 @@ export class FavoritesService {
|
||||
type: FEATURE_FAVORITED,
|
||||
featureName: feature,
|
||||
createdBy: extractUsernameFromUser(user),
|
||||
createdByUserId: user.id,
|
||||
data: {
|
||||
feature,
|
||||
},
|
||||
@ -84,6 +85,7 @@ export class FavoritesService {
|
||||
type: FEATURE_UNFAVORITED,
|
||||
featureName: feature,
|
||||
createdBy: extractUsernameFromUser(user),
|
||||
createdByUserId: user.id,
|
||||
data: {
|
||||
feature,
|
||||
},
|
||||
@ -102,6 +104,7 @@ export class FavoritesService {
|
||||
await this.eventService.storeEvent({
|
||||
type: PROJECT_FAVORITED,
|
||||
createdBy: extractUsernameFromUser(user),
|
||||
createdByUserId: user.id,
|
||||
data: {
|
||||
project,
|
||||
},
|
||||
@ -120,6 +123,7 @@ export class FavoritesService {
|
||||
await this.eventService.storeEvent({
|
||||
type: PROJECT_UNFAVORITED,
|
||||
createdBy: extractUsernameFromUser(user),
|
||||
createdByUserId: user.id,
|
||||
data: {
|
||||
project,
|
||||
},
|
||||
|
||||
@ -56,15 +56,17 @@ class FeatureTagService {
|
||||
featureName: string,
|
||||
tag: ITag,
|
||||
userName: string,
|
||||
addedByUserId: number,
|
||||
): Promise<ITag> {
|
||||
const featureToggle = await this.featureToggleStore.get(featureName);
|
||||
const validatedTag = await tagSchema.validateAsync(tag);
|
||||
await this.createTagIfNeeded(validatedTag, userName);
|
||||
await this.createTagIfNeeded(validatedTag, userName, addedByUserId);
|
||||
await this.featureTagStore.tagFeature(featureName, validatedTag);
|
||||
|
||||
await this.eventService.storeEvent({
|
||||
type: FEATURE_TAGGED,
|
||||
createdBy: userName,
|
||||
createdByUserId: addedByUserId,
|
||||
featureName,
|
||||
project: featureToggle.project,
|
||||
data: validatedTag,
|
||||
@ -77,11 +79,14 @@ class FeatureTagService {
|
||||
addedTags: ITag[],
|
||||
removedTags: ITag[],
|
||||
userName: string,
|
||||
updatedByUserId: number,
|
||||
): Promise<void> {
|
||||
const featureToggles =
|
||||
await this.featureToggleStore.getAllByNames(featureNames);
|
||||
await Promise.all(
|
||||
addedTags.map((tag) => this.createTagIfNeeded(tag, userName)),
|
||||
addedTags.map((tag) =>
|
||||
this.createTagIfNeeded(tag, userName, updatedByUserId),
|
||||
),
|
||||
);
|
||||
const createdFeatureTags: IFeatureTag[] = featureNames.flatMap(
|
||||
(featureName) =>
|
||||
@ -112,6 +117,7 @@ class FeatureTagService {
|
||||
featureName: featureToggle.name,
|
||||
project: featureToggle.project,
|
||||
data: addedTag,
|
||||
createdByUserId: updatedByUserId,
|
||||
})),
|
||||
);
|
||||
|
||||
@ -122,6 +128,7 @@ class FeatureTagService {
|
||||
featureName: featureToggle.name,
|
||||
project: featureToggle.project,
|
||||
preData: removedTag,
|
||||
createdByUserId: updatedByUserId,
|
||||
})),
|
||||
);
|
||||
|
||||
@ -131,7 +138,11 @@ class FeatureTagService {
|
||||
]);
|
||||
}
|
||||
|
||||
async createTagIfNeeded(tag: ITag, userName: string): Promise<void> {
|
||||
async createTagIfNeeded(
|
||||
tag: ITag,
|
||||
userName: string,
|
||||
createdByUserId: number,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await this.tagStore.getTag(tag.type, tag.value);
|
||||
} catch (error) {
|
||||
@ -141,6 +152,7 @@ class FeatureTagService {
|
||||
await this.eventService.storeEvent({
|
||||
type: TAG_CREATED,
|
||||
createdBy: userName,
|
||||
createdByUserId,
|
||||
data: tag,
|
||||
});
|
||||
} catch (err) {
|
||||
@ -159,6 +171,7 @@ class FeatureTagService {
|
||||
featureName: string,
|
||||
tag: ITag,
|
||||
userName: string,
|
||||
removedByUserId: number,
|
||||
): Promise<void> {
|
||||
const featureToggle = await this.featureToggleStore.get(featureName);
|
||||
const tags =
|
||||
@ -167,6 +180,7 @@ class FeatureTagService {
|
||||
await this.eventService.storeEvent({
|
||||
type: FEATURE_UNTAGGED,
|
||||
createdBy: userName,
|
||||
createdByUserId: removedByUserId,
|
||||
featureName,
|
||||
project: featureToggle.project,
|
||||
preData: tag,
|
||||
|
||||
@ -57,6 +57,7 @@ export default class FeatureTypeService {
|
||||
await this.eventService.storeEvent({
|
||||
type: FEATURE_TYPE_UPDATED,
|
||||
createdBy: extractUsernameFromUser(user),
|
||||
createdByUserId: user.id,
|
||||
data: { ...featureType, lifetimeDays: translatedLifetime },
|
||||
preData: featureType,
|
||||
});
|
||||
|
||||
@ -94,6 +94,7 @@ export class GroupService {
|
||||
async createGroup(
|
||||
group: ICreateGroupModel,
|
||||
userName: string,
|
||||
createdByUserId: number,
|
||||
): Promise<IGroup> {
|
||||
await this.validateGroup(group);
|
||||
|
||||
@ -111,13 +112,18 @@ export class GroupService {
|
||||
await this.eventService.storeEvent({
|
||||
type: GROUP_CREATED,
|
||||
createdBy: userName,
|
||||
createdByUserId,
|
||||
data: { ...group, users: newUserIds },
|
||||
});
|
||||
|
||||
return newGroup;
|
||||
}
|
||||
|
||||
async updateGroup(group: IGroupModel, userName: string): Promise<IGroup> {
|
||||
async updateGroup(
|
||||
group: IGroupModel,
|
||||
userName: string,
|
||||
createdByUserId: number,
|
||||
): Promise<IGroup> {
|
||||
const existingGroup = await this.groupStore.get(group.id);
|
||||
|
||||
await this.validateGroup(group, existingGroup);
|
||||
@ -149,6 +155,7 @@ export class GroupService {
|
||||
await this.eventService.storeEvent({
|
||||
type: GROUP_UPDATED,
|
||||
createdBy: userName,
|
||||
createdByUserId,
|
||||
data: { ...newGroup, users: newUserIds },
|
||||
preData: { ...existingGroup, users: existingUserIds },
|
||||
});
|
||||
@ -183,7 +190,11 @@ export class GroupService {
|
||||
return [];
|
||||
}
|
||||
|
||||
async deleteGroup(id: number, userName: string): Promise<void> {
|
||||
async deleteGroup(
|
||||
id: number,
|
||||
userName: string,
|
||||
createdByUserId: number,
|
||||
): Promise<void> {
|
||||
const group = await this.groupStore.get(id);
|
||||
|
||||
const existingUsers = await this.groupStore.getAllUsersByGroups([
|
||||
@ -196,6 +207,7 @@ export class GroupService {
|
||||
await this.eventService.storeEvent({
|
||||
type: GROUP_DELETED,
|
||||
createdBy: userName,
|
||||
createdByUserId,
|
||||
preData: { ...group, users: existingUserIds },
|
||||
});
|
||||
}
|
||||
@ -219,6 +231,57 @@ export class GroupService {
|
||||
return this.groupStore.getProjectGroupRoles(projectId);
|
||||
}
|
||||
|
||||
async syncExternalGroups(
|
||||
userId: number,
|
||||
externalGroups: string[],
|
||||
createdBy?: string,
|
||||
createdByUserId?: number,
|
||||
): Promise<void> {
|
||||
if (Array.isArray(externalGroups)) {
|
||||
const newGroups = await this.groupStore.getNewGroupsForExternalUser(
|
||||
userId,
|
||||
externalGroups,
|
||||
);
|
||||
await this.groupStore.addUserToGroups(
|
||||
userId,
|
||||
newGroups.map((g) => g.id),
|
||||
createdBy,
|
||||
);
|
||||
const oldGroups = await this.groupStore.getOldGroupsForExternalUser(
|
||||
userId,
|
||||
externalGroups,
|
||||
);
|
||||
await this.groupStore.deleteUsersFromGroup(oldGroups);
|
||||
|
||||
const events: IBaseEvent[] = [];
|
||||
for (const group of newGroups) {
|
||||
events.push({
|
||||
type: GROUP_USER_ADDED,
|
||||
createdBy: createdBy ?? 'unknown',
|
||||
createdByUserId: createdByUserId ?? -9999,
|
||||
data: {
|
||||
groupId: group.id,
|
||||
userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
for (const group of oldGroups) {
|
||||
events.push({
|
||||
type: GROUP_USER_REMOVED,
|
||||
createdBy: createdBy ?? 'unknown',
|
||||
createdByUserId: createdByUserId ?? -9999,
|
||||
preData: {
|
||||
groupId: group.groupId,
|
||||
userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await this.eventService.storeEvents(events);
|
||||
}
|
||||
}
|
||||
|
||||
private mapGroupWithUsers(
|
||||
group: IGroup,
|
||||
allGroupUsers: IGroupUser[],
|
||||
@ -242,54 +305,6 @@ export class GroupService {
|
||||
return { ...group, users: finalUsers };
|
||||
}
|
||||
|
||||
async syncExternalGroups(
|
||||
userId: number,
|
||||
externalGroups: string[],
|
||||
createdBy?: string,
|
||||
): Promise<void> {
|
||||
if (Array.isArray(externalGroups)) {
|
||||
const newGroups = await this.groupStore.getNewGroupsForExternalUser(
|
||||
userId,
|
||||
externalGroups,
|
||||
);
|
||||
await this.groupStore.addUserToGroups(
|
||||
userId,
|
||||
newGroups.map((g) => g.id),
|
||||
createdBy,
|
||||
);
|
||||
const oldGroups = await this.groupStore.getOldGroupsForExternalUser(
|
||||
userId,
|
||||
externalGroups,
|
||||
);
|
||||
await this.groupStore.deleteUsersFromGroup(oldGroups);
|
||||
|
||||
const events: IBaseEvent[] = [];
|
||||
for (const group of newGroups) {
|
||||
events.push({
|
||||
type: GROUP_USER_ADDED,
|
||||
createdBy: createdBy ?? 'unknown',
|
||||
data: {
|
||||
groupId: group.id,
|
||||
userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
for (const group of oldGroups) {
|
||||
events.push({
|
||||
type: GROUP_USER_REMOVED,
|
||||
createdBy: createdBy ?? 'unknown',
|
||||
preData: {
|
||||
groupId: group.groupId,
|
||||
userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await this.eventService.storeEvents(events);
|
||||
}
|
||||
}
|
||||
|
||||
async getGroupsForUser(userId: number): Promise<IGroup[]> {
|
||||
return this.groupStore.getGroupsForUser(userId);
|
||||
}
|
||||
|
||||
@ -45,6 +45,7 @@ export default class PatService {
|
||||
await this.eventService.storeEvent({
|
||||
type: PAT_CREATED,
|
||||
createdBy: editor.email || editor.username,
|
||||
createdByUserId: editor.id,
|
||||
data: pat,
|
||||
});
|
||||
|
||||
@ -66,6 +67,7 @@ export default class PatService {
|
||||
await this.eventService.storeEvent({
|
||||
type: PAT_DELETED,
|
||||
createdBy: editor.email || editor.username,
|
||||
createdByUserId: editor.id,
|
||||
data: pat,
|
||||
});
|
||||
|
||||
|
||||
@ -41,6 +41,7 @@ import {
|
||||
CreateProject,
|
||||
IProjectUpdate,
|
||||
IProjectHealth,
|
||||
SYSTEM_USER,
|
||||
} from '../types';
|
||||
import {
|
||||
IProjectQuery,
|
||||
@ -255,6 +256,7 @@ export default class ProjectService {
|
||||
await this.eventService.storeEvent({
|
||||
type: PROJECT_CREATED,
|
||||
createdBy: getCreatedBy(user),
|
||||
createdByUserId: user.id,
|
||||
data,
|
||||
project: newProject.id,
|
||||
});
|
||||
@ -273,10 +275,11 @@ export default class ProjectService {
|
||||
// updated project contains instructions to update the project but it may not represent a whole project
|
||||
const afterData = await this.projectStore.get(updatedProject.id);
|
||||
|
||||
await this.eventStore.store({
|
||||
await this.eventService.storeEvent({
|
||||
type: PROJECT_UPDATED,
|
||||
project: updatedProject.id,
|
||||
createdBy: getCreatedBy(user),
|
||||
createdByUserId: user.id,
|
||||
data: afterData,
|
||||
preData,
|
||||
});
|
||||
@ -300,6 +303,7 @@ export default class ProjectService {
|
||||
type: PROJECT_UPDATED,
|
||||
project: updatedProject.id,
|
||||
createdBy: getCreatedBy(user),
|
||||
createdByUserId: user.id,
|
||||
data: { ...preData, ...updatedProject },
|
||||
preData,
|
||||
});
|
||||
@ -363,6 +367,7 @@ export default class ProjectService {
|
||||
featureName,
|
||||
newProjectId,
|
||||
getCreatedBy(user),
|
||||
user.id,
|
||||
);
|
||||
await this.featureToggleService.updateFeatureStrategyProject(
|
||||
featureName,
|
||||
@ -399,6 +404,7 @@ export default class ProjectService {
|
||||
archivedToggles.map((toggle) => toggle.name),
|
||||
id,
|
||||
user.name,
|
||||
user.id,
|
||||
);
|
||||
|
||||
await this.projectStore.delete(id);
|
||||
@ -407,6 +413,7 @@ export default class ProjectService {
|
||||
type: PROJECT_DELETED,
|
||||
createdBy: getCreatedBy(user),
|
||||
project: id,
|
||||
createdByUserId: user.id,
|
||||
});
|
||||
|
||||
await this.accessService.removeDefaultProjectRoles(user, id);
|
||||
@ -460,7 +467,8 @@ export default class ProjectService {
|
||||
await this.eventService.storeEvent(
|
||||
new ProjectUserAddedEvent({
|
||||
project: projectId,
|
||||
createdBy: createdBy || 'system-user',
|
||||
createdBy: createdBy || SYSTEM_USER.username,
|
||||
createdByUserId: user.id || SYSTEM_USER.id,
|
||||
data: {
|
||||
roleId,
|
||||
userId,
|
||||
@ -479,6 +487,7 @@ export default class ProjectService {
|
||||
roleId: number,
|
||||
userId: number,
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<void> {
|
||||
const role = await this.findProjectRole(projectId, roleId);
|
||||
|
||||
@ -492,6 +501,7 @@ export default class ProjectService {
|
||||
new ProjectUserRemovedEvent({
|
||||
project: projectId,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
preData: {
|
||||
roleId,
|
||||
userId,
|
||||
@ -506,6 +516,7 @@ export default class ProjectService {
|
||||
projectId: string,
|
||||
userId: number,
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<void> {
|
||||
const existingRoles = await this.accessService.getProjectRolesForUser(
|
||||
projectId,
|
||||
@ -526,6 +537,7 @@ export default class ProjectService {
|
||||
new ProjectAccessUserRolesDeleted({
|
||||
project: projectId,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
preData: {
|
||||
roles: existingRoles,
|
||||
userId,
|
||||
@ -538,6 +550,7 @@ export default class ProjectService {
|
||||
projectId: string,
|
||||
groupId: number,
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<void> {
|
||||
const existingRoles = await this.accessService.getProjectRolesForGroup(
|
||||
projectId,
|
||||
@ -558,6 +571,7 @@ export default class ProjectService {
|
||||
new ProjectAccessUserRolesDeleted({
|
||||
project: projectId,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
preData: {
|
||||
roles: existingRoles,
|
||||
groupId,
|
||||
@ -571,6 +585,7 @@ export default class ProjectService {
|
||||
roleId: number,
|
||||
groupId: number,
|
||||
modifiedBy: string,
|
||||
modifiedById: number,
|
||||
): Promise<void> {
|
||||
const role = await this.accessService.getRole(roleId);
|
||||
const group = await this.groupService.getGroup(groupId);
|
||||
@ -593,6 +608,7 @@ export default class ProjectService {
|
||||
new ProjectGroupAddedEvent({
|
||||
project: project.id,
|
||||
createdBy: modifiedBy,
|
||||
createdByUserId: modifiedById,
|
||||
data: {
|
||||
groupId: group.id,
|
||||
projectId: project.id,
|
||||
@ -610,6 +626,7 @@ export default class ProjectService {
|
||||
roleId: number,
|
||||
groupId: number,
|
||||
modifiedBy: string,
|
||||
modifiedById: number,
|
||||
): Promise<void> {
|
||||
const group = await this.groupService.getGroup(groupId);
|
||||
const role = await this.accessService.getRole(roleId);
|
||||
@ -633,6 +650,7 @@ export default class ProjectService {
|
||||
new ProjectGroupRemovedEvent({
|
||||
project: projectId,
|
||||
createdBy: modifiedBy,
|
||||
createdByUserId: modifiedById,
|
||||
preData: {
|
||||
groupId: group.id,
|
||||
projectId: project.id,
|
||||
@ -647,6 +665,7 @@ export default class ProjectService {
|
||||
roleId: number,
|
||||
usersAndGroups: IProjectAccessModel,
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<void> {
|
||||
await this.accessService.addRoleAccessToProject(
|
||||
usersAndGroups.users,
|
||||
@ -660,6 +679,7 @@ export default class ProjectService {
|
||||
new ProjectAccessAddedEvent({
|
||||
project: projectId,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
data: {
|
||||
roleId,
|
||||
groups: usersAndGroups.groups.map(({ id }) => id),
|
||||
@ -675,6 +695,7 @@ export default class ProjectService {
|
||||
groups: number[],
|
||||
users: number[],
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<void> {
|
||||
await this.accessService.addAccessToProject(
|
||||
roles,
|
||||
@ -688,6 +709,7 @@ export default class ProjectService {
|
||||
new ProjectAccessAddedEvent({
|
||||
project: projectId,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
data: {
|
||||
roles,
|
||||
groups,
|
||||
@ -702,6 +724,7 @@ export default class ProjectService {
|
||||
userId: number,
|
||||
newRoles: number[],
|
||||
createdByUserName: string,
|
||||
createdByUserId: number,
|
||||
): Promise<void> {
|
||||
const currentRoles = await this.accessService.getProjectRolesForUser(
|
||||
projectId,
|
||||
@ -727,6 +750,7 @@ export default class ProjectService {
|
||||
new ProjectAccessUserRolesUpdated({
|
||||
project: projectId,
|
||||
createdBy: createdByUserName,
|
||||
createdByUserId,
|
||||
data: {
|
||||
roles: newRoles,
|
||||
userId,
|
||||
@ -744,6 +768,7 @@ export default class ProjectService {
|
||||
groupId: number,
|
||||
newRoles: number[],
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<void> {
|
||||
const currentRoles = await this.accessService.getProjectRolesForGroup(
|
||||
projectId,
|
||||
@ -769,6 +794,7 @@ export default class ProjectService {
|
||||
new ProjectAccessGroupRolesUpdated({
|
||||
project: projectId,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
data: {
|
||||
roles: newRoles,
|
||||
groupId,
|
||||
@ -863,6 +889,7 @@ export default class ProjectService {
|
||||
roleId: number,
|
||||
userId: number,
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<void> {
|
||||
const usersWithRoles = await this.getAccessToProject(projectId);
|
||||
const user = usersWithRoles.users.find((u) => u.id === userId);
|
||||
@ -896,6 +923,7 @@ export default class ProjectService {
|
||||
new ProjectUserUpdateRoleEvent({
|
||||
project: projectId,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
preData: {
|
||||
userId,
|
||||
roleId: currentRole.id,
|
||||
@ -917,6 +945,7 @@ export default class ProjectService {
|
||||
roleId: number,
|
||||
userId: number,
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<void> {
|
||||
const usersWithRoles = await this.getAccessToProject(projectId);
|
||||
const user = usersWithRoles.groups.find((u) => u.id === userId);
|
||||
@ -949,6 +978,7 @@ export default class ProjectService {
|
||||
new ProjectGroupUpdateRoleEvent({
|
||||
project: projectId,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
preData: {
|
||||
userId,
|
||||
roleId: currentRole.id,
|
||||
|
||||
@ -162,6 +162,7 @@ export class ProxyService {
|
||||
async setFrontendSettings(
|
||||
value: FrontendSettings,
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<void> {
|
||||
const error = validateOrigins(value.frontendApiOrigins);
|
||||
if (error) {
|
||||
@ -171,6 +172,7 @@ export class ProxyService {
|
||||
frontendSettingsKey,
|
||||
value,
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import crypto from 'crypto';
|
||||
import { Logger } from '../logger';
|
||||
import { IUnleashConfig, IUnleashStores } from '../types';
|
||||
import { IUnleashConfig, IUnleashStores, SYSTEM_USER } from '../types';
|
||||
import { IPublicSignupTokenStore } from '../types/stores/public-signup-token-store';
|
||||
import { PublicSignupTokenSchema } from '../openapi/spec/public-signup-token-schema';
|
||||
import { IRoleStore } from '../types/stores/role-store';
|
||||
@ -77,11 +77,13 @@ export class PublicSignupTokenService {
|
||||
secret: string,
|
||||
{ expiresAt, enabled }: { expiresAt?: Date; enabled?: boolean },
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<PublicSignupTokenSchema> {
|
||||
const result = await this.store.update(secret, { expiresAt, enabled });
|
||||
await this.eventService.storeEvent(
|
||||
new PublicSignupTokenUpdatedEvent({
|
||||
createdBy,
|
||||
createdByUserId,
|
||||
data: { secret, enabled, expiresAt },
|
||||
}),
|
||||
);
|
||||
@ -100,7 +102,8 @@ export class PublicSignupTokenService {
|
||||
await this.store.addTokenUser(secret, user.id);
|
||||
await this.eventService.storeEvent(
|
||||
new PublicSignupTokenUserAddedEvent({
|
||||
createdBy: 'System',
|
||||
createdBy: SYSTEM_USER.username,
|
||||
createdByUserId: SYSTEM_USER.id,
|
||||
data: { secret, userId: user.id },
|
||||
}),
|
||||
);
|
||||
@ -110,6 +113,7 @@ export class PublicSignupTokenService {
|
||||
public async createNewPublicSignupToken(
|
||||
tokenCreate: PublicSignupTokenCreateSchema,
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<PublicSignupTokenSchema> {
|
||||
const viewerRole = await this.roleStore.getRoleByName(RoleName.VIEWER);
|
||||
const secret = this.generateSecretKey();
|
||||
@ -131,6 +135,7 @@ export class PublicSignupTokenService {
|
||||
await this.eventService.storeEvent(
|
||||
new PublicSignupTokenCreatedEvent({
|
||||
createdBy: createdBy,
|
||||
createdByUserId,
|
||||
data: token,
|
||||
}),
|
||||
);
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
IFlagResolver,
|
||||
IUnleashStores,
|
||||
SKIP_CHANGE_REQUEST,
|
||||
SYSTEM_USER,
|
||||
} from '../types';
|
||||
import { Logger } from '../logger';
|
||||
import NameExistsError from '../error/name-exists-error';
|
||||
@ -143,7 +144,7 @@ export class SegmentService implements ISegmentService {
|
||||
|
||||
async create(
|
||||
data: unknown,
|
||||
user: Partial<Pick<User, 'username' | 'email'>>,
|
||||
user: Partial<Pick<User, 'id' | 'username' | 'email'>>,
|
||||
): Promise<ISegment> {
|
||||
const input = await segmentSchema.validateAsync(data);
|
||||
this.validateSegmentValuesLimit(input);
|
||||
@ -152,7 +153,8 @@ export class SegmentService implements ISegmentService {
|
||||
|
||||
await this.eventService.storeEvent({
|
||||
type: SEGMENT_CREATED,
|
||||
createdBy: user.email || user.username || 'unknown',
|
||||
createdBy: user.email || user.username || SYSTEM_USER.username,
|
||||
createdByUserId: user.id || SYSTEM_USER.id,
|
||||
data: segment,
|
||||
project: segment.project,
|
||||
});
|
||||
@ -186,6 +188,7 @@ export class SegmentService implements ISegmentService {
|
||||
await this.eventService.storeEvent({
|
||||
type: SEGMENT_UPDATED,
|
||||
createdBy: user.email || user.username || 'unknown',
|
||||
createdByUserId: user.id,
|
||||
data: segment,
|
||||
preData,
|
||||
project: segment.project,
|
||||
@ -199,6 +202,7 @@ export class SegmentService implements ISegmentService {
|
||||
await this.eventService.storeEvent({
|
||||
type: SEGMENT_DELETED,
|
||||
createdBy: user.email || user.username,
|
||||
createdByUserId: user.id,
|
||||
preData: segment,
|
||||
project: segment.project,
|
||||
});
|
||||
@ -210,6 +214,7 @@ export class SegmentService implements ISegmentService {
|
||||
await this.eventService.storeEvent({
|
||||
type: SEGMENT_DELETED,
|
||||
createdBy: user.email || user.username,
|
||||
createdByUserId: user.id,
|
||||
preData: segment,
|
||||
});
|
||||
}
|
||||
|
||||
@ -46,6 +46,7 @@ export default class SettingService {
|
||||
id: string,
|
||||
value: object,
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
hideEventDetails: boolean = true,
|
||||
): Promise<void> {
|
||||
const existingSettings = await this.settingStore.get<object>(id);
|
||||
@ -65,6 +66,7 @@ export default class SettingService {
|
||||
{
|
||||
createdBy,
|
||||
data,
|
||||
createdByUserId,
|
||||
},
|
||||
preData,
|
||||
),
|
||||
@ -73,6 +75,7 @@ export default class SettingService {
|
||||
await this.settingStore.insert(id, value);
|
||||
await this.eventService.storeEvent(
|
||||
new SettingCreatedEvent({
|
||||
createdByUserId,
|
||||
createdBy,
|
||||
data,
|
||||
}),
|
||||
@ -80,10 +83,15 @@ export default class SettingService {
|
||||
}
|
||||
}
|
||||
|
||||
async delete(id: string, createdBy: string): Promise<void> {
|
||||
async delete(
|
||||
id: string,
|
||||
createdBy: string,
|
||||
createdByUserId: number,
|
||||
): Promise<void> {
|
||||
await this.settingStore.delete(id);
|
||||
await this.eventService.storeEvent(
|
||||
new SettingDeletedEvent({
|
||||
createdByUserId,
|
||||
createdBy,
|
||||
data: {
|
||||
id,
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
import { GLOBAL_ENV } from '../types/environment';
|
||||
import variantsExportV3 from '../../test/examples/variantsexport_v3.json';
|
||||
import EventService from './event-service';
|
||||
import { SYSTEM_USER_ID } from '../types';
|
||||
const oldExportExample = require('./state-service-export-v1.json');
|
||||
|
||||
function getSetup() {
|
||||
@ -93,7 +94,7 @@ test('should import a feature', async () => {
|
||||
],
|
||||
};
|
||||
|
||||
await stateService.import({ data });
|
||||
await stateService.import({ userId: SYSTEM_USER_ID, data });
|
||||
|
||||
const events = await stores.eventStore.getEvents();
|
||||
expect(events).toHaveLength(1);
|
||||
@ -116,7 +117,11 @@ test('should not import an existing feature', async () => {
|
||||
|
||||
await stores.featureToggleStore.create('default', data.features[0]);
|
||||
|
||||
await stateService.import({ data, keepExisting: true });
|
||||
await stateService.import({
|
||||
data,
|
||||
keepExisting: true,
|
||||
userId: SYSTEM_USER_ID,
|
||||
});
|
||||
|
||||
const events = await stores.eventStore.getEvents();
|
||||
expect(events).toHaveLength(0);
|
||||
@ -141,6 +146,7 @@ test('should not keep existing feature if drop-before-import', async () => {
|
||||
data,
|
||||
keepExisting: true,
|
||||
dropBeforeImport: true,
|
||||
userId: SYSTEM_USER_ID,
|
||||
});
|
||||
|
||||
const events = await stores.eventStore.getEvents();
|
||||
@ -162,7 +168,11 @@ test('should drop feature before import if specified', async () => {
|
||||
],
|
||||
};
|
||||
|
||||
await stateService.import({ data, dropBeforeImport: true });
|
||||
await stateService.import({
|
||||
data,
|
||||
dropBeforeImport: true,
|
||||
userId: SYSTEM_USER_ID,
|
||||
});
|
||||
|
||||
const events = await stores.eventStore.getEvents();
|
||||
expect(events).toHaveLength(2);
|
||||
@ -183,7 +193,7 @@ test('should import a strategy', async () => {
|
||||
],
|
||||
};
|
||||
|
||||
await stateService.import({ data });
|
||||
await stateService.import({ userId: SYSTEM_USER_ID, data });
|
||||
|
||||
const events = await stores.eventStore.getEvents();
|
||||
expect(events).toHaveLength(1);
|
||||
@ -205,7 +215,11 @@ test('should not import an existing strategy', async () => {
|
||||
|
||||
await stores.strategyStore.createStrategy(data.strategies[0]);
|
||||
|
||||
await stateService.import({ data, keepExisting: true });
|
||||
await stateService.import({
|
||||
data,
|
||||
userId: SYSTEM_USER_ID,
|
||||
keepExisting: true,
|
||||
});
|
||||
|
||||
const events = await stores.eventStore.getEvents();
|
||||
expect(events).toHaveLength(0);
|
||||
@ -223,7 +237,11 @@ test('should drop strategies before import if specified', async () => {
|
||||
],
|
||||
};
|
||||
|
||||
await stateService.import({ data, dropBeforeImport: true });
|
||||
await stateService.import({
|
||||
data,
|
||||
userId: SYSTEM_USER_ID,
|
||||
dropBeforeImport: true,
|
||||
});
|
||||
|
||||
const events = await stores.eventStore.getEvents();
|
||||
expect(events).toHaveLength(2);
|
||||
@ -237,7 +255,11 @@ test('should drop neither features nor strategies when neither is imported', asy
|
||||
|
||||
const data = {};
|
||||
|
||||
await stateService.import({ data, dropBeforeImport: true });
|
||||
await stateService.import({
|
||||
data,
|
||||
userId: SYSTEM_USER_ID,
|
||||
dropBeforeImport: true,
|
||||
});
|
||||
|
||||
const events = await stores.eventStore.getEvents();
|
||||
expect(events).toHaveLength(0);
|
||||
@ -253,11 +275,11 @@ test('should not accept gibberish', async () => {
|
||||
const data2 = '{somerandomtext/';
|
||||
|
||||
await expect(async () =>
|
||||
stateService.import({ data: data1 }),
|
||||
stateService.import({ userId: SYSTEM_USER_ID, data: data1 }),
|
||||
).rejects.toThrow();
|
||||
|
||||
await expect(async () =>
|
||||
stateService.import({ data: data2 }),
|
||||
stateService.import({ userId: SYSTEM_USER_ID, data: data2 }),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
@ -349,7 +371,7 @@ test('should import a tag and tag type', async () => {
|
||||
tags: [{ type: 'simple', value: 'test' }],
|
||||
};
|
||||
|
||||
await stateService.import({ data });
|
||||
await stateService.import({ userId: SYSTEM_USER_ID, data });
|
||||
|
||||
const events = await stores.eventStore.getEvents();
|
||||
expect(events).toHaveLength(2);
|
||||
@ -380,7 +402,11 @@ test('Should not import an existing tag', async () => {
|
||||
type: data.featureTags[0].tagType,
|
||||
value: data.featureTags[0].tagValue,
|
||||
});
|
||||
await stateService.import({ data, keepExisting: true });
|
||||
await stateService.import({
|
||||
data,
|
||||
userId: SYSTEM_USER_ID,
|
||||
keepExisting: true,
|
||||
});
|
||||
const events = await stores.eventStore.getEvents();
|
||||
expect(events).toHaveLength(0);
|
||||
});
|
||||
@ -413,7 +439,11 @@ test('Should not keep existing tags if drop-before-import', async () => {
|
||||
},
|
||||
],
|
||||
};
|
||||
await stateService.import({ data, dropBeforeImport: true });
|
||||
await stateService.import({
|
||||
data,
|
||||
userId: SYSTEM_USER_ID,
|
||||
dropBeforeImport: true,
|
||||
});
|
||||
const tagTypes = await stores.tagTypeStore.getAll();
|
||||
expect(tagTypes).toHaveLength(1);
|
||||
});
|
||||
@ -513,7 +543,7 @@ test('should import a project', async () => {
|
||||
],
|
||||
};
|
||||
|
||||
await stateService.import({ data });
|
||||
await stateService.import({ userId: SYSTEM_USER_ID, data });
|
||||
|
||||
const events = await stores.eventStore.getEvents();
|
||||
expect(events).toHaveLength(1);
|
||||
@ -536,11 +566,15 @@ test('Should not import an existing project', async () => {
|
||||
};
|
||||
await stores.projectStore.create(data.projects[0]);
|
||||
|
||||
await stateService.import({ data, keepExisting: true });
|
||||
await stateService.import({
|
||||
data,
|
||||
userId: SYSTEM_USER_ID,
|
||||
keepExisting: true,
|
||||
});
|
||||
const events = await stores.eventStore.getEvents();
|
||||
expect(events).toHaveLength(0);
|
||||
|
||||
await stateService.import({ data });
|
||||
await stateService.import({ userId: SYSTEM_USER_ID, data });
|
||||
});
|
||||
|
||||
test('Should drop projects before import if specified', async () => {
|
||||
@ -561,7 +595,11 @@ test('Should drop projects before import if specified', async () => {
|
||||
description: 'Not expected to be seen after import',
|
||||
mode: 'open' as const,
|
||||
});
|
||||
await stateService.import({ data, dropBeforeImport: true });
|
||||
await stateService.import({
|
||||
data,
|
||||
userId: SYSTEM_USER_ID,
|
||||
dropBeforeImport: true,
|
||||
});
|
||||
const hasProject = await stores.projectStore.hasProject('fancy');
|
||||
expect(hasProject).toBe(false);
|
||||
});
|
||||
@ -695,6 +733,7 @@ test('featureStrategies can keep existing', async () => {
|
||||
const exported = await stateService.export({});
|
||||
await stateService.import({
|
||||
data: exported,
|
||||
userId: SYSTEM_USER_ID,
|
||||
userName: 'testing',
|
||||
keepExisting: true,
|
||||
});
|
||||
@ -746,6 +785,7 @@ test('featureStrategies should not keep existing if dropBeforeImport', async ()
|
||||
exported.featureStrategies = [];
|
||||
await stateService.import({
|
||||
data: exported,
|
||||
userId: SYSTEM_USER_ID,
|
||||
userName: 'testing',
|
||||
keepExisting: true,
|
||||
dropBeforeImport: true,
|
||||
@ -757,6 +797,7 @@ test('Import v1 and exporting v2 should work', async () => {
|
||||
const { stateService } = getSetup();
|
||||
await stateService.import({
|
||||
data: oldExportExample,
|
||||
userId: SYSTEM_USER_ID,
|
||||
dropBeforeImport: true,
|
||||
userName: 'testing',
|
||||
});
|
||||
@ -793,6 +834,7 @@ test('Importing states with deprecated strategies should keep their deprecated s
|
||||
};
|
||||
await stateService.import({
|
||||
data: deprecatedStrategyExample,
|
||||
userId: SYSTEM_USER_ID,
|
||||
userName: 'strategy-importer',
|
||||
dropBeforeImport: true,
|
||||
keepExisting: false,
|
||||
@ -807,6 +849,7 @@ test('Exporting a deprecated strategy and then importing it should keep correct
|
||||
await stateService.import({
|
||||
data: variantsExportV3,
|
||||
keepExisting: false,
|
||||
userId: SYSTEM_USER_ID,
|
||||
dropBeforeImport: true,
|
||||
userName: 'strategy importer',
|
||||
});
|
||||
|
||||
@ -118,6 +118,7 @@ export default class StateService {
|
||||
file,
|
||||
dropBeforeImport = false,
|
||||
userName = 'import-user',
|
||||
userId,
|
||||
keepExisting = true,
|
||||
}: IImportFile): Promise<void> {
|
||||
return readFile(file)
|
||||
@ -128,6 +129,7 @@ export default class StateService {
|
||||
userName,
|
||||
dropBeforeImport,
|
||||
keepExisting,
|
||||
userId,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@ -168,6 +170,7 @@ export default class StateService {
|
||||
async import({
|
||||
data,
|
||||
userName = 'importUser',
|
||||
userId,
|
||||
dropBeforeImport = false,
|
||||
keepExisting = true,
|
||||
}: IImportData): Promise<void> {
|
||||
@ -186,6 +189,7 @@ export default class StateService {
|
||||
userName,
|
||||
dropBeforeImport,
|
||||
keepExisting,
|
||||
userId,
|
||||
});
|
||||
}
|
||||
|
||||
@ -196,6 +200,7 @@ export default class StateService {
|
||||
userName,
|
||||
dropBeforeImport,
|
||||
keepExisting,
|
||||
userId,
|
||||
});
|
||||
}
|
||||
|
||||
@ -215,6 +220,7 @@ export default class StateService {
|
||||
dropBeforeImport,
|
||||
keepExisting,
|
||||
featureEnvironments,
|
||||
userId,
|
||||
});
|
||||
|
||||
if (featureEnvironments) {
|
||||
@ -236,6 +242,7 @@ export default class StateService {
|
||||
userName,
|
||||
dropBeforeImport,
|
||||
keepExisting,
|
||||
userId,
|
||||
});
|
||||
}
|
||||
|
||||
@ -258,6 +265,7 @@ export default class StateService {
|
||||
userName,
|
||||
dropBeforeImport,
|
||||
keepExisting,
|
||||
userId,
|
||||
});
|
||||
}
|
||||
|
||||
@ -265,6 +273,7 @@ export default class StateService {
|
||||
await this.importSegments(
|
||||
data.segments,
|
||||
userName,
|
||||
userId,
|
||||
dropBeforeImport,
|
||||
);
|
||||
}
|
||||
@ -361,6 +370,7 @@ export default class StateService {
|
||||
async importFeatures({
|
||||
features,
|
||||
userName,
|
||||
userId,
|
||||
dropBeforeImport,
|
||||
keepExisting,
|
||||
featureEnvironments,
|
||||
@ -376,6 +386,7 @@ export default class StateService {
|
||||
await this.eventService.storeEvent({
|
||||
type: DROP_FEATURES,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: { name: 'all-features' },
|
||||
});
|
||||
}
|
||||
@ -393,6 +404,7 @@ export default class StateService {
|
||||
);
|
||||
await this.eventService.storeEvent({
|
||||
type: FEATURE_IMPORT,
|
||||
createdByUserId: userId,
|
||||
createdBy: userName,
|
||||
data: feature,
|
||||
});
|
||||
@ -404,6 +416,7 @@ export default class StateService {
|
||||
async importStrategies({
|
||||
strategies,
|
||||
userName,
|
||||
userId,
|
||||
dropBeforeImport,
|
||||
keepExisting,
|
||||
}): Promise<void> {
|
||||
@ -418,6 +431,7 @@ export default class StateService {
|
||||
await this.eventService.storeEvent({
|
||||
type: DROP_STRATEGIES,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: { name: 'all-strategies' },
|
||||
});
|
||||
}
|
||||
@ -431,6 +445,7 @@ export default class StateService {
|
||||
this.eventService.storeEvent({
|
||||
type: STRATEGY_IMPORT,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: strategy,
|
||||
});
|
||||
}),
|
||||
@ -442,6 +457,7 @@ export default class StateService {
|
||||
async importEnvironments({
|
||||
environments,
|
||||
userName,
|
||||
userId,
|
||||
dropBeforeImport,
|
||||
keepExisting,
|
||||
}): Promise<IEnvironment[]> {
|
||||
@ -455,19 +471,21 @@ export default class StateService {
|
||||
await this.eventService.storeEvent({
|
||||
type: DROP_ENVIRONMENTS,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: { name: 'all-environments' },
|
||||
});
|
||||
}
|
||||
const envsImport = environments.filter((env) =>
|
||||
keepExisting ? !oldEnvs.some((old) => old.name === env.name) : true,
|
||||
);
|
||||
let importedEnvs = [];
|
||||
let importedEnvs: IEnvironment[] = [];
|
||||
if (envsImport.length > 0) {
|
||||
importedEnvs =
|
||||
await this.environmentStore.importEnvironments(envsImport);
|
||||
const importedEnvironmentEvents = importedEnvs.map((env) => ({
|
||||
type: ENVIRONMENT_IMPORT,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: env,
|
||||
}));
|
||||
await this.eventService.storeEvents(importedEnvironmentEvents);
|
||||
@ -480,6 +498,7 @@ export default class StateService {
|
||||
projects,
|
||||
importedEnvironments,
|
||||
userName,
|
||||
userId,
|
||||
dropBeforeImport,
|
||||
keepExisting,
|
||||
}): Promise<void> {
|
||||
@ -493,6 +512,7 @@ export default class StateService {
|
||||
await this.eventService.storeEvent({
|
||||
type: DROP_PROJECTS,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: { name: 'all-projects' },
|
||||
});
|
||||
}
|
||||
@ -509,6 +529,7 @@ export default class StateService {
|
||||
const importedProjectEvents = importedProjects.map((project) => ({
|
||||
type: PROJECT_IMPORT,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: project,
|
||||
}));
|
||||
await this.eventService.storeEvents(importedProjectEvents);
|
||||
@ -521,6 +542,7 @@ export default class StateService {
|
||||
tags,
|
||||
featureTags,
|
||||
userName,
|
||||
userId,
|
||||
dropBeforeImport,
|
||||
keepExisting,
|
||||
}): Promise<void> {
|
||||
@ -545,16 +567,19 @@ export default class StateService {
|
||||
{
|
||||
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' },
|
||||
},
|
||||
]);
|
||||
@ -564,13 +589,15 @@ export default class StateService {
|
||||
keepExisting,
|
||||
oldTagTypes,
|
||||
userName,
|
||||
userId,
|
||||
);
|
||||
await this.importTags(tags, keepExisting, oldTags, userName);
|
||||
await this.importTags(tags, keepExisting, oldTags, userName, userId);
|
||||
await this.importFeatureTags(
|
||||
featureTags,
|
||||
keepExisting,
|
||||
oldFeatureTags,
|
||||
userName,
|
||||
userId,
|
||||
);
|
||||
}
|
||||
|
||||
@ -587,6 +614,7 @@ export default class StateService {
|
||||
keepExisting: boolean,
|
||||
oldFeatureTags: IFeatureTag[],
|
||||
userName: string,
|
||||
userId: number,
|
||||
): Promise<void> {
|
||||
const featureTagsToInsert = featureTags.filter((tag) =>
|
||||
keepExisting
|
||||
@ -601,6 +629,7 @@ export default class StateService {
|
||||
const importedFeatureTagEvents = importedFeatureTags.map((tag) => ({
|
||||
type: FEATURE_TAG_IMPORT,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: tag,
|
||||
}));
|
||||
await this.eventService.storeEvents(importedFeatureTagEvents);
|
||||
@ -615,6 +644,7 @@ export default class StateService {
|
||||
keepExisting: boolean,
|
||||
oldTags: ITag[],
|
||||
userName: string,
|
||||
userId: number,
|
||||
): Promise<void> {
|
||||
const tagsToInsert = tags.filter((tag) =>
|
||||
keepExisting
|
||||
@ -626,6 +656,7 @@ export default class StateService {
|
||||
const importedTagEvents = importedTags.map((tag) => ({
|
||||
type: TAG_IMPORT,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: tag,
|
||||
}));
|
||||
await this.eventService.storeEvents(importedTagEvents);
|
||||
@ -637,6 +668,7 @@ export default class StateService {
|
||||
keepExisting: boolean,
|
||||
oldTagTypes: ITagType[],
|
||||
userName: string,
|
||||
userId: number,
|
||||
): Promise<void> {
|
||||
const tagTypesToInsert = tagTypes.filter((tagType) =>
|
||||
keepExisting
|
||||
@ -649,6 +681,7 @@ export default class StateService {
|
||||
const importedTagTypeEvents = importedTagTypes.map((tagType) => ({
|
||||
type: TAG_TYPE_IMPORT,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: tagType,
|
||||
}));
|
||||
await this.eventService.storeEvents(importedTagTypeEvents);
|
||||
@ -658,6 +691,7 @@ export default class StateService {
|
||||
async importSegments(
|
||||
segments: PartialSome<ISegment, 'id'>[],
|
||||
userName: string,
|
||||
userId: number,
|
||||
dropBeforeImport: boolean,
|
||||
): Promise<void> {
|
||||
if (dropBeforeImport) {
|
||||
|
||||
@ -47,6 +47,7 @@ class StrategyService {
|
||||
async removeStrategy(
|
||||
strategyName: string,
|
||||
userName: string,
|
||||
userId: number,
|
||||
): Promise<void> {
|
||||
const strategy = await this.strategyStore.get(strategyName);
|
||||
await this._validateEditable(strategy);
|
||||
@ -54,6 +55,7 @@ class StrategyService {
|
||||
await this.eventService.storeEvent({
|
||||
type: STRATEGY_DELETED,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: {
|
||||
name: strategyName,
|
||||
},
|
||||
@ -63,6 +65,7 @@ class StrategyService {
|
||||
async deprecateStrategy(
|
||||
strategyName: string,
|
||||
userName: string,
|
||||
userId: number,
|
||||
): Promise<void> {
|
||||
if (await this.strategyStore.exists(strategyName)) {
|
||||
// Check existence
|
||||
@ -70,6 +73,7 @@ class StrategyService {
|
||||
await this.eventService.storeEvent({
|
||||
type: STRATEGY_DEPRECATED,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: {
|
||||
name: strategyName,
|
||||
},
|
||||
@ -84,12 +88,14 @@ class StrategyService {
|
||||
async reactivateStrategy(
|
||||
strategyName: string,
|
||||
userName: string,
|
||||
userId: number,
|
||||
): Promise<void> {
|
||||
await this.strategyStore.get(strategyName); // Check existence
|
||||
await this.strategyStore.reactivateStrategy({ name: strategyName });
|
||||
await this.eventService.storeEvent({
|
||||
type: STRATEGY_REACTIVATED,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: {
|
||||
name: strategyName,
|
||||
},
|
||||
@ -99,6 +105,7 @@ class StrategyService {
|
||||
async createStrategy(
|
||||
value: IMinimalStrategy,
|
||||
userName: string,
|
||||
userId: number,
|
||||
): Promise<IStrategy> {
|
||||
const strategy = await strategySchema.validateAsync(value);
|
||||
strategy.deprecated = false;
|
||||
@ -108,6 +115,7 @@ class StrategyService {
|
||||
type: STRATEGY_CREATED,
|
||||
createdBy: userName,
|
||||
data: strategy,
|
||||
createdByUserId: userId,
|
||||
});
|
||||
return this.strategyStore.get(strategy.name);
|
||||
}
|
||||
@ -115,6 +123,7 @@ class StrategyService {
|
||||
async updateStrategy(
|
||||
input: IMinimalStrategy,
|
||||
userName: string,
|
||||
userId: number,
|
||||
): Promise<void> {
|
||||
const value = await strategySchema.validateAsync(input);
|
||||
const strategy = await this.strategyStore.get(input.name);
|
||||
@ -124,6 +133,7 @@ class StrategyService {
|
||||
type: STRATEGY_UPDATED,
|
||||
createdBy: userName,
|
||||
data: value,
|
||||
createdByUserId: userId,
|
||||
});
|
||||
}
|
||||
|
||||
@ -146,7 +156,7 @@ class StrategyService {
|
||||
|
||||
// This check belongs in the store.
|
||||
_validateEditable(strategy: IStrategy): void {
|
||||
if (strategy.editable === false) {
|
||||
if (!strategy.editable) {
|
||||
throw new Error(`Cannot edit strategy ${strategy.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,23 +50,29 @@ export default class TagService {
|
||||
return data;
|
||||
}
|
||||
|
||||
async createTag(tag: ITag, userName: string): Promise<ITag> {
|
||||
async createTag(
|
||||
tag: ITag,
|
||||
userName: string,
|
||||
userId: number,
|
||||
): Promise<ITag> {
|
||||
const data = await this.validate(tag);
|
||||
await this.tagStore.createTag(data);
|
||||
await this.eventService.storeEvent({
|
||||
type: TAG_CREATED,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data,
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async deleteTag(tag: ITag, userName: string): Promise<void> {
|
||||
async deleteTag(tag: ITag, userName: string, userId): Promise<void> {
|
||||
await this.tagStore.delete(tag);
|
||||
await this.eventService.storeEvent({
|
||||
type: TAG_DELETED,
|
||||
createdBy: userName,
|
||||
createdByUserId: userId,
|
||||
data: tag,
|
||||
});
|
||||
}
|
||||
|
||||
@ -239,6 +239,7 @@ class UserService {
|
||||
await this.eventService.storeEvent(
|
||||
new UserCreatedEvent({
|
||||
createdBy: this.getCreatedBy(updatedBy),
|
||||
createdByUserId: user.id,
|
||||
userCreated,
|
||||
}),
|
||||
);
|
||||
@ -281,6 +282,7 @@ class UserService {
|
||||
createdBy: this.getCreatedBy(updatedBy),
|
||||
preUser: preUser,
|
||||
postUser: storedUser,
|
||||
createdByUserId: user.id,
|
||||
}),
|
||||
);
|
||||
|
||||
@ -298,6 +300,7 @@ class UserService {
|
||||
new UserDeletedEvent({
|
||||
createdBy: this.getCreatedBy(updatedBy),
|
||||
deletedUser: user,
|
||||
createdByUserId: updatedBy?.id || -1337,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import { Request } from 'express';
|
||||
import EventEmitter from 'events';
|
||||
import * as https from 'https';
|
||||
import * as http from 'http';
|
||||
import User from './user';
|
||||
import User, { IUser } from './user';
|
||||
import { IUnleashConfig } from './option';
|
||||
import { IUnleashStores } from './stores';
|
||||
import { IUnleashServices } from './services';
|
||||
@ -21,3 +21,14 @@ export interface IUnleash {
|
||||
stop: () => Promise<void>;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export const SYSTEM_USER: IUser = {
|
||||
email: 'systemuser@getunleash.io',
|
||||
id: -1337,
|
||||
imageUrl: '',
|
||||
isAPI: false,
|
||||
name: 'Used by unleash internally for performing system actions that have no user',
|
||||
permissions: [],
|
||||
username: 'unleash_system_user',
|
||||
};
|
||||
export const SYSTEM_USER_ID: number = SYSTEM_USER.id;
|
||||
|
||||
@ -102,6 +102,8 @@ export const ENVIRONMENT_DELETED = 'environment-deleted' as const;
|
||||
export const SEGMENT_CREATED = 'segment-created' as const;
|
||||
export const SEGMENT_UPDATED = 'segment-updated' as const;
|
||||
export const SEGMENT_DELETED = 'segment-deleted' as const;
|
||||
|
||||
export const SEGMENT_IMPORT = 'segment-import' as const;
|
||||
export const GROUP_CREATED = 'group-created' as const;
|
||||
export const GROUP_UPDATED = 'group-updated' as const;
|
||||
export const GROUP_DELETED = 'group-deleted' as const;
|
||||
@ -306,12 +308,14 @@ export const IEventTypes = [
|
||||
PROJECT_ENVIRONMENT_ADDED,
|
||||
PROJECT_ENVIRONMENT_REMOVED,
|
||||
DEFAULT_STRATEGY_UPDATED,
|
||||
SEGMENT_IMPORT,
|
||||
] as const;
|
||||
export type IEventType = (typeof IEventTypes)[number];
|
||||
|
||||
export interface IBaseEvent {
|
||||
type: IEventType;
|
||||
createdBy: string;
|
||||
createdByUserId: number;
|
||||
project?: string;
|
||||
environment?: string;
|
||||
featureName?: string;
|
||||
@ -335,15 +339,24 @@ class BaseEvent implements IBaseEvent {
|
||||
|
||||
readonly createdBy: string;
|
||||
|
||||
readonly createdByUserId: number;
|
||||
|
||||
/**
|
||||
* @param type the type of the event we're creating.
|
||||
* @param createdBy accepts a string for backward compatibility. Prefer using IUser for standardization
|
||||
* @param createdByUserId accepts a number representing the internal id of the user creating this event
|
||||
*/
|
||||
constructor(type: IEventType, createdBy: string | IUser) {
|
||||
constructor(
|
||||
type: IEventType,
|
||||
createdBy: string | IUser,
|
||||
createdByUserId: number,
|
||||
) {
|
||||
this.type = type;
|
||||
this.createdBy =
|
||||
typeof createdBy === 'string'
|
||||
? createdBy
|
||||
: extractUsernameFromUser(createdBy);
|
||||
this.createdByUserId = createdByUserId;
|
||||
}
|
||||
}
|
||||
|
||||
@ -360,8 +373,13 @@ export class FeatureStaleEvent extends BaseEvent {
|
||||
project: string;
|
||||
featureName: string;
|
||||
createdBy: string | IUser;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(p.stale ? FEATURE_STALE_ON : FEATURE_STALE_OFF, p.createdBy);
|
||||
super(
|
||||
p.stale ? FEATURE_STALE_ON : FEATURE_STALE_OFF,
|
||||
p.createdBy,
|
||||
p.createdByUserId,
|
||||
);
|
||||
this.project = p.project;
|
||||
this.featureName = p.featureName;
|
||||
}
|
||||
@ -383,12 +401,14 @@ export class FeatureEnvironmentEvent extends BaseEvent {
|
||||
featureName: string;
|
||||
environment: string;
|
||||
createdBy: string | IUser;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(
|
||||
p.enabled
|
||||
? FEATURE_ENVIRONMENT_ENABLED
|
||||
: FEATURE_ENVIRONMENT_DISABLED,
|
||||
p.createdBy,
|
||||
p.createdByUserId,
|
||||
);
|
||||
this.project = p.project;
|
||||
this.featureName = p.featureName;
|
||||
@ -417,8 +437,9 @@ export class StrategiesOrderChangedEvent extends BaseEvent {
|
||||
createdBy: string | IUser;
|
||||
data: StrategyIds;
|
||||
preData: StrategyIds;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(STRATEGY_ORDER_CHANGED, p.createdBy);
|
||||
super(STRATEGY_ORDER_CHANGED, p.createdBy, p.createdByUserId);
|
||||
const { project, featureName, environment, data, preData } = p;
|
||||
this.project = project;
|
||||
this.featureName = featureName;
|
||||
@ -446,8 +467,9 @@ export class FeatureVariantEvent extends BaseEvent {
|
||||
createdBy: string | IUser;
|
||||
newVariants: IVariant[];
|
||||
oldVariants: IVariant[];
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(FEATURE_VARIANTS_UPDATED, p.createdBy);
|
||||
super(FEATURE_VARIANTS_UPDATED, p.createdBy, p.createdByUserId);
|
||||
this.project = p.project;
|
||||
this.featureName = p.featureName;
|
||||
this.data = { variants: p.newVariants };
|
||||
@ -476,8 +498,13 @@ export class EnvironmentVariantEvent extends BaseEvent {
|
||||
createdBy: string | IUser;
|
||||
newVariants: IVariant[];
|
||||
oldVariants: IVariant[];
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(FEATURE_ENVIRONMENT_VARIANTS_UPDATED, p.createdBy);
|
||||
super(
|
||||
FEATURE_ENVIRONMENT_VARIANTS_UPDATED,
|
||||
p.createdBy,
|
||||
p.createdByUserId,
|
||||
);
|
||||
this.featureName = p.featureName;
|
||||
this.environment = p.environment;
|
||||
this.project = p.project;
|
||||
@ -504,8 +531,9 @@ export class FeatureChangeProjectEvent extends BaseEvent {
|
||||
newProject: string;
|
||||
featureName: string;
|
||||
createdBy: string | IUser;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(FEATURE_PROJECT_CHANGE, p.createdBy);
|
||||
super(FEATURE_PROJECT_CHANGE, p.createdBy, p.createdByUserId);
|
||||
const { newProject, oldProject, featureName } = p;
|
||||
this.project = newProject;
|
||||
this.featureName = featureName;
|
||||
@ -528,8 +556,9 @@ export class FeatureCreatedEvent extends BaseEvent {
|
||||
featureName: string;
|
||||
createdBy: string | IUser;
|
||||
data: FeatureToggle;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(FEATURE_CREATED, p.createdBy);
|
||||
super(FEATURE_CREATED, p.createdBy, p.createdByUserId);
|
||||
const { project, featureName, data } = p;
|
||||
this.project = project;
|
||||
this.featureName = featureName;
|
||||
@ -549,8 +578,9 @@ export class FeatureArchivedEvent extends BaseEvent {
|
||||
project: string;
|
||||
featureName: string;
|
||||
createdBy: string | IUser;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(FEATURE_ARCHIVED, p.createdBy);
|
||||
super(FEATURE_ARCHIVED, p.createdBy, p.createdByUserId);
|
||||
const { project, featureName } = p;
|
||||
this.project = project;
|
||||
this.featureName = featureName;
|
||||
@ -569,8 +599,9 @@ export class FeatureRevivedEvent extends BaseEvent {
|
||||
project: string;
|
||||
featureName: string;
|
||||
createdBy: string | IUser;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(FEATURE_REVIVED, p.createdBy);
|
||||
super(FEATURE_REVIVED, p.createdBy, p.createdByUserId);
|
||||
const { project, featureName } = p;
|
||||
this.project = project;
|
||||
this.featureName = featureName;
|
||||
@ -595,8 +626,9 @@ export class FeatureDeletedEvent extends BaseEvent {
|
||||
preData: FeatureToggle;
|
||||
createdBy: string | IUser;
|
||||
tags: ITag[];
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(FEATURE_DELETED, p.createdBy);
|
||||
super(FEATURE_DELETED, p.createdBy, p.createdByUserId);
|
||||
const { project, featureName, preData } = p;
|
||||
this.project = project;
|
||||
this.featureName = featureName;
|
||||
@ -623,8 +655,9 @@ export class FeatureMetadataUpdateEvent extends BaseEvent {
|
||||
project: string;
|
||||
data: FeatureToggle;
|
||||
preData: FeatureToggle;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(FEATURE_METADATA_UPDATED, p.createdBy);
|
||||
super(FEATURE_METADATA_UPDATED, p.createdBy, p.createdByUserId);
|
||||
const { project, featureName, data, preData } = p;
|
||||
this.project = project;
|
||||
this.featureName = featureName;
|
||||
@ -651,8 +684,9 @@ export class FeatureStrategyAddEvent extends BaseEvent {
|
||||
environment: string;
|
||||
createdBy: string | IUser;
|
||||
data: IStrategyConfig;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(FEATURE_STRATEGY_ADD, p.createdBy);
|
||||
super(FEATURE_STRATEGY_ADD, p.createdBy, p.createdByUserId);
|
||||
const { project, featureName, environment, data } = p;
|
||||
this.project = project;
|
||||
this.featureName = featureName;
|
||||
@ -682,8 +716,9 @@ export class FeatureStrategyUpdateEvent extends BaseEvent {
|
||||
createdBy: string | IUser;
|
||||
data: IStrategyConfig;
|
||||
preData: IStrategyConfig;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(FEATURE_STRATEGY_UPDATE, p.createdBy);
|
||||
super(FEATURE_STRATEGY_UPDATE, p.createdBy, p.createdByUserId);
|
||||
const { project, featureName, environment, data, preData } = p;
|
||||
this.project = project;
|
||||
this.featureName = featureName;
|
||||
@ -711,8 +746,9 @@ export class FeatureStrategyRemoveEvent extends BaseEvent {
|
||||
environment: string;
|
||||
createdBy: string | IUser;
|
||||
preData: IStrategyConfig;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(FEATURE_STRATEGY_REMOVE, p.createdBy);
|
||||
super(FEATURE_STRATEGY_REMOVE, p.createdBy, p.createdByUserId);
|
||||
const { project, featureName, environment, preData } = p;
|
||||
this.project = project;
|
||||
this.featureName = featureName;
|
||||
@ -731,8 +767,13 @@ export class ProjectUserAddedEvent extends BaseEvent {
|
||||
/**
|
||||
* @param createdBy accepts a string for backward compatibility. Prefer using IUser for standardization
|
||||
*/
|
||||
constructor(p: { project: string; createdBy: string | IUser; data: any }) {
|
||||
super(PROJECT_USER_ADDED, p.createdBy);
|
||||
constructor(p: {
|
||||
project: string;
|
||||
createdBy: string | IUser;
|
||||
data: any;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(PROJECT_USER_ADDED, p.createdBy, p.createdByUserId);
|
||||
const { project, data } = p;
|
||||
this.project = project;
|
||||
this.data = data;
|
||||
@ -754,8 +795,9 @@ export class ProjectUserRemovedEvent extends BaseEvent {
|
||||
project: string;
|
||||
createdBy: string | IUser;
|
||||
preData: any;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(PROJECT_USER_REMOVED, p.createdBy);
|
||||
super(PROJECT_USER_REMOVED, p.createdBy, p.createdByUserId);
|
||||
const { project, preData } = p;
|
||||
this.project = project;
|
||||
this.data = null;
|
||||
@ -778,8 +820,13 @@ export class ProjectUserUpdateRoleEvent extends BaseEvent {
|
||||
createdBy: string | IUser;
|
||||
data: any;
|
||||
preData: any;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(PROJECT_USER_ROLE_CHANGED, eventData.createdBy);
|
||||
super(
|
||||
PROJECT_USER_ROLE_CHANGED,
|
||||
eventData.createdBy,
|
||||
eventData.createdByUserId,
|
||||
);
|
||||
const { project, data, preData } = eventData;
|
||||
this.project = project;
|
||||
this.data = data;
|
||||
@ -797,8 +844,13 @@ export class ProjectGroupAddedEvent extends BaseEvent {
|
||||
/**
|
||||
* @param createdBy accepts a string for backward compatibility. Prefer using IUser for standardization
|
||||
*/
|
||||
constructor(p: { project: string; createdBy: string | IUser; data: any }) {
|
||||
super(PROJECT_GROUP_ADDED, p.createdBy);
|
||||
constructor(p: {
|
||||
project: string;
|
||||
createdBy: string | IUser;
|
||||
data: any;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(PROJECT_GROUP_ADDED, p.createdBy, p.createdByUserId);
|
||||
const { project, data } = p;
|
||||
this.project = project;
|
||||
this.data = data;
|
||||
@ -820,8 +872,9 @@ export class ProjectGroupRemovedEvent extends BaseEvent {
|
||||
project: string;
|
||||
createdBy: string | IUser;
|
||||
preData: any;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(PROJECT_GROUP_REMOVED, p.createdBy);
|
||||
super(PROJECT_GROUP_REMOVED, p.createdBy, p.createdByUserId);
|
||||
const { project, preData } = p;
|
||||
this.project = project;
|
||||
this.data = null;
|
||||
@ -844,8 +897,13 @@ export class ProjectGroupUpdateRoleEvent extends BaseEvent {
|
||||
createdBy: string | IUser;
|
||||
data: any;
|
||||
preData: any;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(PROJECT_GROUP_ROLE_CHANGED, eventData.createdBy);
|
||||
super(
|
||||
PROJECT_GROUP_ROLE_CHANGED,
|
||||
eventData.createdBy,
|
||||
eventData.createdByUserId,
|
||||
);
|
||||
const { project, data, preData } = eventData;
|
||||
this.project = project;
|
||||
this.data = data;
|
||||
@ -863,8 +921,13 @@ export class ProjectAccessAddedEvent extends BaseEvent {
|
||||
/**
|
||||
* @param createdBy accepts a string for backward compatibility. Prefer using IUser for standardization
|
||||
*/
|
||||
constructor(p: { project: string; createdBy: string | IUser; data: any }) {
|
||||
super(PROJECT_ACCESS_ADDED, p.createdBy);
|
||||
constructor(p: {
|
||||
project: string;
|
||||
createdBy: string | IUser;
|
||||
data: any;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(PROJECT_ACCESS_ADDED, p.createdBy, p.createdByUserId);
|
||||
const { project, data } = p;
|
||||
this.project = project;
|
||||
this.data = data;
|
||||
@ -887,8 +950,13 @@ export class ProjectAccessUserRolesUpdated extends BaseEvent {
|
||||
createdBy: string | IUser;
|
||||
data: any;
|
||||
preData: any;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(PROJECT_ACCESS_USER_ROLES_UPDATED, p.createdBy);
|
||||
super(
|
||||
PROJECT_ACCESS_USER_ROLES_UPDATED,
|
||||
p.createdBy,
|
||||
p.createdByUserId,
|
||||
);
|
||||
const { project, data, preData } = p;
|
||||
this.project = project;
|
||||
this.data = data;
|
||||
@ -911,8 +979,13 @@ export class ProjectAccessGroupRolesUpdated extends BaseEvent {
|
||||
createdBy: string | IUser;
|
||||
data: any;
|
||||
preData: any;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(PROJECT_ACCESS_GROUP_ROLES_UPDATED, p.createdBy);
|
||||
super(
|
||||
PROJECT_ACCESS_GROUP_ROLES_UPDATED,
|
||||
p.createdBy,
|
||||
p.createdByUserId,
|
||||
);
|
||||
const { project, data, preData } = p;
|
||||
this.project = project;
|
||||
this.data = data;
|
||||
@ -934,8 +1007,13 @@ export class ProjectAccessUserRolesDeleted extends BaseEvent {
|
||||
project: string;
|
||||
createdBy: string | IUser;
|
||||
preData: any;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(PROJECT_ACCESS_USER_ROLES_DELETED, p.createdBy);
|
||||
super(
|
||||
PROJECT_ACCESS_USER_ROLES_DELETED,
|
||||
p.createdBy,
|
||||
p.createdByUserId,
|
||||
);
|
||||
const { project, preData } = p;
|
||||
this.project = project;
|
||||
this.data = null;
|
||||
@ -957,8 +1035,13 @@ export class ProjectAccessGroupRolesDeleted extends BaseEvent {
|
||||
project: string;
|
||||
createdBy: string | IUser;
|
||||
preData: any;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(PROJECT_ACCESS_GROUP_ROLES_DELETED, p.createdBy);
|
||||
super(
|
||||
PROJECT_ACCESS_GROUP_ROLES_DELETED,
|
||||
p.createdBy,
|
||||
p.createdByUserId,
|
||||
);
|
||||
const { project, preData } = p;
|
||||
this.project = project;
|
||||
this.data = null;
|
||||
@ -972,8 +1055,12 @@ export class SettingCreatedEvent extends BaseEvent {
|
||||
/**
|
||||
* @param createdBy accepts a string for backward compatibility. Prefer using IUser for standardization
|
||||
*/
|
||||
constructor(eventData: { createdBy: string | IUser; data: any }) {
|
||||
super(SETTING_CREATED, eventData.createdBy);
|
||||
constructor(eventData: {
|
||||
createdBy: string | IUser;
|
||||
data: any;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(SETTING_CREATED, eventData.createdBy, eventData.createdByUserId);
|
||||
this.data = eventData.data;
|
||||
}
|
||||
}
|
||||
@ -984,8 +1071,12 @@ export class SettingDeletedEvent extends BaseEvent {
|
||||
/**
|
||||
* @param createdBy accepts a string for backward compatibility. Prefer using IUser for standardization
|
||||
*/
|
||||
constructor(eventData: { createdBy: string | IUser; data: any }) {
|
||||
super(SETTING_DELETED, eventData.createdBy);
|
||||
constructor(eventData: {
|
||||
createdBy: string | IUser;
|
||||
data: any;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(SETTING_DELETED, eventData.createdBy, eventData.createdByUserId);
|
||||
this.data = eventData.data;
|
||||
}
|
||||
}
|
||||
@ -998,10 +1089,14 @@ export class SettingUpdatedEvent extends BaseEvent {
|
||||
* @param createdBy accepts a string for backward compatibility. Prefer using IUser for standardization
|
||||
*/
|
||||
constructor(
|
||||
eventData: { createdBy: string | IUser; data: any },
|
||||
eventData: {
|
||||
createdBy: string | IUser;
|
||||
data: any;
|
||||
createdByUserId: number;
|
||||
},
|
||||
preData: any,
|
||||
) {
|
||||
super(SETTING_UPDATED, eventData.createdBy);
|
||||
super(SETTING_UPDATED, eventData.createdBy, eventData.createdByUserId);
|
||||
this.data = eventData.data;
|
||||
this.preData = preData;
|
||||
}
|
||||
@ -1013,8 +1108,16 @@ export class PublicSignupTokenCreatedEvent extends BaseEvent {
|
||||
/**
|
||||
* @param createdBy accepts a string for backward compatibility. Prefer using IUser for standardization
|
||||
*/
|
||||
constructor(eventData: { createdBy: string | IUser; data: any }) {
|
||||
super(PUBLIC_SIGNUP_TOKEN_CREATED, eventData.createdBy);
|
||||
constructor(eventData: {
|
||||
createdBy: string | IUser;
|
||||
data: any;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(
|
||||
PUBLIC_SIGNUP_TOKEN_CREATED,
|
||||
eventData.createdBy,
|
||||
eventData.createdByUserId,
|
||||
);
|
||||
this.data = eventData.data;
|
||||
}
|
||||
}
|
||||
@ -1025,8 +1128,16 @@ export class PublicSignupTokenUpdatedEvent extends BaseEvent {
|
||||
/**
|
||||
* @param createdBy accepts a string for backward compatibility. Prefer using IUser for standardization
|
||||
*/
|
||||
constructor(eventData: { createdBy: string | IUser; data: any }) {
|
||||
super(PUBLIC_SIGNUP_TOKEN_TOKEN_UPDATED, eventData.createdBy);
|
||||
constructor(eventData: {
|
||||
createdBy: string | IUser;
|
||||
data: any;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(
|
||||
PUBLIC_SIGNUP_TOKEN_TOKEN_UPDATED,
|
||||
eventData.createdBy,
|
||||
eventData.createdByUserId,
|
||||
);
|
||||
this.data = eventData.data;
|
||||
}
|
||||
}
|
||||
@ -1037,8 +1148,16 @@ export class PublicSignupTokenUserAddedEvent extends BaseEvent {
|
||||
/**
|
||||
* @param createdBy accepts a string for backward compatibility. Prefer using IUser for standardization
|
||||
*/
|
||||
constructor(eventData: { createdBy: string | IUser; data: any }) {
|
||||
super(PUBLIC_SIGNUP_TOKEN_USER_ADDED, eventData.createdBy);
|
||||
constructor(eventData: {
|
||||
createdBy: string | IUser;
|
||||
data: any;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(
|
||||
PUBLIC_SIGNUP_TOKEN_USER_ADDED,
|
||||
eventData.createdBy,
|
||||
eventData.createdByUserId,
|
||||
);
|
||||
this.data = eventData.data;
|
||||
}
|
||||
}
|
||||
@ -1056,8 +1175,13 @@ export class ApiTokenCreatedEvent extends BaseEvent {
|
||||
constructor(eventData: {
|
||||
createdBy: string | IUser;
|
||||
apiToken: Omit<IApiToken, 'secret'>;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(API_TOKEN_CREATED, eventData.createdBy);
|
||||
super(
|
||||
API_TOKEN_CREATED,
|
||||
eventData.createdBy,
|
||||
eventData.createdByUserId,
|
||||
);
|
||||
this.data = eventData.apiToken;
|
||||
this.environment = eventData.apiToken.environment;
|
||||
this.project = eventData.apiToken.project;
|
||||
@ -1077,8 +1201,13 @@ export class ApiTokenDeletedEvent extends BaseEvent {
|
||||
constructor(eventData: {
|
||||
createdBy: string | IUser;
|
||||
apiToken: Omit<IApiToken, 'secret'>;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(API_TOKEN_DELETED, eventData.createdBy);
|
||||
super(
|
||||
API_TOKEN_DELETED,
|
||||
eventData.createdBy,
|
||||
eventData.createdByUserId,
|
||||
);
|
||||
this.preData = eventData.apiToken;
|
||||
this.environment = eventData.apiToken.environment;
|
||||
this.project = eventData.apiToken.project;
|
||||
@ -1101,8 +1230,13 @@ export class ApiTokenUpdatedEvent extends BaseEvent {
|
||||
createdBy: string | IUser;
|
||||
previousToken: Omit<IApiToken, 'secret'>;
|
||||
apiToken: Omit<IApiToken, 'secret'>;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(API_TOKEN_UPDATED, eventData.createdBy);
|
||||
super(
|
||||
API_TOKEN_UPDATED,
|
||||
eventData.createdBy,
|
||||
eventData.createdByUserId,
|
||||
);
|
||||
this.preData = eventData.previousToken;
|
||||
this.data = eventData.apiToken;
|
||||
this.environment = eventData.apiToken.environment;
|
||||
@ -1115,8 +1249,16 @@ export class PotentiallyStaleOnEvent extends BaseEvent {
|
||||
|
||||
readonly project: string;
|
||||
|
||||
constructor(eventData: { featureName: string; project: string }) {
|
||||
super(FEATURE_POTENTIALLY_STALE_ON, 'unleash-system');
|
||||
constructor(eventData: {
|
||||
featureName: string;
|
||||
project: string;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(
|
||||
FEATURE_POTENTIALLY_STALE_ON,
|
||||
'unleash-system',
|
||||
eventData.createdByUserId,
|
||||
);
|
||||
this.featureName = eventData.featureName;
|
||||
this.project = eventData.project;
|
||||
}
|
||||
@ -1128,8 +1270,9 @@ export class UserCreatedEvent extends BaseEvent {
|
||||
constructor(eventData: {
|
||||
createdBy: string | IUser;
|
||||
userCreated: IUserWithRootRole;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(USER_CREATED, eventData.createdBy);
|
||||
super(USER_CREATED, eventData.createdBy, eventData.createdByUserId);
|
||||
this.data = mapUserToData(eventData.userCreated);
|
||||
}
|
||||
}
|
||||
@ -1142,8 +1285,9 @@ export class UserUpdatedEvent extends BaseEvent {
|
||||
createdBy: string | IUser;
|
||||
preUser: IUserWithRootRole;
|
||||
postUser: IUserWithRootRole;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(USER_UPDATED, eventData.createdBy);
|
||||
super(USER_UPDATED, eventData.createdBy, eventData.createdByUserId);
|
||||
this.preData = mapUserToData(eventData.preUser);
|
||||
this.data = mapUserToData(eventData.postUser);
|
||||
}
|
||||
@ -1155,8 +1299,9 @@ export class UserDeletedEvent extends BaseEvent {
|
||||
constructor(eventData: {
|
||||
createdBy: string | IUser;
|
||||
deletedUser: IUserWithRootRole;
|
||||
createdByUserId: number;
|
||||
}) {
|
||||
super(USER_DELETED, eventData.createdBy);
|
||||
super(USER_DELETED, eventData.createdBy, eventData.createdByUserId);
|
||||
this.preData = mapUserToData(eventData.deletedUser);
|
||||
}
|
||||
}
|
||||
|
||||
@ -443,6 +443,7 @@ interface ImportCommon {
|
||||
dropBeforeImport?: boolean;
|
||||
keepExisting?: boolean;
|
||||
userName?: string;
|
||||
userId: number;
|
||||
}
|
||||
|
||||
export interface IImportData extends ImportCommon {
|
||||
|
||||
@ -12,6 +12,7 @@ export interface IClientApplication {
|
||||
lastSeen: Date;
|
||||
description: string;
|
||||
createdBy: string;
|
||||
createdByUserId?: number;
|
||||
announced: boolean;
|
||||
url: string;
|
||||
color: string;
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
DELETE_CLIENT_API_TOKEN,
|
||||
READ_CLIENT_API_TOKEN,
|
||||
READ_FRONTEND_API_TOKEN,
|
||||
SYSTEM_USER_ID,
|
||||
UPDATE_CLIENT_API_TOKEN,
|
||||
} from '../../../../lib/types';
|
||||
import { addDays } from 'date-fns';
|
||||
@ -197,6 +198,7 @@ test('A role with only CREATE_PROJECT_API_TOKEN can create project tokens', asyn
|
||||
description: 'Can create client tokens',
|
||||
permissions: [{ name: CREATE_PROJECT_API_TOKEN }],
|
||||
type: 'root-custom',
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
});
|
||||
await accessService.addUserToRole(
|
||||
user.id,
|
||||
|
||||
@ -56,6 +56,7 @@ test('gets ui config with frontendSettings', async () => {
|
||||
await app.services.proxyService.setFrontendSettings(
|
||||
{ frontendApiOrigins },
|
||||
randomId(),
|
||||
-9999,
|
||||
);
|
||||
await app.request
|
||||
.get('/api/admin/ui-config')
|
||||
|
||||
@ -11,7 +11,7 @@ import { EventService } from '../../../../lib/services';
|
||||
let app: IUnleashTest;
|
||||
let db: ITestDb;
|
||||
let eventService: EventService;
|
||||
|
||||
const TEST_USER_ID = -9999;
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('event_api_serial', getLogger);
|
||||
app = await setupAppWithCustomConfig(db.stores, {
|
||||
@ -57,6 +57,7 @@ test('Can filter by project', async () => {
|
||||
tags: [],
|
||||
createdBy: 'test-user',
|
||||
environment: 'test',
|
||||
createdByUserId: TEST_USER_ID,
|
||||
});
|
||||
await eventService.storeEvent({
|
||||
type: FEATURE_CREATED,
|
||||
@ -65,6 +66,7 @@ test('Can filter by project', async () => {
|
||||
tags: [],
|
||||
createdBy: 'test-user',
|
||||
environment: 'test',
|
||||
createdByUserId: TEST_USER_ID,
|
||||
});
|
||||
await app.request
|
||||
.get('/api/admin/events?project=default')
|
||||
@ -83,6 +85,7 @@ test('can search for events', async () => {
|
||||
data: { id: randomId() },
|
||||
tags: [],
|
||||
createdBy: randomId(),
|
||||
createdByUserId: TEST_USER_ID,
|
||||
},
|
||||
{
|
||||
type: FEATURE_CREATED,
|
||||
@ -91,6 +94,7 @@ test('can search for events', async () => {
|
||||
preData: { id: randomId() },
|
||||
tags: [{ type: 'simple', value: randomId() }],
|
||||
createdBy: randomId(),
|
||||
createdByUserId: TEST_USER_ID,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -1,18 +1,19 @@
|
||||
import dbInit, { ITestDb } from '../../helpers/database-init';
|
||||
import {
|
||||
IUnleashTest,
|
||||
setupApp,
|
||||
setupAppWithCustomConfig,
|
||||
} from '../../helpers/test-helper';
|
||||
import getLogger from '../../../fixtures/no-logger';
|
||||
import { DEFAULT_ENV } from '../../../../lib/util/constants';
|
||||
import { collectIds } from '../../../../lib/util/collect-ids';
|
||||
import { ApiTokenType } from '../../../../lib/types/models/api-token';
|
||||
import { IUser, SYSTEM_USER } from '../../../../lib/types';
|
||||
|
||||
const importData = require('../../../examples/import.json');
|
||||
|
||||
let app: IUnleashTest;
|
||||
let db: ITestDb;
|
||||
const userId = -9999;
|
||||
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('state_api_serial', getLogger);
|
||||
@ -173,6 +174,8 @@ test('Can roundtrip. I.e. export and then import', async () => {
|
||||
await app.services.environmentService.addEnvironmentToProject(
|
||||
environment,
|
||||
projectId,
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||
projectId,
|
||||
@ -182,6 +185,7 @@ test('Can roundtrip. I.e. export and then import', async () => {
|
||||
description: 'Feature for export',
|
||||
},
|
||||
userName,
|
||||
userId,
|
||||
);
|
||||
await app.services.featureToggleServiceV2.createStrategy(
|
||||
{
|
||||
@ -193,6 +197,7 @@ test('Can roundtrip. I.e. export and then import', async () => {
|
||||
},
|
||||
{ projectId, featureName, environment },
|
||||
userName,
|
||||
{ id: userId } as IUser,
|
||||
);
|
||||
const data = await app.services.stateService.export({});
|
||||
await app.services.stateService.import({
|
||||
@ -200,6 +205,7 @@ test('Can roundtrip. I.e. export and then import', async () => {
|
||||
dropBeforeImport: true,
|
||||
keepExisting: false,
|
||||
userName: 'export-tester',
|
||||
userId: -9999,
|
||||
});
|
||||
});
|
||||
|
||||
@ -221,6 +227,8 @@ test('Roundtrip with tags works', async () => {
|
||||
await app.services.environmentService.addEnvironmentToProject(
|
||||
environment,
|
||||
projectId,
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||
projectId,
|
||||
@ -230,6 +238,7 @@ test('Roundtrip with tags works', async () => {
|
||||
description: 'Feature for export',
|
||||
},
|
||||
userName,
|
||||
userId,
|
||||
);
|
||||
await app.services.featureToggleServiceV2.createStrategy(
|
||||
{
|
||||
@ -250,11 +259,13 @@ test('Roundtrip with tags works', async () => {
|
||||
featureName,
|
||||
{ type: 'simple', value: 'export-test' },
|
||||
userName,
|
||||
-9999,
|
||||
);
|
||||
await app.services.featureTagService.addTag(
|
||||
featureName,
|
||||
{ type: 'simple', value: 'export-test-2' },
|
||||
userName,
|
||||
-9999,
|
||||
);
|
||||
const data = await app.services.stateService.export({});
|
||||
await app.services.stateService.import({
|
||||
@ -262,6 +273,7 @@ test('Roundtrip with tags works', async () => {
|
||||
dropBeforeImport: true,
|
||||
keepExisting: false,
|
||||
userName: 'export-tester',
|
||||
userId: -9999,
|
||||
});
|
||||
|
||||
const f = await app.services.featureTagService.listTags(featureName);
|
||||
@ -292,15 +304,20 @@ test('Roundtrip with strategies in multiple environments works', async () => {
|
||||
description: 'Feature for export',
|
||||
},
|
||||
userName,
|
||||
userId,
|
||||
);
|
||||
await app.services.environmentService.addEnvironmentToProject(
|
||||
environment,
|
||||
projectId,
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
|
||||
await app.services.environmentService.addEnvironmentToProject(
|
||||
DEFAULT_ENV,
|
||||
projectId,
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
await app.services.featureToggleServiceV2.createStrategy(
|
||||
{
|
||||
@ -330,6 +347,7 @@ test('Roundtrip with strategies in multiple environments works', async () => {
|
||||
dropBeforeImport: true,
|
||||
keepExisting: false,
|
||||
userName: 'export-tester',
|
||||
userId: -9999,
|
||||
});
|
||||
const f = await app.services.featureToggleServiceV2.getFeature({
|
||||
featureName,
|
||||
@ -387,6 +405,8 @@ test(`should not delete api_tokens on import when drop-flag is set`, async () =>
|
||||
await app.services.environmentService.addEnvironmentToProject(
|
||||
environment,
|
||||
projectId,
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||
projectId,
|
||||
@ -396,6 +416,7 @@ test(`should not delete api_tokens on import when drop-flag is set`, async () =>
|
||||
description: 'Feature for export',
|
||||
},
|
||||
userName,
|
||||
userId,
|
||||
);
|
||||
await app.services.apiTokenService.createApiTokenWithProjects({
|
||||
tokenName: apiTokenName,
|
||||
@ -410,6 +431,7 @@ test(`should not delete api_tokens on import when drop-flag is set`, async () =>
|
||||
dropBeforeImport: true,
|
||||
keepExisting: false,
|
||||
userName: userName,
|
||||
userId: -9999,
|
||||
});
|
||||
|
||||
const apiTokens = await app.services.apiTokenService.getAllTokens();
|
||||
|
||||
@ -6,10 +6,11 @@ import dbInit, { ITestDb } from '../../helpers/database-init';
|
||||
import getLogger from '../../../fixtures/no-logger';
|
||||
import { DEFAULT_ENV } from '../../../../lib/util/constants';
|
||||
import User from '../../../../lib/types/user';
|
||||
import { SYSTEM_USER } from '../../../../lib/types';
|
||||
|
||||
let app: IUnleashTest;
|
||||
let db: ITestDb;
|
||||
const testUser = { name: 'test' } as User;
|
||||
const testUser = { name: 'test', id: -9999 } as User;
|
||||
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('feature_api_client', getLogger);
|
||||
@ -32,6 +33,7 @@ beforeAll(async () => {
|
||||
impressionData: true,
|
||||
},
|
||||
'test',
|
||||
testUser.id,
|
||||
);
|
||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||
'default',
|
||||
@ -40,6 +42,7 @@ beforeAll(async () => {
|
||||
description: 'soon to be the #1 feature',
|
||||
},
|
||||
'test',
|
||||
testUser.id,
|
||||
);
|
||||
|
||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||
@ -49,6 +52,7 @@ beforeAll(async () => {
|
||||
description: 'terrible feature',
|
||||
},
|
||||
'test',
|
||||
testUser.id,
|
||||
);
|
||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||
'default',
|
||||
@ -57,12 +61,14 @@ beforeAll(async () => {
|
||||
description: 'the #1 feature',
|
||||
},
|
||||
'test',
|
||||
testUser.id,
|
||||
);
|
||||
// depend on enabled feature with variant
|
||||
await app.services.dependentFeaturesService.unprotectedUpsertFeatureDependency(
|
||||
{ child: 'featureY', projectId: 'default' },
|
||||
{ feature: 'featureX', variants: ['featureXVariant'] },
|
||||
'test',
|
||||
testUser.id,
|
||||
);
|
||||
|
||||
await app.services.featureToggleServiceV2.archiveToggle(
|
||||
@ -77,6 +83,7 @@ beforeAll(async () => {
|
||||
description: 'soon to be the #1 feature',
|
||||
},
|
||||
'test',
|
||||
testUser.id,
|
||||
);
|
||||
|
||||
await app.services.featureToggleServiceV2.archiveToggle(
|
||||
@ -90,6 +97,7 @@ beforeAll(async () => {
|
||||
description: 'terrible feature',
|
||||
},
|
||||
'test',
|
||||
testUser.id,
|
||||
);
|
||||
await app.services.featureToggleServiceV2.archiveToggle(
|
||||
'featureArchivedZ',
|
||||
@ -102,6 +110,7 @@ beforeAll(async () => {
|
||||
description: 'A feature toggle with variants',
|
||||
},
|
||||
'test',
|
||||
testUser.id,
|
||||
);
|
||||
await app.services.featureToggleServiceV2.saveVariants(
|
||||
'feature.with.variants',
|
||||
@ -121,6 +130,7 @@ beforeAll(async () => {
|
||||
},
|
||||
],
|
||||
'ivar',
|
||||
testUser.id,
|
||||
);
|
||||
});
|
||||
|
||||
@ -243,6 +253,8 @@ test('Can get strategies for specific environment', async () => {
|
||||
await app.services.environmentService.addEnvironmentToProject(
|
||||
'testing',
|
||||
'default',
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
|
||||
await app.request
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
import {
|
||||
IUnleashTest,
|
||||
setupApp,
|
||||
setupAppWithCustomConfig,
|
||||
} from '../../helpers/test-helper';
|
||||
import dbInit, { ITestDb } from '../../helpers/database-init';
|
||||
import getLogger from '../../../fixtures/no-logger';
|
||||
import { DEFAULT_ENV } from '../../../../lib/util/constants';
|
||||
import { IUser } from '../../../../lib/types';
|
||||
|
||||
let app: IUnleashTest;
|
||||
let db: ITestDb;
|
||||
|
||||
const featureName = 'feature.default.1';
|
||||
const username = 'test';
|
||||
const userId = -9999;
|
||||
const projectId = 'default';
|
||||
|
||||
beforeAll(async () => {
|
||||
@ -25,12 +26,14 @@ beforeAll(async () => {
|
||||
description: 'the #1 feature',
|
||||
},
|
||||
username,
|
||||
userId,
|
||||
);
|
||||
|
||||
await app.services.featureToggleServiceV2.createStrategy(
|
||||
{ name: 'default', constraints: [], parameters: {} },
|
||||
{ projectId, featureName, environment: DEFAULT_ENV },
|
||||
username,
|
||||
{ id: userId } as IUser,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ import User from '../../../../lib/types/user';
|
||||
|
||||
let app: IUnleashTest;
|
||||
let db: ITestDb;
|
||||
const testUser = { name: 'test' } as User;
|
||||
const testUser = { name: 'test', id: -9999 } as User;
|
||||
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('feature_304_api_client', getLogger);
|
||||
@ -29,6 +29,7 @@ beforeAll(async () => {
|
||||
impressionData: true,
|
||||
},
|
||||
'test',
|
||||
testUser.id,
|
||||
);
|
||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||
'default',
|
||||
@ -37,6 +38,7 @@ beforeAll(async () => {
|
||||
description: 'soon to be the #1 feature',
|
||||
},
|
||||
'test',
|
||||
testUser.id,
|
||||
);
|
||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||
'default',
|
||||
@ -45,6 +47,7 @@ beforeAll(async () => {
|
||||
description: 'terrible feature',
|
||||
},
|
||||
'test',
|
||||
testUser.id,
|
||||
);
|
||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||
'default',
|
||||
@ -53,6 +56,7 @@ beforeAll(async () => {
|
||||
description: 'the #1 feature',
|
||||
},
|
||||
'test',
|
||||
testUser.id,
|
||||
);
|
||||
|
||||
await app.services.featureToggleServiceV2.archiveToggle(
|
||||
@ -67,6 +71,7 @@ beforeAll(async () => {
|
||||
description: 'soon to be the #1 feature',
|
||||
},
|
||||
'test',
|
||||
testUser.id,
|
||||
);
|
||||
|
||||
await app.services.featureToggleServiceV2.archiveToggle(
|
||||
@ -80,6 +85,7 @@ beforeAll(async () => {
|
||||
description: 'terrible feature',
|
||||
},
|
||||
'test',
|
||||
testUser.id,
|
||||
);
|
||||
await app.services.featureToggleServiceV2.archiveToggle(
|
||||
'featureArchivedZ',
|
||||
@ -92,6 +98,7 @@ beforeAll(async () => {
|
||||
description: 'A feature toggle with variants',
|
||||
},
|
||||
'test',
|
||||
testUser.id,
|
||||
);
|
||||
await app.services.featureToggleServiceV2.saveVariants(
|
||||
'feature.with.variants',
|
||||
@ -111,6 +118,7 @@ beforeAll(async () => {
|
||||
},
|
||||
],
|
||||
'ivar',
|
||||
testUser.id,
|
||||
);
|
||||
});
|
||||
|
||||
@ -143,6 +151,7 @@ test('returns 200 when content updates and hash does not match anymore', async (
|
||||
description: 'the #1 feature',
|
||||
},
|
||||
'test',
|
||||
testUser.id,
|
||||
);
|
||||
await app.services.configurationRevisionService.updateMaxRevisionId();
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import getLogger from '../../../fixtures/no-logger';
|
||||
import { ApiTokenService } from '../../../../lib/services/api-token-service';
|
||||
import { ApiTokenType } from '../../../../lib/types/models/api-token';
|
||||
import { DEFAULT_ENV } from '../../../../lib/util/constants';
|
||||
import { SYSTEM_USER } from '../../../../lib/types';
|
||||
|
||||
let app: IUnleashTest;
|
||||
let db: ITestDb;
|
||||
@ -14,6 +15,7 @@ const environment = 'testing';
|
||||
const project = 'default';
|
||||
const project2 = 'some';
|
||||
const tokenName = 'test';
|
||||
const tokenUserId = -9999;
|
||||
const feature1 = 'f1.token.access';
|
||||
const feature2 = 'f2.token.access';
|
||||
const feature3 = 'f3.p2.token.access';
|
||||
@ -38,8 +40,18 @@ beforeAll(async () => {
|
||||
mode: 'open' as const,
|
||||
});
|
||||
|
||||
await environmentService.addEnvironmentToProject(environment, project);
|
||||
await environmentService.addEnvironmentToProject(environment, project2);
|
||||
await environmentService.addEnvironmentToProject(
|
||||
environment,
|
||||
project,
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
await environmentService.addEnvironmentToProject(
|
||||
environment,
|
||||
project2,
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
|
||||
await featureToggleServiceV2.createFeatureToggle(
|
||||
project,
|
||||
@ -48,6 +60,7 @@ beforeAll(async () => {
|
||||
description: 'the #1 feature',
|
||||
},
|
||||
tokenName,
|
||||
tokenUserId,
|
||||
);
|
||||
|
||||
await featureToggleServiceV2.createStrategy(
|
||||
@ -76,6 +89,7 @@ beforeAll(async () => {
|
||||
name: feature2,
|
||||
},
|
||||
tokenName,
|
||||
tokenUserId,
|
||||
);
|
||||
await featureToggleServiceV2.createStrategy(
|
||||
{
|
||||
@ -94,6 +108,7 @@ beforeAll(async () => {
|
||||
name: feature3,
|
||||
},
|
||||
tokenName,
|
||||
tokenUserId,
|
||||
);
|
||||
await featureToggleServiceV2.createStrategy(
|
||||
{
|
||||
|
||||
@ -8,7 +8,7 @@ let app: IUnleashTest;
|
||||
let db: ITestDb;
|
||||
|
||||
let defaultToken;
|
||||
|
||||
const TEST_USER_ID = -9999;
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('metrics_two_api_client', getLogger);
|
||||
app = await setupAppWithAuth(db.stores, {}, db.rawDatabase);
|
||||
@ -104,11 +104,13 @@ test('should set lastSeen for toggles with metrics both for toggle and toggle en
|
||||
'default',
|
||||
{ name: 't1' },
|
||||
'tester',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||
'default',
|
||||
{ name: 't2' },
|
||||
'tester',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
const token = await app.services.apiTokenService.createApiToken({
|
||||
|
||||
@ -12,13 +12,14 @@ import {
|
||||
FEATURE_UPDATED,
|
||||
IConstraint,
|
||||
IStrategyConfig,
|
||||
SYSTEM_USER,
|
||||
} from '../../../../lib/types';
|
||||
import { ProxyRepository } from '../../../../lib/proxy';
|
||||
import { Logger } from '../../../../lib/logger';
|
||||
|
||||
let app: IUnleashTest;
|
||||
let db: ITestDb;
|
||||
|
||||
const TEST_USER_ID = -9999;
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('proxy', getLogger);
|
||||
app = await setupAppWithAuth(
|
||||
@ -78,6 +79,7 @@ const createFeatureToggle = async ({
|
||||
project,
|
||||
{ name },
|
||||
'userName',
|
||||
TEST_USER_ID,
|
||||
true,
|
||||
);
|
||||
const createdStrategies = await Promise.all(
|
||||
@ -688,10 +690,14 @@ test('should filter features by environment', async () => {
|
||||
await app.services.environmentService.addEnvironmentToProject(
|
||||
environmentA,
|
||||
'default',
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
await app.services.environmentService.addEnvironmentToProject(
|
||||
environmentB,
|
||||
'default',
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
const frontendTokenEnvironmentDefault = await createApiToken(
|
||||
ApiTokenType.FRONTEND,
|
||||
|
||||
@ -3,6 +3,7 @@ import getLogger from '../../fixtures/no-logger';
|
||||
|
||||
import {
|
||||
AccessService,
|
||||
IRoleUpdate,
|
||||
PermissionRef,
|
||||
} from '../../../lib/services/access-service';
|
||||
|
||||
@ -34,6 +35,7 @@ let adminRole;
|
||||
let readRole;
|
||||
|
||||
let userIndex = 0;
|
||||
const TEST_USER_ID = -9999;
|
||||
const createUser = async (role?: number) => {
|
||||
const name = `User ${userIndex}`;
|
||||
const email = `user-${userIndex}@getunleash.io`;
|
||||
@ -73,6 +75,7 @@ const createRole = async (rolePermissions: PermissionRef[]) => {
|
||||
name: `Role ${roleIndex}`,
|
||||
description: `Role ${roleIndex++} description`,
|
||||
permissions: rolePermissions,
|
||||
createdByUserId: TEST_USER_ID,
|
||||
});
|
||||
};
|
||||
|
||||
@ -737,7 +740,7 @@ test('Should be denied access to delete a role that is in use', async () => {
|
||||
await projectService.addUser(project.id, customRole.id, projectMember.id);
|
||||
|
||||
try {
|
||||
await accessService.deleteRole(customRole.id);
|
||||
await accessService.deleteRole(customRole.id, 'testuser', TEST_USER_ID);
|
||||
} catch (e) {
|
||||
expect(e.toString()).toBe(
|
||||
'RoleInUseError: Role is in use by users(1) or groups(0). You cannot delete a role that is in use without first removing the role from the users and groups.',
|
||||
@ -822,7 +825,8 @@ test('Should not be allowed to edit a root role', async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const editRole = await accessService.getRoleByName(RoleName.EDITOR);
|
||||
const roleUpdate = {
|
||||
const roleUpdate: IRoleUpdate = {
|
||||
createdByUserId: TEST_USER_ID,
|
||||
id: editRole.id,
|
||||
name: 'NoLongerTheEditor',
|
||||
description: '',
|
||||
@ -843,7 +847,7 @@ test('Should not be allowed to delete a root role', async () => {
|
||||
const editRole = await accessService.getRoleByName(RoleName.EDITOR);
|
||||
|
||||
try {
|
||||
await accessService.deleteRole(editRole.id);
|
||||
await accessService.deleteRole(editRole.id, 'testuser', TEST_USER_ID);
|
||||
} catch (e) {
|
||||
expect(e.toString()).toBe(
|
||||
'InvalidOperationError: You cannot change built in roles.',
|
||||
@ -855,7 +859,8 @@ test('Should not be allowed to edit a project role', async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const ownerRole = await accessService.getRoleByName(RoleName.OWNER);
|
||||
const roleUpdate = {
|
||||
const roleUpdate: IRoleUpdate = {
|
||||
createdByUserId: TEST_USER_ID,
|
||||
id: ownerRole.id,
|
||||
name: 'NoLongerTheEditor',
|
||||
description: '',
|
||||
@ -876,7 +881,7 @@ test('Should not be allowed to delete a project role', async () => {
|
||||
const ownerRole = await accessService.getRoleByName(RoleName.OWNER);
|
||||
|
||||
try {
|
||||
await accessService.deleteRole(ownerRole.id);
|
||||
await accessService.deleteRole(ownerRole.id, 'testuser', TEST_USER_ID);
|
||||
} catch (e) {
|
||||
expect(e.toString()).toBe(
|
||||
'InvalidOperationError: You cannot change built in roles.',
|
||||
|
||||
@ -14,6 +14,7 @@ const addonProvider = { simple: new SimpleAddon() };
|
||||
let db;
|
||||
let stores: IUnleashStores;
|
||||
let addonService: AddonService;
|
||||
const TEST_USER_ID = -9999;
|
||||
|
||||
beforeAll(async () => {
|
||||
const config = createTestConfig({
|
||||
@ -77,9 +78,9 @@ test('should only return active addons', async () => {
|
||||
description: '',
|
||||
};
|
||||
|
||||
await addonService.createAddon(config, 'me@mail.com');
|
||||
await addonService.createAddon(config2, 'me@mail.com');
|
||||
await addonService.createAddon(config3, 'me@mail.com');
|
||||
await addonService.createAddon(config, 'me@mail.com', TEST_USER_ID);
|
||||
await addonService.createAddon(config2, 'me@mail.com', TEST_USER_ID);
|
||||
await addonService.createAddon(config3, 'me@mail.com', TEST_USER_ID);
|
||||
|
||||
jest.advanceTimersByTime(61_000);
|
||||
|
||||
|
||||
@ -117,7 +117,7 @@ test('should update expiry of token', async () => {
|
||||
'tester',
|
||||
);
|
||||
|
||||
await apiTokenService.updateExpiry(token.secret, newTime, 'tester');
|
||||
await apiTokenService.updateExpiry(token.secret, newTime, 'tester', -9999);
|
||||
|
||||
const [updatedToken] = await apiTokenService.getAllTokens();
|
||||
|
||||
|
||||
@ -186,6 +186,7 @@ test('adding a root role to a group with a project role should not fail', async
|
||||
description: 'root_group',
|
||||
},
|
||||
'test',
|
||||
-9999,
|
||||
);
|
||||
|
||||
await stores.accessStore.addGroupToRole(group.id, 1, 'test', 'default');
|
||||
@ -200,6 +201,7 @@ test('adding a root role to a group with a project role should not fail', async
|
||||
createdBy: 'test',
|
||||
},
|
||||
'test',
|
||||
-9999,
|
||||
);
|
||||
|
||||
expect(updatedGroup).toMatchObject({
|
||||
@ -256,6 +258,7 @@ test('adding a nonexistent role to a group should fail', async () => {
|
||||
createdBy: 'test',
|
||||
},
|
||||
'test',
|
||||
-9999,
|
||||
);
|
||||
}).rejects.toThrow(
|
||||
'Request validation failed: your request body or params contain invalid data: Incorrect role id 100',
|
||||
|
||||
@ -17,7 +17,12 @@ import {
|
||||
createFeatureToggleService,
|
||||
createProjectService,
|
||||
} from '../../../lib/features';
|
||||
import { IGroup, IUnleashStores } from 'lib/types';
|
||||
import {
|
||||
IGroup,
|
||||
IUnleashStores,
|
||||
SYSTEM_USER,
|
||||
SYSTEM_USER_ID,
|
||||
} from '../../../lib/types';
|
||||
import { User } from 'lib/server-impl';
|
||||
|
||||
let stores: IUnleashStores;
|
||||
@ -30,6 +35,7 @@ let environmentService: EnvironmentService;
|
||||
let featureToggleService: FeatureToggleService;
|
||||
let user: User; // many methods in this test use User instead of IUser
|
||||
let group: IGroup;
|
||||
const TEST_USER_ID = -9999;
|
||||
|
||||
const isProjectUser = async (
|
||||
userId: number,
|
||||
@ -487,6 +493,7 @@ test('should remove user from the project', async () => {
|
||||
memberRole.id,
|
||||
projectMember1.id,
|
||||
'test',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
const { users } = await projectService.getAccessToProject(project.id);
|
||||
@ -511,6 +518,7 @@ test('should not change project if feature toggle project does not match current
|
||||
project.id,
|
||||
toggle,
|
||||
user.email,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
try {
|
||||
@ -542,6 +550,7 @@ test('should return 404 if no project is found with the project id', async () =>
|
||||
project.id,
|
||||
toggle,
|
||||
user.email,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
try {
|
||||
@ -585,6 +594,7 @@ test('should fail if user is not authorized', async () => {
|
||||
project.id,
|
||||
toggle,
|
||||
user.email,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
try {
|
||||
@ -621,6 +631,7 @@ test('should change project when checks pass', async () => {
|
||||
projectA.id,
|
||||
toggle,
|
||||
user.email,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
await projectService.changeProject(
|
||||
projectB.id,
|
||||
@ -655,6 +666,7 @@ test('changing project should emit event even if user does not have a username s
|
||||
projectA.id,
|
||||
toggle,
|
||||
user.email,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
const eventsBeforeChange = await stores.eventStore.getEvents();
|
||||
await projectService.changeProject(
|
||||
@ -689,11 +701,14 @@ test('should require equal project environments to move features', async () => {
|
||||
projectA.id,
|
||||
toggle,
|
||||
user.email,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
await stores.environmentStore.create(environment);
|
||||
await environmentService.addEnvironmentToProject(
|
||||
environment.name,
|
||||
projectB.id,
|
||||
'test',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
await expect(() =>
|
||||
@ -810,6 +825,7 @@ test('should add a user to the project with a custom role', async () => {
|
||||
id: 8, // DELETE_FEATURE
|
||||
},
|
||||
],
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
});
|
||||
|
||||
await projectService.addUser(
|
||||
@ -860,6 +876,7 @@ test('should delete role entries when deleting project', async () => {
|
||||
id: 8, // DELETE_FEATURE
|
||||
},
|
||||
],
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
});
|
||||
|
||||
await projectService.addUser(project.id, customRole.id, user1.id, 'test');
|
||||
@ -900,6 +917,7 @@ test('should change a users role in the project', async () => {
|
||||
id: 8, // DELETE_FEATURE
|
||||
},
|
||||
],
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
});
|
||||
const member = await stores.roleStore.getRoleByName(RoleName.MEMBER);
|
||||
|
||||
@ -915,6 +933,7 @@ test('should change a users role in the project', async () => {
|
||||
member.id,
|
||||
projectUser.id,
|
||||
'test',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
await projectService.addUser(
|
||||
project.id,
|
||||
@ -962,6 +981,7 @@ test('should update role for user on project', async () => {
|
||||
ownerRole.id,
|
||||
projectMember1.id,
|
||||
'test',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
const { users } = await projectService.getAccessToProject(project.id);
|
||||
@ -1006,6 +1026,7 @@ test('should able to assign role without existing members', async () => {
|
||||
testRole.id,
|
||||
projectMember1.id,
|
||||
'test',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
const { users } = await projectService.getAccessToProject(project.id);
|
||||
@ -1036,13 +1057,19 @@ describe('ensure project has at least one owner', () => {
|
||||
ownerRole.id,
|
||||
user.id,
|
||||
'test',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
}).rejects.toThrowError(
|
||||
new Error('A project must have at least one owner'),
|
||||
);
|
||||
|
||||
await expect(async () => {
|
||||
await projectService.removeUserAccess(project.id, user.id, 'test');
|
||||
await projectService.removeUserAccess(
|
||||
project.id,
|
||||
user.id,
|
||||
'test',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
}).rejects.toThrowError(
|
||||
new Error('A project must have at least one owner'),
|
||||
);
|
||||
@ -1073,6 +1100,7 @@ describe('ensure project has at least one owner', () => {
|
||||
[],
|
||||
[memberUser.id],
|
||||
'test',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
const usersBefore = await projectService.getProjectUsers(project.id);
|
||||
@ -1080,6 +1108,7 @@ describe('ensure project has at least one owner', () => {
|
||||
project.id,
|
||||
memberUser.id,
|
||||
'test',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
const usersAfter = await projectService.getProjectUsers(project.id);
|
||||
expect(usersBefore).toHaveLength(2);
|
||||
@ -1118,6 +1147,7 @@ describe('ensure project has at least one owner', () => {
|
||||
memberRole.id,
|
||||
user.id,
|
||||
'test',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
}).rejects.toThrowError(
|
||||
new Error('A project must have at least one owner'),
|
||||
@ -1129,6 +1159,7 @@ describe('ensure project has at least one owner', () => {
|
||||
user.id,
|
||||
[memberRole.id],
|
||||
'test',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
}).rejects.toThrowError(
|
||||
new Error('A project must have at least one owner'),
|
||||
@ -1153,6 +1184,7 @@ describe('ensure project has at least one owner', () => {
|
||||
ownerRole.id,
|
||||
group.id,
|
||||
'test',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
// this should be fine, leaving the group as the only owner
|
||||
@ -1162,6 +1194,7 @@ describe('ensure project has at least one owner', () => {
|
||||
ownerRole.id,
|
||||
user.id,
|
||||
'test',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
return {
|
||||
@ -1182,6 +1215,7 @@ describe('ensure project has at least one owner', () => {
|
||||
ownerRole.id,
|
||||
group.id,
|
||||
'test',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
}).rejects.toThrowError(
|
||||
new Error('A project must have at least one owner'),
|
||||
@ -1192,6 +1226,7 @@ describe('ensure project has at least one owner', () => {
|
||||
project.id,
|
||||
group.id,
|
||||
'test',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
}).rejects.toThrowError(
|
||||
new Error('A project must have at least one owner'),
|
||||
@ -1212,6 +1247,7 @@ describe('ensure project has at least one owner', () => {
|
||||
memberRole.id,
|
||||
group.id,
|
||||
'test',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
}).rejects.toThrowError(
|
||||
new Error('A project must have at least one owner'),
|
||||
@ -1223,6 +1259,7 @@ describe('ensure project has at least one owner', () => {
|
||||
group.id,
|
||||
[memberRole.id],
|
||||
'test',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
}).rejects.toThrowError(
|
||||
new Error('A project must have at least one owner'),
|
||||
@ -1258,6 +1295,7 @@ test('Should allow bulk update of group permissions', async () => {
|
||||
id: 2, // CREATE_FEATURE
|
||||
},
|
||||
],
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
});
|
||||
|
||||
await projectService.addAccess(
|
||||
@ -1266,6 +1304,7 @@ test('Should allow bulk update of group permissions', async () => {
|
||||
[group1.id],
|
||||
[user1.id],
|
||||
'some-admin-user',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
});
|
||||
|
||||
@ -1285,6 +1324,7 @@ test('Should bulk update of only users', async () => {
|
||||
id: 2, // CREATE_FEATURE
|
||||
},
|
||||
],
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
});
|
||||
|
||||
await projectService.addAccess(
|
||||
@ -1293,6 +1333,7 @@ test('Should bulk update of only users', async () => {
|
||||
[],
|
||||
[user1.id],
|
||||
'some-admin-user',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
});
|
||||
|
||||
@ -1320,6 +1361,7 @@ test('Should allow bulk update of only groups', async () => {
|
||||
id: 2, // CREATE_FEATURE
|
||||
},
|
||||
],
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
});
|
||||
|
||||
await projectService.addAccess(
|
||||
@ -1328,6 +1370,7 @@ test('Should allow bulk update of only groups', async () => {
|
||||
[group1.id],
|
||||
[],
|
||||
'some-admin-user',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
});
|
||||
|
||||
@ -1369,6 +1412,7 @@ test('Should allow permutations of roles, groups and users when adding a new acc
|
||||
id: 2, // CREATE_FEATURE
|
||||
},
|
||||
],
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
});
|
||||
|
||||
const role2 = await accessService.createRole({
|
||||
@ -1379,6 +1423,7 @@ test('Should allow permutations of roles, groups and users when adding a new acc
|
||||
id: 7, // UPDATE_FEATURE
|
||||
},
|
||||
],
|
||||
createdByUserId: SYSTEM_USER_ID,
|
||||
});
|
||||
|
||||
await projectService.addAccess(
|
||||
@ -1387,6 +1432,7 @@ test('Should allow permutations of roles, groups and users when adding a new acc
|
||||
[group1.id, group2.id],
|
||||
[user1.id, user2.id],
|
||||
'some-admin-user',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
|
||||
const { users, groups } = await projectService.getAccessToProject(
|
||||
@ -1487,6 +1533,7 @@ test('should calculate average time to production', async () => {
|
||||
project.id,
|
||||
toggle,
|
||||
user.email,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
}),
|
||||
);
|
||||
@ -1500,6 +1547,7 @@ test('should calculate average time to production', async () => {
|
||||
featureName: toggle.name,
|
||||
environment: 'default',
|
||||
createdBy: 'Fredrik',
|
||||
createdByUserId: TEST_USER_ID,
|
||||
}),
|
||||
);
|
||||
}),
|
||||
@ -1534,6 +1582,7 @@ test('should calculate average time to production ignoring some items', async ()
|
||||
featureName,
|
||||
environment: 'default',
|
||||
createdBy: 'Fredrik',
|
||||
createdByUserId: TEST_USER_ID,
|
||||
tags: [],
|
||||
});
|
||||
|
||||
@ -1542,7 +1591,12 @@ test('should calculate average time to production ignoring some items', async ()
|
||||
name: 'customEnv',
|
||||
type: 'development',
|
||||
});
|
||||
await environmentService.addEnvironmentToProject('customEnv', project.id);
|
||||
await environmentService.addEnvironmentToProject(
|
||||
'customEnv',
|
||||
project.id,
|
||||
SYSTEM_USER.username,
|
||||
SYSTEM_USER.id,
|
||||
);
|
||||
|
||||
// actual toggle we take for calculations
|
||||
const toggle = { name: 'main-toggle' };
|
||||
@ -1550,6 +1604,7 @@ test('should calculate average time to production ignoring some items', async ()
|
||||
project.id,
|
||||
toggle,
|
||||
user.email,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
await updateFeature(toggle.name, {
|
||||
created_at: subDays(new Date(), 20),
|
||||
@ -1569,6 +1624,7 @@ test('should calculate average time to production ignoring some items', async ()
|
||||
project.id,
|
||||
devToggle,
|
||||
user.email,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
await eventService.storeEvent(
|
||||
new FeatureEnvironmentEvent({
|
||||
@ -1583,6 +1639,7 @@ test('should calculate average time to production ignoring some items', async ()
|
||||
'default',
|
||||
otherProjectToggle,
|
||||
user.email,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
await eventService.storeEvent(
|
||||
new FeatureEnvironmentEvent(makeEvent(otherProjectToggle.name)),
|
||||
@ -1594,6 +1651,7 @@ test('should calculate average time to production ignoring some items', async ()
|
||||
project.id,
|
||||
nonReleaseToggle,
|
||||
user.email,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
await eventService.storeEvent(
|
||||
new FeatureEnvironmentEvent(makeEvent(nonReleaseToggle.name)),
|
||||
@ -1605,6 +1663,7 @@ test('should calculate average time to production ignoring some items', async ()
|
||||
project.id,
|
||||
previouslyDeleteToggle,
|
||||
user.email,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
await eventService.storeEvent(
|
||||
new FeatureEnvironmentEvent(makeEvent(previouslyDeleteToggle.name)),
|
||||
@ -1641,6 +1700,7 @@ test('should get correct amount of features created in current and past window',
|
||||
project.id,
|
||||
toggle,
|
||||
user.email,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
}),
|
||||
);
|
||||
@ -1678,6 +1738,7 @@ test('should get correct amount of features archived in current and past window'
|
||||
project.id,
|
||||
toggle,
|
||||
user.email,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
}),
|
||||
);
|
||||
@ -1770,6 +1831,7 @@ test('should return average time to production per toggle', async () => {
|
||||
project.id,
|
||||
toggle,
|
||||
user.email,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
}),
|
||||
);
|
||||
@ -1783,6 +1845,7 @@ test('should return average time to production per toggle', async () => {
|
||||
featureName: toggle.name,
|
||||
environment: 'default',
|
||||
createdBy: 'Fredrik',
|
||||
createdByUserId: TEST_USER_ID,
|
||||
}),
|
||||
);
|
||||
}),
|
||||
@ -1838,6 +1901,7 @@ test('should return average time to production per toggle for a specific project
|
||||
project1.id,
|
||||
toggle,
|
||||
user.email,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
}),
|
||||
);
|
||||
@ -1848,6 +1912,7 @@ test('should return average time to production per toggle for a specific project
|
||||
project2.id,
|
||||
toggle,
|
||||
user.email,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
}),
|
||||
);
|
||||
@ -1861,6 +1926,7 @@ test('should return average time to production per toggle for a specific project
|
||||
featureName: toggle.name,
|
||||
environment: 'default',
|
||||
createdBy: 'Fredrik',
|
||||
createdByUserId: TEST_USER_ID,
|
||||
}),
|
||||
);
|
||||
}),
|
||||
@ -1875,6 +1941,7 @@ test('should return average time to production per toggle for a specific project
|
||||
featureName: toggle.name,
|
||||
environment: 'default',
|
||||
createdBy: 'Fredrik',
|
||||
createdByUserId: TEST_USER_ID,
|
||||
}),
|
||||
);
|
||||
}),
|
||||
@ -1925,6 +1992,7 @@ test('should return average time to production per toggle and include archived t
|
||||
project1.id,
|
||||
toggle,
|
||||
user.email,
|
||||
TEST_USER_ID,
|
||||
);
|
||||
}),
|
||||
);
|
||||
@ -1938,6 +2006,7 @@ test('should return average time to production per toggle and include archived t
|
||||
featureName: toggle.name,
|
||||
environment: 'default',
|
||||
createdBy: 'Fredrik',
|
||||
createdByUserId: TEST_USER_ID,
|
||||
}),
|
||||
);
|
||||
}),
|
||||
|
||||
@ -13,6 +13,7 @@ import { property } from 'fast-check';
|
||||
let stores: IUnleashStores;
|
||||
let db;
|
||||
let service: SettingService;
|
||||
const TEST_USER_ID = -9999;
|
||||
|
||||
beforeAll(async () => {
|
||||
const config = createTestConfig();
|
||||
@ -30,7 +31,13 @@ afterAll(async () => {
|
||||
|
||||
test('Can create new setting', async () => {
|
||||
const someData = { some: 'blob' };
|
||||
await service.insert('some-setting', someData, 'test-user', false);
|
||||
await service.insert(
|
||||
'some-setting',
|
||||
someData,
|
||||
'test-user',
|
||||
TEST_USER_ID,
|
||||
false,
|
||||
);
|
||||
const actual = await service.get('some-setting');
|
||||
|
||||
expect(actual).toStrictEqual(someData);
|
||||
@ -44,8 +51,8 @@ test('Can create new setting', async () => {
|
||||
|
||||
test('Can delete setting', async () => {
|
||||
const someData = { some: 'blob' };
|
||||
await service.insert('some-setting', someData, 'test-user');
|
||||
await service.delete('some-setting', 'test-user');
|
||||
await service.insert('some-setting', someData, 'test-user', TEST_USER_ID);
|
||||
await service.delete('some-setting', 'test-user', TEST_USER_ID);
|
||||
|
||||
const actual = await service.get('some-setting');
|
||||
expect(actual).toBeUndefined();
|
||||
@ -59,9 +66,14 @@ test('Can delete setting', async () => {
|
||||
test('Sentitive SSO settings are redacted in event log', async () => {
|
||||
const someData = { password: 'mySecretPassword' };
|
||||
const property = 'unleash.enterprise.auth.oidc';
|
||||
await service.insert(property, someData, 'a-user-in-places');
|
||||
await service.insert(property, someData, 'a-user-in-places', TEST_USER_ID);
|
||||
|
||||
await service.insert(property, { password: 'changed' }, 'a-user-in-places');
|
||||
await service.insert(
|
||||
property,
|
||||
{ password: 'changed' },
|
||||
'a-user-in-places',
|
||||
TEST_USER_ID,
|
||||
);
|
||||
const actual = await service.get(property);
|
||||
const { eventStore } = stores;
|
||||
|
||||
@ -69,17 +81,24 @@ test('Sentitive SSO settings are redacted in event log', async () => {
|
||||
type: SETTING_UPDATED,
|
||||
});
|
||||
expect(updatedEvents[0].preData).toEqual({ hideEventDetails: true });
|
||||
await service.delete(property, 'test-user');
|
||||
await service.delete(property, 'test-user', TEST_USER_ID);
|
||||
});
|
||||
|
||||
test('Can update setting', async () => {
|
||||
const { eventStore } = stores;
|
||||
const someData = { some: 'blob' };
|
||||
await service.insert('updated-setting', someData, 'test-user', false);
|
||||
await service.insert(
|
||||
'updated-setting',
|
||||
someData,
|
||||
'test-user',
|
||||
TEST_USER_ID,
|
||||
false,
|
||||
);
|
||||
await service.insert(
|
||||
'updated-setting',
|
||||
{ ...someData, test: 'fun' },
|
||||
'test-user',
|
||||
TEST_USER_ID,
|
||||
false,
|
||||
);
|
||||
const updatedEvents = await eventStore.searchEvents({
|
||||
|
||||
@ -131,7 +131,7 @@ test('Exporting featureEnvironmentVariants should work', async () => {
|
||||
expect(
|
||||
exportedData.featureEnvironments.find(
|
||||
(fE) => fE.featureName === 'Some-feature',
|
||||
).variants,
|
||||
)!.variants,
|
||||
).toHaveLength(3);
|
||||
});
|
||||
|
||||
@ -140,6 +140,7 @@ test('Should import variants from old format and convert to new format (per envi
|
||||
data: oldFormat,
|
||||
keepExisting: false,
|
||||
dropBeforeImport: true,
|
||||
userId: -9999,
|
||||
});
|
||||
const featureEnvironments = await stores.featureEnvironmentStore.getAll();
|
||||
expect(featureEnvironments).toHaveLength(6); // There are 3 environments enabled and 2 features
|
||||
@ -154,12 +155,14 @@ test('Should import variants in new format (per environment)', async () => {
|
||||
data: oldFormat,
|
||||
keepExisting: false,
|
||||
dropBeforeImport: true,
|
||||
userId: -9999,
|
||||
});
|
||||
const exportedJson = await stateService.export({});
|
||||
await stateService.import({
|
||||
data: exportedJson,
|
||||
keepExisting: false,
|
||||
dropBeforeImport: true,
|
||||
userId: -9999,
|
||||
});
|
||||
const featureEnvironments = await stores.featureEnvironmentStore.getAll();
|
||||
expect(featureEnvironments).toHaveLength(6); // 3 environments, 2 features === 6 rows
|
||||
@ -187,6 +190,7 @@ test('Importing states with deprecated strategies should keep their deprecated s
|
||||
userName: 'strategy-importer',
|
||||
dropBeforeImport: true,
|
||||
keepExisting: false,
|
||||
userId: -9999,
|
||||
});
|
||||
const deprecatedStrategy =
|
||||
await stores.strategyStore.get('deprecatedstrat');
|
||||
@ -199,6 +203,7 @@ test('Exporting a deprecated strategy and then importing it should keep correct
|
||||
keepExisting: false,
|
||||
dropBeforeImport: true,
|
||||
userName: 'strategy importer',
|
||||
userId: -9999,
|
||||
});
|
||||
const rolloutRandom = await stores.strategyStore.get(
|
||||
'gradualRolloutRandom',
|
||||
|
||||
@ -211,6 +211,7 @@ test('should not login user if simple auth is disabled', async () => {
|
||||
simpleAuthSettingsKey,
|
||||
{ disabled: true },
|
||||
randomId(),
|
||||
-9999,
|
||||
true,
|
||||
);
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ import { IUnleashStores } from '../../../lib/types';
|
||||
let db;
|
||||
let stores: IUnleashStores;
|
||||
let eventStore: IEventStore;
|
||||
const TEST_USER_ID = -9999;
|
||||
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('event_store_serial', getLogger);
|
||||
@ -35,6 +36,7 @@ test('Should include id and createdAt when saving', async () => {
|
||||
const event1 = {
|
||||
type: APPLICATION_CREATED,
|
||||
createdBy: '127.0.0.1',
|
||||
createdByUserId: TEST_USER_ID,
|
||||
data: {
|
||||
clientIp: '127.0.0.1',
|
||||
appName: 'test1',
|
||||
@ -57,6 +59,7 @@ test('Should include empty tags array for new event', async () => {
|
||||
const event = {
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'me@mail.com',
|
||||
createdByUserId: TEST_USER_ID,
|
||||
data: {
|
||||
name: 'someName',
|
||||
enabled: true,
|
||||
@ -83,6 +86,7 @@ test('Should be able to store multiple events at once', async () => {
|
||||
jest.useFakeTimers();
|
||||
const event1 = {
|
||||
type: APPLICATION_CREATED,
|
||||
createdByUserId: TEST_USER_ID,
|
||||
createdBy: '127.0.0.1',
|
||||
data: {
|
||||
clientIp: '127.0.0.1',
|
||||
@ -91,6 +95,7 @@ test('Should be able to store multiple events at once', async () => {
|
||||
};
|
||||
const event2 = {
|
||||
type: APPLICATION_CREATED,
|
||||
createdByUserId: TEST_USER_ID,
|
||||
createdBy: '127.0.0.1',
|
||||
data: {
|
||||
clientIp: '127.0.0.1',
|
||||
@ -99,6 +104,7 @@ test('Should be able to store multiple events at once', async () => {
|
||||
};
|
||||
const event3 = {
|
||||
type: APPLICATION_CREATED,
|
||||
createdByUserId: TEST_USER_ID,
|
||||
createdBy: '127.0.0.1',
|
||||
data: {
|
||||
clientIp: '127.0.0.1',
|
||||
@ -122,6 +128,7 @@ test('Should get all stored events', async () => {
|
||||
const event = {
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'me@mail.com',
|
||||
createdByUserId: TEST_USER_ID,
|
||||
data: {
|
||||
name: 'someName',
|
||||
enabled: true,
|
||||
@ -139,6 +146,7 @@ test('Should get all stored events', async () => {
|
||||
test('Should delete stored event', async () => {
|
||||
const event = {
|
||||
type: FEATURE_CREATED,
|
||||
createdByUserId: TEST_USER_ID,
|
||||
createdBy: 'me@mail.com',
|
||||
data: {
|
||||
name: 'someName',
|
||||
@ -163,6 +171,7 @@ test('Should get stored event by id', async () => {
|
||||
const event = {
|
||||
type: FEATURE_CREATED,
|
||||
createdBy: 'me@mail.com',
|
||||
createdByUserId: TEST_USER_ID,
|
||||
data: {
|
||||
name: 'someName',
|
||||
enabled: true,
|
||||
@ -197,6 +206,8 @@ test('Should get all events of type', async () => {
|
||||
project: data.project,
|
||||
featureName: data.name,
|
||||
createdBy: 'test-user',
|
||||
createdByUserId: TEST_USER_ID,
|
||||
|
||||
data,
|
||||
})
|
||||
: new FeatureDeletedEvent({
|
||||
@ -204,6 +215,8 @@ test('Should get all events of type', async () => {
|
||||
preData: data,
|
||||
featureName: data.name,
|
||||
createdBy: 'test-user',
|
||||
createdByUserId: TEST_USER_ID,
|
||||
|
||||
tags: [],
|
||||
});
|
||||
return eventStore.store(event);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user