Skip to content

Commit 2e96ace

Browse files
authored
feat: feature type updated audit log (#5415)
1 parent d680e50 commit 2e96ace

File tree

7 files changed

+50
-5
lines changed

7 files changed

+50
-5
lines changed

src/lib/db/feature-type-store.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class FeatureTypeStore implements IFeatureTypeStore {
4141

4242
async get(id: string): Promise<IFeatureType> {
4343
const row = await this.db(TABLE).where({ id }).first();
44-
return this.rowToFeatureType(row);
44+
return row ? this.rowToFeatureType(row) : row;
4545
}
4646

4747
async getByName(name: string): Promise<IFeatureType> {

src/lib/routes/admin-api/feature-type.ts

+1
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ When a feature toggle type's expected lifetime is changed, this will also cause
116116
const result = await this.featureTypeService.updateLifetime(
117117
req.params.id.toLowerCase(),
118118
req.body.lifetimeDays,
119+
req.user,
119120
);
120121

121122
this.openApiService.respondWithValidation(

src/lib/services/feature-type-service.ts

+18-1
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,25 @@ import {
66
IFeatureTypeStore,
77
} from '../types/stores/feature-type-store';
88
import NotFoundError from '../error/notfound-error';
9+
import EventService from './event-service';
10+
import { FEATURE_FAVORITED, FEATURE_TYPE_UPDATED, IUser } from '../types';
11+
import { extractUsernameFromUser } from '../util';
912

1013
export default class FeatureTypeService {
1114
private featureTypeStore: IFeatureTypeStore;
1215

16+
private eventService: EventService;
17+
1318
private logger: Logger;
1419

1520
constructor(
1621
{ featureTypeStore }: Pick<IUnleashStores, 'featureTypeStore'>,
1722
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
23+
eventService: EventService,
1824
) {
1925
this.featureTypeStore = featureTypeStore;
2026
this.logger = getLogger('services/feature-type-service.ts');
27+
this.eventService = eventService;
2128
}
2229

2330
async getAll(): Promise<IFeatureType[]> {
@@ -27,23 +34,33 @@ export default class FeatureTypeService {
2734
async updateLifetime(
2835
id: string,
2936
newLifetimeDays: number | null,
37+
user: IUser,
3038
): Promise<IFeatureType> {
3139
// because our OpenAPI library does type coercion, any `null` values you
3240
// pass in get converted to `0`.
3341
const translatedLifetime =
3442
newLifetimeDays === 0 ? null : newLifetimeDays;
3543

44+
const featureType = await this.featureTypeStore.get(id);
45+
3646
const result = await this.featureTypeStore.updateLifetime(
3747
id,
3848
translatedLifetime,
3949
);
4050

41-
if (!result) {
51+
if (!featureType || !result) {
4252
throw new NotFoundError(
4353
`The feature type you tried to update ("${id}") does not exist.`,
4454
);
4555
}
4656

57+
await this.eventService.storeEvent({
58+
type: FEATURE_TYPE_UPDATED,
59+
createdBy: extractUsernameFromUser(user),
60+
data: { ...featureType, lifetimeDays: translatedLifetime },
61+
preData: featureType,
62+
});
63+
4764
return result;
4865
}
4966
}

src/lib/services/index.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,11 @@ export const createServices = (
135135
privateProjectChecker,
136136
);
137137
const emailService = new EmailService(config.email, config.getLogger);
138-
const featureTypeService = new FeatureTypeService(stores, config);
138+
const featureTypeService = new FeatureTypeService(
139+
stores,
140+
config,
141+
eventService,
142+
);
139143
const resetTokenService = new ResetTokenService(stores, config);
140144
const stateService = new StateService(stores, config, eventService);
141145
const strategyService = new StrategyService(stores, config, eventService);

src/lib/types/events.ts

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export const CONTEXT_FIELD_CREATED = 'context-field-created' as const;
4747
export const CONTEXT_FIELD_UPDATED = 'context-field-updated' as const;
4848
export const CONTEXT_FIELD_DELETED = 'context-field-deleted' as const;
4949
export const PROJECT_ACCESS_ADDED = 'project-access-added' as const;
50+
export const FEATURE_TYPE_UPDATED = 'feature-type-updated' as const;
5051

5152
export const PROJECT_ACCESS_USER_ROLES_UPDATED =
5253
'project-access-user-roles-updated';
@@ -178,6 +179,7 @@ export const IEventTypes = [
178179
FEATURE_STRATEGY_UPDATE,
179180
FEATURE_STRATEGY_ADD,
180181
FEATURE_STRATEGY_REMOVE,
182+
FEATURE_TYPE_UPDATED,
181183
STRATEGY_ORDER_CHANGED,
182184
DROP_FEATURE_TAGS,
183185
FEATURE_UNTAGGED,

src/test/e2e/api/admin/feature-type.test.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import dbInit from '../../helpers/database-init';
22
import getLogger from '../../../fixtures/no-logger';
3-
import { setupAppWithCustomConfig } from '../../helpers/test-helper';
3+
import {
4+
IUnleashTest,
5+
setupAppWithCustomConfig,
6+
} from '../../helpers/test-helper';
47
import { validateSchema } from '../../../../lib/openapi/validate';
58
import { featureTypesSchema } from '../../../../lib/openapi/spec/feature-types-schema';
69

7-
let app;
10+
let app: IUnleashTest;
811
let db;
912

1013
beforeAll(async () => {
@@ -68,6 +71,11 @@ describe('updating lifetimes', () => {
6871
};
6972

7073
expect(await setLifetime(0)).toMatchObject(await setLifetime(null));
74+
75+
const { body } = await app.getRecordedEvents();
76+
expect(body.events[0]).toMatchObject({
77+
data: { id: 'release', lifetimeDays: null },
78+
});
7179
});
7280
test('the :id parameter is not case sensitive', async () => {
7381
const lifetimeDays = 45;

src/test/e2e/helpers/test-helper.ts

+13
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ export interface IUnleashHttpAPI {
9797
tag: { type: string; value: string },
9898
expectedResponseCode?: number,
9999
): supertest.Test;
100+
101+
getRecordedEvents(): supertest.Test;
100102
}
101103

102104
function httpApis(
@@ -258,6 +260,17 @@ function httpApis(
258260
.set('Content-Type', 'application/json')
259261
.expect(expectedResponseCode);
260262
},
263+
264+
getRecordedEvents(
265+
project: string | null = null,
266+
expectedResponseCode: number = 200,
267+
): supertest.Test {
268+
return request
269+
.post('/api/admin/events/search')
270+
.send({ project, query: '', limit: 50, offset: 0 })
271+
.set('Content-Type', 'application/json')
272+
.expect(expectedResponseCode);
273+
},
261274
};
262275
}
263276

0 commit comments

Comments
 (0)