mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-09 00:18:00 +01:00
feat: project environment added and removed events (#5459)
This commit is contained in:
parent
9ed2c37b25
commit
2965daa195
@ -18,11 +18,13 @@ import {
|
|||||||
insertLastSeenAt,
|
insertLastSeenAt,
|
||||||
insertFeatureEnvironmentsLastSeen,
|
insertFeatureEnvironmentsLastSeen,
|
||||||
} from '../../../../test/e2e/helpers/test-helper';
|
} from '../../../../test/e2e/helpers/test-helper';
|
||||||
|
import { EventService } from '../../../services';
|
||||||
|
|
||||||
let stores: IUnleashStores;
|
let stores: IUnleashStores;
|
||||||
let db;
|
let db;
|
||||||
let service: FeatureToggleService;
|
let service: FeatureToggleService;
|
||||||
let segmentService: ISegmentService;
|
let segmentService: ISegmentService;
|
||||||
|
let eventService: EventService;
|
||||||
let environmentService: EnvironmentService;
|
let environmentService: EnvironmentService;
|
||||||
let unleashConfig;
|
let unleashConfig;
|
||||||
|
|
||||||
@ -48,6 +50,8 @@ beforeAll(async () => {
|
|||||||
segmentService = createSegmentService(db.rawDatabase, config);
|
segmentService = createSegmentService(db.rawDatabase, config);
|
||||||
|
|
||||||
service = createFeatureToggleService(db.rawDatabase, config);
|
service = createFeatureToggleService(db.rawDatabase, config);
|
||||||
|
|
||||||
|
eventService = new EventService(stores, config);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@ -253,13 +257,17 @@ test('adding and removing an environment preserves variants when variants per en
|
|||||||
);
|
);
|
||||||
|
|
||||||
//force the variantEnvironments flag off so that we can test legacy behavior
|
//force the variantEnvironments flag off so that we can test legacy behavior
|
||||||
environmentService = new EnvironmentService(stores, {
|
environmentService = new EnvironmentService(
|
||||||
|
stores,
|
||||||
|
{
|
||||||
...unleashConfig,
|
...unleashConfig,
|
||||||
flagResolver: {
|
flagResolver: {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
isEnabled: (toggleName: string) => false,
|
isEnabled: (toggleName: string) => false,
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
eventService,
|
||||||
|
);
|
||||||
|
|
||||||
await environmentService.addEnvironmentToProject(prodEnv, 'default');
|
await environmentService.addEnvironmentToProject(prodEnv, 'default');
|
||||||
await environmentService.removeEnvironmentFromProject(prodEnv, 'default');
|
await environmentService.removeEnvironmentFromProject(prodEnv, 'default');
|
||||||
|
@ -18,6 +18,8 @@ import {
|
|||||||
ProjectEnvironmentSchema,
|
ProjectEnvironmentSchema,
|
||||||
} from '../../../openapi';
|
} from '../../../openapi';
|
||||||
import { OpenApiService, ProjectService } from '../../../services';
|
import { OpenApiService, ProjectService } from '../../../services';
|
||||||
|
import { extractUsername } from '../../../util';
|
||||||
|
import { IAuthRequest } from '../../unleash-types';
|
||||||
|
|
||||||
const PREFIX = '/:projectId/environments';
|
const PREFIX = '/:projectId/environments';
|
||||||
|
|
||||||
@ -124,7 +126,7 @@ export default class EnvironmentsController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async addEnvironmentToProject(
|
async addEnvironmentToProject(
|
||||||
req: Request<
|
req: IAuthRequest<
|
||||||
Omit<IProjectEnvironmentParams, 'environment'>,
|
Omit<IProjectEnvironmentParams, 'environment'>,
|
||||||
void,
|
void,
|
||||||
ProjectEnvironmentSchema
|
ProjectEnvironmentSchema
|
||||||
@ -138,13 +140,14 @@ export default class EnvironmentsController extends Controller {
|
|||||||
await this.environmentService.addEnvironmentToProject(
|
await this.environmentService.addEnvironmentToProject(
|
||||||
environment,
|
environment,
|
||||||
projectId,
|
projectId,
|
||||||
|
extractUsername(req),
|
||||||
);
|
);
|
||||||
|
|
||||||
res.status(200).end();
|
res.status(200).end();
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeEnvironmentFromProject(
|
async removeEnvironmentFromProject(
|
||||||
req: Request<IProjectEnvironmentParams>,
|
req: IAuthRequest<IProjectEnvironmentParams>,
|
||||||
res: Response<void>,
|
res: Response<void>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { projectId, environment } = req.params;
|
const { projectId, environment } = req.params;
|
||||||
@ -152,6 +155,7 @@ export default class EnvironmentsController extends Controller {
|
|||||||
await this.environmentService.removeEnvironmentFromProject(
|
await this.environmentService.removeEnvironmentFromProject(
|
||||||
environment,
|
environment,
|
||||||
projectId,
|
projectId,
|
||||||
|
extractUsername(req),
|
||||||
);
|
);
|
||||||
|
|
||||||
res.status(200).end();
|
res.status(200).end();
|
||||||
|
@ -7,6 +7,8 @@ import {
|
|||||||
ISortOrder,
|
ISortOrder,
|
||||||
IUnleashConfig,
|
IUnleashConfig,
|
||||||
IUnleashStores,
|
IUnleashStores,
|
||||||
|
PROJECT_ENVIRONMENT_ADDED,
|
||||||
|
PROJECT_ENVIRONMENT_REMOVED,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { Logger } from '../logger';
|
import { Logger } from '../logger';
|
||||||
import { BadDataError, UNIQUE_CONSTRAINT_VIOLATION } from '../error';
|
import { BadDataError, UNIQUE_CONSTRAINT_VIOLATION } from '../error';
|
||||||
@ -17,6 +19,7 @@ import { IProjectStore } from 'lib/types/stores/project-store';
|
|||||||
import MinimumOneEnvironmentError from '../error/minimum-one-environment-error';
|
import MinimumOneEnvironmentError from '../error/minimum-one-environment-error';
|
||||||
import { IFlagResolver } from 'lib/types/experimental';
|
import { IFlagResolver } from 'lib/types/experimental';
|
||||||
import { CreateFeatureStrategySchema } from '../openapi';
|
import { CreateFeatureStrategySchema } from '../openapi';
|
||||||
|
import EventService from './event-service';
|
||||||
|
|
||||||
export default class EnvironmentService {
|
export default class EnvironmentService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
@ -29,6 +32,8 @@ export default class EnvironmentService {
|
|||||||
|
|
||||||
private featureEnvironmentStore: IFeatureEnvironmentStore;
|
private featureEnvironmentStore: IFeatureEnvironmentStore;
|
||||||
|
|
||||||
|
private eventService: EventService;
|
||||||
|
|
||||||
private flagResolver: IFlagResolver;
|
private flagResolver: IFlagResolver;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -48,12 +53,14 @@ export default class EnvironmentService {
|
|||||||
getLogger,
|
getLogger,
|
||||||
flagResolver,
|
flagResolver,
|
||||||
}: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>,
|
}: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>,
|
||||||
|
eventService: EventService,
|
||||||
) {
|
) {
|
||||||
this.logger = getLogger('services/environment-service.ts');
|
this.logger = getLogger('services/environment-service.ts');
|
||||||
this.environmentStore = environmentStore;
|
this.environmentStore = environmentStore;
|
||||||
this.featureStrategiesStore = featureStrategiesStore;
|
this.featureStrategiesStore = featureStrategiesStore;
|
||||||
this.featureEnvironmentStore = featureEnvironmentStore;
|
this.featureEnvironmentStore = featureEnvironmentStore;
|
||||||
this.projectStore = projectStore;
|
this.projectStore = projectStore;
|
||||||
|
this.eventService = eventService;
|
||||||
this.flagResolver = flagResolver;
|
this.flagResolver = flagResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,6 +99,7 @@ export default class EnvironmentService {
|
|||||||
async addEnvironmentToProject(
|
async addEnvironmentToProject(
|
||||||
environment: string,
|
environment: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
|
username = 'unknown',
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await this.featureEnvironmentStore.connectProject(
|
await this.featureEnvironmentStore.connectProject(
|
||||||
@ -102,6 +110,12 @@ export default class EnvironmentService {
|
|||||||
environment,
|
environment,
|
||||||
projectId,
|
projectId,
|
||||||
);
|
);
|
||||||
|
await this.eventService.storeEvent({
|
||||||
|
type: PROJECT_ENVIRONMENT_ADDED,
|
||||||
|
project: projectId,
|
||||||
|
environment,
|
||||||
|
createdBy: username,
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.code === UNIQUE_CONSTRAINT_VIOLATION) {
|
if (e.code === UNIQUE_CONSTRAINT_VIOLATION) {
|
||||||
throw new NameExistsError(
|
throw new NameExistsError(
|
||||||
@ -213,6 +227,7 @@ export default class EnvironmentService {
|
|||||||
async removeEnvironmentFromProject(
|
async removeEnvironmentFromProject(
|
||||||
environment: string,
|
environment: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
|
username = 'unknown',
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const projectEnvs =
|
const projectEnvs =
|
||||||
await this.projectStore.getEnvironmentsForProject(projectId);
|
await this.projectStore.getEnvironmentsForProject(projectId);
|
||||||
@ -222,6 +237,12 @@ export default class EnvironmentService {
|
|||||||
environment,
|
environment,
|
||||||
projectId,
|
projectId,
|
||||||
);
|
);
|
||||||
|
await this.eventService.storeEvent({
|
||||||
|
type: PROJECT_ENVIRONMENT_REMOVED,
|
||||||
|
project: projectId,
|
||||||
|
environment,
|
||||||
|
createdBy: username,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw new MinimumOneEnvironmentError(
|
throw new MinimumOneEnvironmentError(
|
||||||
|
@ -222,7 +222,11 @@ export const createServices = (
|
|||||||
dependentFeaturesReadModel,
|
dependentFeaturesReadModel,
|
||||||
dependentFeaturesService,
|
dependentFeaturesService,
|
||||||
);
|
);
|
||||||
const environmentService = new EnvironmentService(stores, config);
|
const environmentService = new EnvironmentService(
|
||||||
|
stores,
|
||||||
|
config,
|
||||||
|
eventService,
|
||||||
|
);
|
||||||
const featureTagService = new FeatureTagService(
|
const featureTagService = new FeatureTagService(
|
||||||
stores,
|
stores,
|
||||||
config,
|
config,
|
||||||
|
@ -108,6 +108,9 @@ export const GROUP_DELETED = 'group-deleted' as const;
|
|||||||
export const SETTING_CREATED = 'setting-created' as const;
|
export const SETTING_CREATED = 'setting-created' as const;
|
||||||
export const SETTING_UPDATED = 'setting-updated' as const;
|
export const SETTING_UPDATED = 'setting-updated' as const;
|
||||||
export const SETTING_DELETED = 'setting-deleted' as const;
|
export const SETTING_DELETED = 'setting-deleted' as const;
|
||||||
|
export const PROJECT_ENVIRONMENT_ADDED = 'project-environment-added' as const;
|
||||||
|
export const PROJECT_ENVIRONMENT_REMOVED =
|
||||||
|
'project-environment-removed' as const;
|
||||||
|
|
||||||
export const CLIENT_METRICS = 'client-metrics' as const;
|
export const CLIENT_METRICS = 'client-metrics' as const;
|
||||||
export const CLIENT_REGISTER = 'client-register' as const;
|
export const CLIENT_REGISTER = 'client-register' as const;
|
||||||
@ -292,6 +295,8 @@ export const IEventTypes = [
|
|||||||
BANNER_CREATED,
|
BANNER_CREATED,
|
||||||
BANNER_UPDATED,
|
BANNER_UPDATED,
|
||||||
BANNER_DELETED,
|
BANNER_DELETED,
|
||||||
|
PROJECT_ENVIRONMENT_ADDED,
|
||||||
|
PROJECT_ENVIRONMENT_REMOVED,
|
||||||
] as const;
|
] as const;
|
||||||
export type IEventType = (typeof IEventTypes)[number];
|
export type IEventType = (typeof IEventTypes)[number];
|
||||||
|
|
||||||
|
@ -4,16 +4,19 @@ import dbInit from '../helpers/database-init';
|
|||||||
import NotFoundError from '../../../lib/error/notfound-error';
|
import NotFoundError from '../../../lib/error/notfound-error';
|
||||||
import { IUnleashStores } from '../../../lib/types';
|
import { IUnleashStores } from '../../../lib/types';
|
||||||
import NameExistsError from '../../../lib/error/name-exists-error';
|
import NameExistsError from '../../../lib/error/name-exists-error';
|
||||||
|
import { EventService } from '../../../lib/services';
|
||||||
|
|
||||||
let stores: IUnleashStores;
|
let stores: IUnleashStores;
|
||||||
let db;
|
let db;
|
||||||
let service: EnvironmentService;
|
let service: EnvironmentService;
|
||||||
|
let eventService: EventService;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const config = createTestConfig();
|
const config = createTestConfig();
|
||||||
db = await dbInit('environment_service_serial', config.getLogger);
|
db = await dbInit('environment_service_serial', config.getLogger);
|
||||||
stores = db.stores;
|
stores = db.stores;
|
||||||
service = new EnvironmentService(stores, config);
|
eventService = new EventService(stores, config);
|
||||||
|
service = new EnvironmentService(stores, config, eventService);
|
||||||
});
|
});
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await db.destroy();
|
await db.destroy();
|
||||||
@ -50,7 +53,7 @@ test('Can connect environment to project', async () => {
|
|||||||
description: '',
|
description: '',
|
||||||
stale: false,
|
stale: false,
|
||||||
});
|
});
|
||||||
await service.addEnvironmentToProject('test-connection', 'default');
|
await service.addEnvironmentToProject('test-connection', 'default', 'user');
|
||||||
const overview = await stores.featureStrategiesStore.getFeatureOverview({
|
const overview = await stores.featureStrategiesStore.getFeatureOverview({
|
||||||
projectId: 'default',
|
projectId: 'default',
|
||||||
});
|
});
|
||||||
@ -68,6 +71,13 @@ test('Can connect environment to project', async () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
const { events } = await eventService.getEvents();
|
||||||
|
expect(events[0]).toMatchObject({
|
||||||
|
type: 'project-environment-added',
|
||||||
|
project: 'default',
|
||||||
|
environment: 'test-connection',
|
||||||
|
createdBy: 'user',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can remove environment from project', async () => {
|
test('Can remove environment from project', async () => {
|
||||||
@ -98,7 +108,11 @@ test('Can remove environment from project', async () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
await service.removeEnvironmentFromProject('removal-test', 'default');
|
await service.removeEnvironmentFromProject(
|
||||||
|
'removal-test',
|
||||||
|
'default',
|
||||||
|
'user',
|
||||||
|
);
|
||||||
overview = await stores.featureStrategiesStore.getFeatureOverview({
|
overview = await stores.featureStrategiesStore.getFeatureOverview({
|
||||||
projectId: 'default',
|
projectId: 'default',
|
||||||
});
|
});
|
||||||
@ -106,6 +120,13 @@ test('Can remove environment from project', async () => {
|
|||||||
overview.forEach((o) => {
|
overview.forEach((o) => {
|
||||||
expect(o.environments).toEqual([]);
|
expect(o.environments).toEqual([]);
|
||||||
});
|
});
|
||||||
|
const { events } = await eventService.getEvents();
|
||||||
|
expect(events[0]).toMatchObject({
|
||||||
|
type: 'project-environment-removed',
|
||||||
|
project: 'default',
|
||||||
|
environment: 'removal-test',
|
||||||
|
createdBy: 'user',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Adding same environment twice should throw a NameExistsError', async () => {
|
test('Adding same environment twice should throw a NameExistsError', async () => {
|
||||||
|
@ -64,7 +64,7 @@ beforeAll(async () => {
|
|||||||
|
|
||||||
featureToggleService = createFeatureToggleService(db.rawDatabase, config);
|
featureToggleService = createFeatureToggleService(db.rawDatabase, config);
|
||||||
|
|
||||||
environmentService = new EnvironmentService(stores, config);
|
environmentService = new EnvironmentService(stores, config, eventService);
|
||||||
projectService = createProjectService(db.rawDatabase, config);
|
projectService = createProjectService(db.rawDatabase, config);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user