mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-05 17:53:12 +02:00
feat: add project and environment columns to events
This commit is contained in:
parent
37d6c4886a
commit
68d4ac0252
@ -12,6 +12,8 @@ const EVENT_COLUMNS = [
|
|||||||
'created_at',
|
'created_at',
|
||||||
'data',
|
'data',
|
||||||
'tags',
|
'tags',
|
||||||
|
'project',
|
||||||
|
'environment',
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface IEventTable {
|
export interface IEventTable {
|
||||||
@ -20,6 +22,8 @@ export interface IEventTable {
|
|||||||
created_by: string;
|
created_by: string;
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
data: any;
|
data: any;
|
||||||
|
project?: string;
|
||||||
|
environment?: string;
|
||||||
tags: [];
|
tags: [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,6 +130,19 @@ class EventStore extends EventEmitter implements IEventStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getEventsFilterByProject(project: string): Promise<IEvent[]> {
|
||||||
|
try {
|
||||||
|
const rows = await this.db
|
||||||
|
.select(EVENT_COLUMNS)
|
||||||
|
.from(TABLE)
|
||||||
|
.where({ project })
|
||||||
|
.orderBy('created_at', 'desc');
|
||||||
|
return rows.map(this.rowToEvent);
|
||||||
|
} catch (err) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rowToEvent(row: IEventTable): IEvent {
|
rowToEvent(row: IEventTable): IEvent {
|
||||||
return {
|
return {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
@ -134,6 +151,8 @@ class EventStore extends EventEmitter implements IEventStore {
|
|||||||
createdAt: row.created_at,
|
createdAt: row.created_at,
|
||||||
data: row.data,
|
data: row.data,
|
||||||
tags: row.tags || [],
|
tags: row.tags || [],
|
||||||
|
project: row.project,
|
||||||
|
environment: row.environment,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,6 +162,8 @@ class EventStore extends EventEmitter implements IEventStore {
|
|||||||
created_by: e.createdBy,
|
created_by: e.createdBy,
|
||||||
data: e.data,
|
data: e.data,
|
||||||
tags: JSON.stringify(e.tags),
|
tags: JSON.stringify(e.tags),
|
||||||
|
project: e.project,
|
||||||
|
environment: e.environment,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,14 @@ export default class EventController extends Controller {
|
|||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
async getEvents(req, res): Promise<void> {
|
async getEvents(req, res): Promise<void> {
|
||||||
const events = await this.eventService.getEvents();
|
let events;
|
||||||
|
if (req.query?.project) {
|
||||||
|
events = await this.eventService.getEventsForProject(
|
||||||
|
req.query.project,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
events = await this.eventService.getEvents();
|
||||||
|
}
|
||||||
eventDiffer.addDiffs(events);
|
eventDiffer.addDiffs(events);
|
||||||
res.json({ version, events });
|
res.json({ version, events });
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,10 @@ export default class EventService {
|
|||||||
(e: IEvent) => e.type !== FEATURE_METADATA_UPDATED,
|
(e: IEvent) => e.type !== FEATURE_METADATA_UPDATED,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getEventsForProject(project: string): Promise<IEvent[]> {
|
||||||
|
return this.eventStore.getEventsFilterByProject(project);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = EventService;
|
module.exports = EventService;
|
||||||
|
@ -309,6 +309,7 @@ class FeatureToggleServiceV2 {
|
|||||||
await this.eventStore.store({
|
await this.eventStore.store({
|
||||||
type: FEATURE_CREATED,
|
type: FEATURE_CREATED,
|
||||||
createdBy: userName,
|
createdBy: userName,
|
||||||
|
project: projectId,
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -341,6 +342,7 @@ class FeatureToggleServiceV2 {
|
|||||||
type: FEATURE_METADATA_UPDATED,
|
type: FEATURE_METADATA_UPDATED,
|
||||||
createdBy: userName,
|
createdBy: userName,
|
||||||
data: featureToggle,
|
data: featureToggle,
|
||||||
|
project: projectId,
|
||||||
tags,
|
tags,
|
||||||
});
|
});
|
||||||
return featureToggle;
|
return featureToggle;
|
||||||
@ -455,12 +457,13 @@ class FeatureToggleServiceV2 {
|
|||||||
createdBy: userName,
|
createdBy: userName,
|
||||||
data,
|
data,
|
||||||
tags,
|
tags,
|
||||||
|
project: feature.project,
|
||||||
});
|
});
|
||||||
return feature;
|
return feature;
|
||||||
}
|
}
|
||||||
|
|
||||||
async archiveToggle(name: string, userName: string): Promise<void> {
|
async archiveToggle(name: string, userName: string): Promise<void> {
|
||||||
await this.featureToggleStore.get(name);
|
const feature = await this.featureToggleStore.get(name);
|
||||||
await this.featureToggleStore.archive(name);
|
await this.featureToggleStore.archive(name);
|
||||||
const tags =
|
const tags =
|
||||||
(await this.featureTagStore.getAllTagsForFeature(name)) || [];
|
(await this.featureTagStore.getAllTagsForFeature(name)) || [];
|
||||||
@ -468,6 +471,7 @@ class FeatureToggleServiceV2 {
|
|||||||
type: FEATURE_ARCHIVED,
|
type: FEATURE_ARCHIVED,
|
||||||
createdBy: userName,
|
createdBy: userName,
|
||||||
data: { name },
|
data: { name },
|
||||||
|
project: feature.project,
|
||||||
tags,
|
tags,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -514,6 +518,7 @@ class FeatureToggleServiceV2 {
|
|||||||
createdBy: userName,
|
createdBy: userName,
|
||||||
data,
|
data,
|
||||||
tags,
|
tags,
|
||||||
|
project: projectId,
|
||||||
});
|
});
|
||||||
return feature;
|
return feature;
|
||||||
}
|
}
|
||||||
@ -583,6 +588,7 @@ class FeatureToggleServiceV2 {
|
|||||||
type: event || FEATURE_UPDATED,
|
type: event || FEATURE_UPDATED,
|
||||||
createdBy: userName,
|
createdBy: userName,
|
||||||
data,
|
data,
|
||||||
|
project: data.project,
|
||||||
tags,
|
tags,
|
||||||
});
|
});
|
||||||
return feature;
|
return feature;
|
||||||
@ -612,6 +618,7 @@ class FeatureToggleServiceV2 {
|
|||||||
type: FEATURE_REVIVED,
|
type: FEATURE_REVIVED,
|
||||||
createdBy: userName,
|
createdBy: userName,
|
||||||
data,
|
data,
|
||||||
|
project: data.project,
|
||||||
tags,
|
tags,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -131,6 +131,7 @@ export default class ProjectService {
|
|||||||
type: PROJECT_CREATED,
|
type: PROJECT_CREATED,
|
||||||
createdBy: getCreatedBy(user),
|
createdBy: getCreatedBy(user),
|
||||||
data,
|
data,
|
||||||
|
project: newProject.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
@ -146,6 +147,7 @@ export default class ProjectService {
|
|||||||
type: PROJECT_UPDATED,
|
type: PROJECT_UPDATED,
|
||||||
createdBy: getCreatedBy(user),
|
createdBy: getCreatedBy(user),
|
||||||
data: project,
|
data: project,
|
||||||
|
project: project.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,10 +213,11 @@ export default class ProjectService {
|
|||||||
await this.eventStore.store({
|
await this.eventStore.store({
|
||||||
type: PROJECT_DELETED,
|
type: PROJECT_DELETED,
|
||||||
createdBy: getCreatedBy(user),
|
createdBy: getCreatedBy(user),
|
||||||
|
project: id,
|
||||||
data: { id },
|
data: { id },
|
||||||
});
|
});
|
||||||
|
|
||||||
this.accessService.removeDefaultProjectRoles(user, id);
|
await this.accessService.removeDefaultProjectRoles(user, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async validateId(id: string): Promise<boolean> {
|
async validateId(id: string): Promise<boolean> {
|
||||||
|
@ -197,6 +197,8 @@ export interface IAddonConfig {
|
|||||||
export interface ICreateEvent {
|
export interface ICreateEvent {
|
||||||
type: string;
|
type: string;
|
||||||
createdBy: string;
|
createdBy: string;
|
||||||
|
project?: string;
|
||||||
|
environment?: string;
|
||||||
data?: any;
|
data?: any;
|
||||||
tags?: ITag[];
|
tags?: ITag[];
|
||||||
}
|
}
|
||||||
|
@ -7,4 +7,5 @@ export interface IEventStore extends Store<IEvent, number>, EventEmitter {
|
|||||||
batchStore(events: ICreateEvent[]): Promise<void>;
|
batchStore(events: ICreateEvent[]): Promise<void>;
|
||||||
getEvents(): Promise<IEvent[]>;
|
getEvents(): Promise<IEvent[]>;
|
||||||
getEventsFilterByType(name: string): Promise<IEvent[]>;
|
getEventsFilterByType(name: string): Promise<IEvent[]>;
|
||||||
|
getEventsFilterByProject(project: string): Promise<IEvent[]>;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
exports.up = function (db, cb) {
|
||||||
|
db.runSql(
|
||||||
|
`
|
||||||
|
ALTER TABLE events
|
||||||
|
ADD COLUMN project TEXT;
|
||||||
|
ALTER TABLE events
|
||||||
|
ADD COLUMN environment TEXT;
|
||||||
|
CREATE INDEX events_project_idx ON events(project);
|
||||||
|
CREATE INDEX events_environment_idx ON events(environment);
|
||||||
|
`,
|
||||||
|
cb,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (db, cb) {
|
||||||
|
db.runSql(
|
||||||
|
`
|
||||||
|
DROP INDEX events_environment_idx;
|
||||||
|
DROP INDEX events_project_idx;
|
||||||
|
ALTER TABLE events
|
||||||
|
DROP COLUMN environment;
|
||||||
|
ALTER TABLE events
|
||||||
|
DROP COLUMN project;
|
||||||
|
`,
|
||||||
|
cb,
|
||||||
|
);
|
||||||
|
};
|
@ -1,13 +1,17 @@
|
|||||||
import { setupApp } from '../../helpers/test-helper';
|
import { IUnleashTest, setupApp } from '../../helpers/test-helper';
|
||||||
import dbInit from '../../helpers/database-init';
|
import dbInit, { ITestDb } from '../../helpers/database-init';
|
||||||
import getLogger from '../../../fixtures/no-logger';
|
import getLogger from '../../../fixtures/no-logger';
|
||||||
|
import { FEATURE_CREATED } from '../../../../lib/types/events';
|
||||||
|
import { IEventStore } from '../../../../lib/types/stores/event-store';
|
||||||
|
|
||||||
let app;
|
let app: IUnleashTest;
|
||||||
let db;
|
let db: ITestDb;
|
||||||
|
let eventStore: IEventStore;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
db = await dbInit('event_api_serial', getLogger);
|
db = await dbInit('event_api_serial', getLogger);
|
||||||
app = await setupApp(db.stores);
|
app = await setupApp(db.stores);
|
||||||
|
eventStore = db.stores.eventStore;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@ -30,3 +34,29 @@ test('returns events given a name', async () => {
|
|||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Can filter by project', async () => {
|
||||||
|
await eventStore.store({
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
project: 'something-else',
|
||||||
|
data: { id: 'some-other-feature' },
|
||||||
|
tags: [],
|
||||||
|
createdBy: 'test-user',
|
||||||
|
environment: 'test',
|
||||||
|
});
|
||||||
|
await eventStore.store({
|
||||||
|
type: FEATURE_CREATED,
|
||||||
|
project: 'default',
|
||||||
|
data: { id: 'feature' },
|
||||||
|
tags: [],
|
||||||
|
createdBy: 'test-user',
|
||||||
|
environment: 'test',
|
||||||
|
});
|
||||||
|
await app.request
|
||||||
|
.get('/api/admin/events?project=default')
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.events).toHaveLength(1);
|
||||||
|
expect(res.body.events[0].data.id).toEqual('feature');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
4
src/test/fixtures/fake-event-store.ts
vendored
4
src/test/fixtures/fake-event-store.ts
vendored
@ -57,6 +57,10 @@ class FakeEventStore extends EventEmitter implements IEventStore {
|
|||||||
async getEventsFilterByType(type: string): Promise<IEvent[]> {
|
async getEventsFilterByType(type: string): Promise<IEvent[]> {
|
||||||
return this.events.filter((e) => e.type === type);
|
return this.events.filter((e) => e.type === type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getEventsFilterByProject(project: string): Promise<IEvent[]> {
|
||||||
|
return this.events.filter((e) => e.project === project);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = FakeEventStore;
|
module.exports = FakeEventStore;
|
||||||
|
Loading…
Reference in New Issue
Block a user