1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-28 00:06:53 +01:00

feat: feature type updated audit log (#5415)

This commit is contained in:
Mateusz Kwasniewski 2023-11-24 14:24:31 +01:00 committed by GitHub
parent d680e50055
commit 2e96ace14e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 50 additions and 5 deletions

View File

@ -41,7 +41,7 @@ class FeatureTypeStore implements IFeatureTypeStore {
async get(id: string): Promise<IFeatureType> { async get(id: string): Promise<IFeatureType> {
const row = await this.db(TABLE).where({ id }).first(); const row = await this.db(TABLE).where({ id }).first();
return this.rowToFeatureType(row); return row ? this.rowToFeatureType(row) : row;
} }
async getByName(name: string): Promise<IFeatureType> { async getByName(name: string): Promise<IFeatureType> {

View File

@ -116,6 +116,7 @@ When a feature toggle type's expected lifetime is changed, this will also cause
const result = await this.featureTypeService.updateLifetime( const result = await this.featureTypeService.updateLifetime(
req.params.id.toLowerCase(), req.params.id.toLowerCase(),
req.body.lifetimeDays, req.body.lifetimeDays,
req.user,
); );
this.openApiService.respondWithValidation( this.openApiService.respondWithValidation(

View File

@ -6,18 +6,25 @@ import {
IFeatureTypeStore, IFeatureTypeStore,
} from '../types/stores/feature-type-store'; } from '../types/stores/feature-type-store';
import NotFoundError from '../error/notfound-error'; 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 { export default class FeatureTypeService {
private featureTypeStore: IFeatureTypeStore; private featureTypeStore: IFeatureTypeStore;
private eventService: EventService;
private logger: Logger; private logger: Logger;
constructor( constructor(
{ featureTypeStore }: Pick<IUnleashStores, 'featureTypeStore'>, { featureTypeStore }: Pick<IUnleashStores, 'featureTypeStore'>,
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>, { getLogger }: Pick<IUnleashConfig, 'getLogger'>,
eventService: EventService,
) { ) {
this.featureTypeStore = featureTypeStore; this.featureTypeStore = featureTypeStore;
this.logger = getLogger('services/feature-type-service.ts'); this.logger = getLogger('services/feature-type-service.ts');
this.eventService = eventService;
} }
async getAll(): Promise<IFeatureType[]> { async getAll(): Promise<IFeatureType[]> {
@ -27,23 +34,33 @@ export default class FeatureTypeService {
async updateLifetime( async updateLifetime(
id: string, id: string,
newLifetimeDays: number | null, newLifetimeDays: number | null,
user: IUser,
): Promise<IFeatureType> { ): Promise<IFeatureType> {
// because our OpenAPI library does type coercion, any `null` values you // because our OpenAPI library does type coercion, any `null` values you
// pass in get converted to `0`. // pass in get converted to `0`.
const translatedLifetime = const translatedLifetime =
newLifetimeDays === 0 ? null : newLifetimeDays; newLifetimeDays === 0 ? null : newLifetimeDays;
const featureType = await this.featureTypeStore.get(id);
const result = await this.featureTypeStore.updateLifetime( const result = await this.featureTypeStore.updateLifetime(
id, id,
translatedLifetime, translatedLifetime,
); );
if (!result) { if (!featureType || !result) {
throw new NotFoundError( throw new NotFoundError(
`The feature type you tried to update ("${id}") does not exist.`, `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; return result;
} }
} }

View File

@ -135,7 +135,11 @@ export const createServices = (
privateProjectChecker, privateProjectChecker,
); );
const emailService = new EmailService(config.email, config.getLogger); 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 resetTokenService = new ResetTokenService(stores, config);
const stateService = new StateService(stores, config, eventService); const stateService = new StateService(stores, config, eventService);
const strategyService = new StrategyService(stores, config, eventService); const strategyService = new StrategyService(stores, config, eventService);

View File

@ -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_UPDATED = 'context-field-updated' as const;
export const CONTEXT_FIELD_DELETED = 'context-field-deleted' as const; export const CONTEXT_FIELD_DELETED = 'context-field-deleted' as const;
export const PROJECT_ACCESS_ADDED = 'project-access-added' 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 = export const PROJECT_ACCESS_USER_ROLES_UPDATED =
'project-access-user-roles-updated'; 'project-access-user-roles-updated';
@ -178,6 +179,7 @@ export const IEventTypes = [
FEATURE_STRATEGY_UPDATE, FEATURE_STRATEGY_UPDATE,
FEATURE_STRATEGY_ADD, FEATURE_STRATEGY_ADD,
FEATURE_STRATEGY_REMOVE, FEATURE_STRATEGY_REMOVE,
FEATURE_TYPE_UPDATED,
STRATEGY_ORDER_CHANGED, STRATEGY_ORDER_CHANGED,
DROP_FEATURE_TAGS, DROP_FEATURE_TAGS,
FEATURE_UNTAGGED, FEATURE_UNTAGGED,

View File

@ -1,10 +1,13 @@
import dbInit from '../../helpers/database-init'; import dbInit from '../../helpers/database-init';
import getLogger from '../../../fixtures/no-logger'; 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 { validateSchema } from '../../../../lib/openapi/validate';
import { featureTypesSchema } from '../../../../lib/openapi/spec/feature-types-schema'; import { featureTypesSchema } from '../../../../lib/openapi/spec/feature-types-schema';
let app; let app: IUnleashTest;
let db; let db;
beforeAll(async () => { beforeAll(async () => {
@ -68,6 +71,11 @@ describe('updating lifetimes', () => {
}; };
expect(await setLifetime(0)).toMatchObject(await setLifetime(null)); 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 () => { test('the :id parameter is not case sensitive', async () => {
const lifetimeDays = 45; const lifetimeDays = 45;

View File

@ -97,6 +97,8 @@ export interface IUnleashHttpAPI {
tag: { type: string; value: string }, tag: { type: string; value: string },
expectedResponseCode?: number, expectedResponseCode?: number,
): supertest.Test; ): supertest.Test;
getRecordedEvents(): supertest.Test;
} }
function httpApis( function httpApis(
@ -258,6 +260,17 @@ function httpApis(
.set('Content-Type', 'application/json') .set('Content-Type', 'application/json')
.expect(expectedResponseCode); .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);
},
}; };
} }