From 2e96ace14ec8232dc043c0e2d571af6836def036 Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Fri, 24 Nov 2023 14:24:31 +0100 Subject: [PATCH] feat: feature type updated audit log (#5415) --- src/lib/db/feature-type-store.ts | 2 +- src/lib/routes/admin-api/feature-type.ts | 1 + src/lib/services/feature-type-service.ts | 19 ++++++++++++++++++- src/lib/services/index.ts | 6 +++++- src/lib/types/events.ts | 2 ++ src/test/e2e/api/admin/feature-type.test.ts | 12 ++++++++++-- src/test/e2e/helpers/test-helper.ts | 13 +++++++++++++ 7 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/lib/db/feature-type-store.ts b/src/lib/db/feature-type-store.ts index 52d925da82..0d02174e4b 100644 --- a/src/lib/db/feature-type-store.ts +++ b/src/lib/db/feature-type-store.ts @@ -41,7 +41,7 @@ class FeatureTypeStore implements IFeatureTypeStore { async get(id: string): Promise { const row = await this.db(TABLE).where({ id }).first(); - return this.rowToFeatureType(row); + return row ? this.rowToFeatureType(row) : row; } async getByName(name: string): Promise { diff --git a/src/lib/routes/admin-api/feature-type.ts b/src/lib/routes/admin-api/feature-type.ts index 2edbb27c28..ef573df0de 100644 --- a/src/lib/routes/admin-api/feature-type.ts +++ b/src/lib/routes/admin-api/feature-type.ts @@ -116,6 +116,7 @@ When a feature toggle type's expected lifetime is changed, this will also cause const result = await this.featureTypeService.updateLifetime( req.params.id.toLowerCase(), req.body.lifetimeDays, + req.user, ); this.openApiService.respondWithValidation( diff --git a/src/lib/services/feature-type-service.ts b/src/lib/services/feature-type-service.ts index c31d222514..1827e28a45 100644 --- a/src/lib/services/feature-type-service.ts +++ b/src/lib/services/feature-type-service.ts @@ -6,18 +6,25 @@ import { IFeatureTypeStore, } from '../types/stores/feature-type-store'; import NotFoundError from '../error/notfound-error'; +import EventService from './event-service'; +import { FEATURE_FAVORITED, FEATURE_TYPE_UPDATED, IUser } from '../types'; +import { extractUsernameFromUser } from '../util'; export default class FeatureTypeService { private featureTypeStore: IFeatureTypeStore; + private eventService: EventService; + private logger: Logger; constructor( { featureTypeStore }: Pick, { getLogger }: Pick, + eventService: EventService, ) { this.featureTypeStore = featureTypeStore; this.logger = getLogger('services/feature-type-service.ts'); + this.eventService = eventService; } async getAll(): Promise { @@ -27,23 +34,33 @@ export default class FeatureTypeService { async updateLifetime( id: string, newLifetimeDays: number | null, + user: IUser, ): Promise { // because our OpenAPI library does type coercion, any `null` values you // pass in get converted to `0`. const translatedLifetime = newLifetimeDays === 0 ? null : newLifetimeDays; + const featureType = await this.featureTypeStore.get(id); + const result = await this.featureTypeStore.updateLifetime( id, translatedLifetime, ); - if (!result) { + if (!featureType || !result) { throw new NotFoundError( `The feature type you tried to update ("${id}") does not exist.`, ); } + await this.eventService.storeEvent({ + type: FEATURE_TYPE_UPDATED, + createdBy: extractUsernameFromUser(user), + data: { ...featureType, lifetimeDays: translatedLifetime }, + preData: featureType, + }); + return result; } } diff --git a/src/lib/services/index.ts b/src/lib/services/index.ts index cae929ba1f..02634c7656 100644 --- a/src/lib/services/index.ts +++ b/src/lib/services/index.ts @@ -135,7 +135,11 @@ export const createServices = ( privateProjectChecker, ); const emailService = new EmailService(config.email, config.getLogger); - const featureTypeService = new FeatureTypeService(stores, config); + const featureTypeService = new FeatureTypeService( + stores, + config, + eventService, + ); const resetTokenService = new ResetTokenService(stores, config); const stateService = new StateService(stores, config, eventService); const strategyService = new StrategyService(stores, config, eventService); diff --git a/src/lib/types/events.ts b/src/lib/types/events.ts index f55bfc5bdc..7d89bfba34 100644 --- a/src/lib/types/events.ts +++ b/src/lib/types/events.ts @@ -47,6 +47,7 @@ export const CONTEXT_FIELD_CREATED = 'context-field-created' as const; export const CONTEXT_FIELD_UPDATED = 'context-field-updated' as const; export const CONTEXT_FIELD_DELETED = 'context-field-deleted' as const; export const PROJECT_ACCESS_ADDED = 'project-access-added' as const; +export const FEATURE_TYPE_UPDATED = 'feature-type-updated' as const; export const PROJECT_ACCESS_USER_ROLES_UPDATED = 'project-access-user-roles-updated'; @@ -178,6 +179,7 @@ export const IEventTypes = [ FEATURE_STRATEGY_UPDATE, FEATURE_STRATEGY_ADD, FEATURE_STRATEGY_REMOVE, + FEATURE_TYPE_UPDATED, STRATEGY_ORDER_CHANGED, DROP_FEATURE_TAGS, FEATURE_UNTAGGED, diff --git a/src/test/e2e/api/admin/feature-type.test.ts b/src/test/e2e/api/admin/feature-type.test.ts index 8c450f0018..4b61e8d45c 100644 --- a/src/test/e2e/api/admin/feature-type.test.ts +++ b/src/test/e2e/api/admin/feature-type.test.ts @@ -1,10 +1,13 @@ import dbInit from '../../helpers/database-init'; import getLogger from '../../../fixtures/no-logger'; -import { setupAppWithCustomConfig } from '../../helpers/test-helper'; +import { + IUnleashTest, + setupAppWithCustomConfig, +} from '../../helpers/test-helper'; import { validateSchema } from '../../../../lib/openapi/validate'; import { featureTypesSchema } from '../../../../lib/openapi/spec/feature-types-schema'; -let app; +let app: IUnleashTest; let db; beforeAll(async () => { @@ -68,6 +71,11 @@ describe('updating lifetimes', () => { }; expect(await setLifetime(0)).toMatchObject(await setLifetime(null)); + + const { body } = await app.getRecordedEvents(); + expect(body.events[0]).toMatchObject({ + data: { id: 'release', lifetimeDays: null }, + }); }); test('the :id parameter is not case sensitive', async () => { const lifetimeDays = 45; diff --git a/src/test/e2e/helpers/test-helper.ts b/src/test/e2e/helpers/test-helper.ts index e1056857da..25234eb541 100644 --- a/src/test/e2e/helpers/test-helper.ts +++ b/src/test/e2e/helpers/test-helper.ts @@ -97,6 +97,8 @@ export interface IUnleashHttpAPI { tag: { type: string; value: string }, expectedResponseCode?: number, ): supertest.Test; + + getRecordedEvents(): supertest.Test; } function httpApis( @@ -258,6 +260,17 @@ function httpApis( .set('Content-Type', 'application/json') .expect(expectedResponseCode); }, + + getRecordedEvents( + project: string | null = null, + expectedResponseCode: number = 200, + ): supertest.Test { + return request + .post('/api/admin/events/search') + .send({ project, query: '', limit: 50, offset: 0 }) + .set('Content-Type', 'application/json') + .expect(expectedResponseCode); + }, }; }