1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-04 00:18:01 +01:00

feat: project environment added and removed events (#5459)

This commit is contained in:
Mateusz Kwasniewski 2023-11-28 12:58:30 +01:00 committed by GitHub
parent 9ed2c37b25
commit 2965daa195
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 76 additions and 13 deletions

View File

@ -18,11 +18,13 @@ import {
insertLastSeenAt,
insertFeatureEnvironmentsLastSeen,
} from '../../../../test/e2e/helpers/test-helper';
import { EventService } from '../../../services';
let stores: IUnleashStores;
let db;
let service: FeatureToggleService;
let segmentService: ISegmentService;
let eventService: EventService;
let environmentService: EnvironmentService;
let unleashConfig;
@ -48,6 +50,8 @@ beforeAll(async () => {
segmentService = createSegmentService(db.rawDatabase, config);
service = createFeatureToggleService(db.rawDatabase, config);
eventService = new EventService(stores, config);
});
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
environmentService = new EnvironmentService(stores, {
...unleashConfig,
flagResolver: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
isEnabled: (toggleName: string) => false,
environmentService = new EnvironmentService(
stores,
{
...unleashConfig,
flagResolver: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
isEnabled: (toggleName: string) => false,
},
},
});
eventService,
);
await environmentService.addEnvironmentToProject(prodEnv, 'default');
await environmentService.removeEnvironmentFromProject(prodEnv, 'default');

View File

@ -18,6 +18,8 @@ import {
ProjectEnvironmentSchema,
} from '../../../openapi';
import { OpenApiService, ProjectService } from '../../../services';
import { extractUsername } from '../../../util';
import { IAuthRequest } from '../../unleash-types';
const PREFIX = '/:projectId/environments';
@ -124,7 +126,7 @@ export default class EnvironmentsController extends Controller {
}
async addEnvironmentToProject(
req: Request<
req: IAuthRequest<
Omit<IProjectEnvironmentParams, 'environment'>,
void,
ProjectEnvironmentSchema
@ -138,13 +140,14 @@ export default class EnvironmentsController extends Controller {
await this.environmentService.addEnvironmentToProject(
environment,
projectId,
extractUsername(req),
);
res.status(200).end();
}
async removeEnvironmentFromProject(
req: Request<IProjectEnvironmentParams>,
req: IAuthRequest<IProjectEnvironmentParams>,
res: Response<void>,
): Promise<void> {
const { projectId, environment } = req.params;
@ -152,6 +155,7 @@ export default class EnvironmentsController extends Controller {
await this.environmentService.removeEnvironmentFromProject(
environment,
projectId,
extractUsername(req),
);
res.status(200).end();

View File

@ -7,6 +7,8 @@ import {
ISortOrder,
IUnleashConfig,
IUnleashStores,
PROJECT_ENVIRONMENT_ADDED,
PROJECT_ENVIRONMENT_REMOVED,
} from '../types';
import { Logger } from '../logger';
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 { IFlagResolver } from 'lib/types/experimental';
import { CreateFeatureStrategySchema } from '../openapi';
import EventService from './event-service';
export default class EnvironmentService {
private logger: Logger;
@ -29,6 +32,8 @@ export default class EnvironmentService {
private featureEnvironmentStore: IFeatureEnvironmentStore;
private eventService: EventService;
private flagResolver: IFlagResolver;
constructor(
@ -48,12 +53,14 @@ export default class EnvironmentService {
getLogger,
flagResolver,
}: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>,
eventService: EventService,
) {
this.logger = getLogger('services/environment-service.ts');
this.environmentStore = environmentStore;
this.featureStrategiesStore = featureStrategiesStore;
this.featureEnvironmentStore = featureEnvironmentStore;
this.projectStore = projectStore;
this.eventService = eventService;
this.flagResolver = flagResolver;
}
@ -92,6 +99,7 @@ export default class EnvironmentService {
async addEnvironmentToProject(
environment: string,
projectId: string,
username = 'unknown',
): Promise<void> {
try {
await this.featureEnvironmentStore.connectProject(
@ -102,6 +110,12 @@ export default class EnvironmentService {
environment,
projectId,
);
await this.eventService.storeEvent({
type: PROJECT_ENVIRONMENT_ADDED,
project: projectId,
environment,
createdBy: username,
});
} catch (e) {
if (e.code === UNIQUE_CONSTRAINT_VIOLATION) {
throw new NameExistsError(
@ -213,6 +227,7 @@ export default class EnvironmentService {
async removeEnvironmentFromProject(
environment: string,
projectId: string,
username = 'unknown',
): Promise<void> {
const projectEnvs =
await this.projectStore.getEnvironmentsForProject(projectId);
@ -222,6 +237,12 @@ export default class EnvironmentService {
environment,
projectId,
);
await this.eventService.storeEvent({
type: PROJECT_ENVIRONMENT_REMOVED,
project: projectId,
environment,
createdBy: username,
});
return;
}
throw new MinimumOneEnvironmentError(

View File

@ -222,7 +222,11 @@ export const createServices = (
dependentFeaturesReadModel,
dependentFeaturesService,
);
const environmentService = new EnvironmentService(stores, config);
const environmentService = new EnvironmentService(
stores,
config,
eventService,
);
const featureTagService = new FeatureTagService(
stores,
config,

View File

@ -108,6 +108,9 @@ export const GROUP_DELETED = 'group-deleted' as const;
export const SETTING_CREATED = 'setting-created' as const;
export const SETTING_UPDATED = 'setting-updated' 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_REGISTER = 'client-register' as const;
@ -292,6 +295,8 @@ export const IEventTypes = [
BANNER_CREATED,
BANNER_UPDATED,
BANNER_DELETED,
PROJECT_ENVIRONMENT_ADDED,
PROJECT_ENVIRONMENT_REMOVED,
] as const;
export type IEventType = (typeof IEventTypes)[number];

View File

@ -4,16 +4,19 @@ import dbInit from '../helpers/database-init';
import NotFoundError from '../../../lib/error/notfound-error';
import { IUnleashStores } from '../../../lib/types';
import NameExistsError from '../../../lib/error/name-exists-error';
import { EventService } from '../../../lib/services';
let stores: IUnleashStores;
let db;
let service: EnvironmentService;
let eventService: EventService;
beforeAll(async () => {
const config = createTestConfig();
db = await dbInit('environment_service_serial', config.getLogger);
stores = db.stores;
service = new EnvironmentService(stores, config);
eventService = new EventService(stores, config);
service = new EnvironmentService(stores, config, eventService);
});
afterAll(async () => {
await db.destroy();
@ -50,7 +53,7 @@ test('Can connect environment to project', async () => {
description: '',
stale: false,
});
await service.addEnvironmentToProject('test-connection', 'default');
await service.addEnvironmentToProject('test-connection', 'default', 'user');
const overview = await stores.featureStrategiesStore.getFeatureOverview({
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 () => {
@ -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({
projectId: 'default',
});
@ -106,6 +120,13 @@ test('Can remove environment from project', async () => {
overview.forEach((o) => {
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 () => {

View File

@ -64,7 +64,7 @@ beforeAll(async () => {
featureToggleService = createFeatureToggleService(db.rawDatabase, config);
environmentService = new EnvironmentService(stores, config);
environmentService = new EnvironmentService(stores, config, eventService);
projectService = createProjectService(db.rawDatabase, config);
});