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:
parent
d680e50055
commit
2e96ace14e
@ -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> {
|
||||||
|
@ -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(
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user