mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-14 00:19:16 +01:00
parent
467eb57fb8
commit
f4d857285b
@ -79,6 +79,7 @@ exports[`should create default config 1`] = `
|
||||
"messageBanner": false,
|
||||
"networkView": false,
|
||||
"newProjectOverview": false,
|
||||
"projectStatusApi": false,
|
||||
"proxyReturnAllToggles": false,
|
||||
"responseTimeWithAppName": false,
|
||||
"serviceAccounts": false,
|
||||
@ -99,6 +100,7 @@ exports[`should create default config 1`] = `
|
||||
"messageBanner": false,
|
||||
"networkView": false,
|
||||
"newProjectOverview": false,
|
||||
"projectStatusApi": false,
|
||||
"proxyReturnAllToggles": false,
|
||||
"responseTimeWithAppName": false,
|
||||
"serviceAccounts": false,
|
||||
|
@ -183,6 +183,7 @@ export default class EnvironmentStore implements IEnvironmentStore {
|
||||
|
||||
async getProjectEnvironments(
|
||||
projectId: string,
|
||||
query?: Object,
|
||||
): Promise<IProjectEnvironment[]> {
|
||||
let qB = this.db<IEnvironmentsWithProjectCountsTable>(TABLE)
|
||||
.select(
|
||||
@ -200,7 +201,13 @@ export default class EnvironmentStore implements IEnvironmentStore {
|
||||
{ column: 'sort_order', order: 'asc' },
|
||||
{ column: 'created_at', order: 'asc' },
|
||||
]);
|
||||
|
||||
if (query) {
|
||||
qB = qB.where(query);
|
||||
}
|
||||
|
||||
const rows = await qB;
|
||||
|
||||
return rows.map(mapRowWithProjectCounts);
|
||||
}
|
||||
|
||||
|
@ -120,6 +120,25 @@ class EventStore extends AnyEventEmitter implements IEventStore {
|
||||
return present;
|
||||
}
|
||||
|
||||
async getForFeatures(
|
||||
features: string[],
|
||||
environments: string[],
|
||||
query: { type: string; projectId: string },
|
||||
): Promise<IEvent[]> {
|
||||
try {
|
||||
const rows = await this.db
|
||||
.select(EVENT_COLUMNS)
|
||||
.from(TABLE)
|
||||
.where({ type: query.type, project: query.projectId })
|
||||
.whereIn('feature_name', features)
|
||||
.whereIn('environment', environments);
|
||||
|
||||
return rows.map(this.rowToEvent);
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async get(key: number): Promise<IEvent> {
|
||||
const row = await this.db(TABLE).where({ id: key }).first();
|
||||
return this.rowToEvent(row);
|
||||
|
208
src/lib/read-models/project-status/project-status.test.ts
Normal file
208
src/lib/read-models/project-status/project-status.test.ts
Normal file
@ -0,0 +1,208 @@
|
||||
import { addDays, subDays } from 'date-fns';
|
||||
import { IEvent } from 'lib/types';
|
||||
import { ProjectStatus } from './project-status';
|
||||
|
||||
const modifyEventCreatedAt = (events: IEvent[], days: number): IEvent[] => {
|
||||
return events.map((event) => {
|
||||
const newEvent = { ...event };
|
||||
newEvent.createdAt = addDays(newEvent.createdAt, days);
|
||||
newEvent.id = newEvent.id + days;
|
||||
return newEvent;
|
||||
});
|
||||
};
|
||||
|
||||
const createEvent = (env: string, overrides: Partial<IEvent>) => {
|
||||
return {
|
||||
id: Math.floor(Math.random() * 1000),
|
||||
type: 'feature-environment-enabled',
|
||||
createdBy: 'Fredrik',
|
||||
createdAt: new Date('2023-01-25T09:37:32.504Z'),
|
||||
data: null,
|
||||
preData: null,
|
||||
tags: [],
|
||||
featureName: 'average-prod-time',
|
||||
project: 'average-time-to-prod',
|
||||
environment: env,
|
||||
...overrides,
|
||||
};
|
||||
};
|
||||
|
||||
const events = [
|
||||
{
|
||||
id: 65,
|
||||
type: 'feature-environment-enabled',
|
||||
createdBy: 'Fredrik',
|
||||
createdAt: new Date('2023-01-25T09:37:32.504Z'),
|
||||
data: null,
|
||||
preData: null,
|
||||
tags: [],
|
||||
featureName: 'average-prod-time',
|
||||
project: 'average-time-to-prod',
|
||||
environment: 'default',
|
||||
},
|
||||
{
|
||||
id: 66,
|
||||
type: 'feature-environment-enabled',
|
||||
createdBy: 'Fredrik',
|
||||
createdAt: new Date('2023-01-31T09:37:32.506Z'),
|
||||
data: null,
|
||||
preData: null,
|
||||
tags: [],
|
||||
featureName: 'average-prod-time-2',
|
||||
project: 'average-time-to-prod',
|
||||
environment: 'default',
|
||||
},
|
||||
{
|
||||
id: 67,
|
||||
type: 'feature-environment-enabled',
|
||||
createdBy: 'Fredrik',
|
||||
createdAt: new Date('2023-01-26T09:37:32.508Z'),
|
||||
data: null,
|
||||
preData: null,
|
||||
tags: [],
|
||||
featureName: 'average-prod-time-3',
|
||||
project: 'average-time-to-prod',
|
||||
environment: 'default',
|
||||
},
|
||||
{
|
||||
id: 68,
|
||||
type: 'feature-environment-enabled',
|
||||
createdBy: 'Fredrik',
|
||||
createdAt: new Date('2023-02-02T09:37:32.509Z'),
|
||||
data: null,
|
||||
preData: null,
|
||||
tags: [],
|
||||
featureName: 'average-prod-time-4',
|
||||
project: 'average-time-to-prod',
|
||||
environment: 'default',
|
||||
},
|
||||
];
|
||||
|
||||
const environments = [
|
||||
{
|
||||
name: 'default',
|
||||
type: 'production',
|
||||
sortOrder: 1,
|
||||
enabled: true,
|
||||
protected: true,
|
||||
projectApiTokenCount: 0,
|
||||
projectEnabledToggleCount: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const features = [
|
||||
{
|
||||
name: 'average-prod-time',
|
||||
description: null,
|
||||
type: 'release',
|
||||
project: 'average-time-to-prod',
|
||||
stale: false,
|
||||
createdAt: new Date('2023-01-19T09:37:32.483Z'),
|
||||
lastSeenAt: null,
|
||||
impressionData: false,
|
||||
archivedAt: null,
|
||||
archived: false,
|
||||
},
|
||||
{
|
||||
name: 'average-prod-time-4',
|
||||
description: null,
|
||||
type: 'release',
|
||||
project: 'average-time-to-prod',
|
||||
stale: false,
|
||||
createdAt: new Date('2023-01-19T09:37:32.484Z'),
|
||||
lastSeenAt: null,
|
||||
impressionData: false,
|
||||
archivedAt: null,
|
||||
archived: false,
|
||||
},
|
||||
{
|
||||
name: 'average-prod-time-2',
|
||||
description: null,
|
||||
type: 'release',
|
||||
project: 'average-time-to-prod',
|
||||
stale: false,
|
||||
createdAt: new Date('2023-01-19T09:37:32.484Z'),
|
||||
lastSeenAt: null,
|
||||
impressionData: false,
|
||||
archivedAt: null,
|
||||
archived: false,
|
||||
},
|
||||
{
|
||||
name: 'average-prod-time-3',
|
||||
description: null,
|
||||
type: 'release',
|
||||
project: 'average-time-to-prod',
|
||||
stale: false,
|
||||
createdAt: new Date('2023-01-19T09:37:32.486Z'),
|
||||
lastSeenAt: null,
|
||||
impressionData: false,
|
||||
archivedAt: null,
|
||||
archived: false,
|
||||
},
|
||||
];
|
||||
|
||||
describe('calculate average time to production', () => {
|
||||
test('should build a map of feature events', () => {
|
||||
const projectStatus = new ProjectStatus(features, environments, events);
|
||||
|
||||
const featureEvents = projectStatus.getFeatureEvents();
|
||||
|
||||
expect(Object.keys(featureEvents).length).toBe(4);
|
||||
expect(featureEvents['average-prod-time'].createdAt).toBeTruthy();
|
||||
expect(featureEvents['average-prod-time'].events).toBeInstanceOf(Array);
|
||||
});
|
||||
|
||||
test('should calculate average correctly', () => {
|
||||
const projectStatus = new ProjectStatus(features, environments, events);
|
||||
|
||||
const timeToProduction = projectStatus.calculateAverageTimeToProd();
|
||||
|
||||
expect(timeToProduction).toBe(9.75);
|
||||
});
|
||||
|
||||
test('should sort events by createdAt', () => {
|
||||
const projectStatus = new ProjectStatus(features, environments, [
|
||||
...modifyEventCreatedAt(events, 5),
|
||||
...events,
|
||||
]);
|
||||
|
||||
const featureEvents = projectStatus.getFeatureEvents();
|
||||
const sortedFeatureEvents =
|
||||
projectStatus.sortFeatureEventsByCreatedAt(featureEvents);
|
||||
|
||||
const [firstEvent, secondEvent] =
|
||||
sortedFeatureEvents['average-prod-time'].events;
|
||||
|
||||
const firstEventCreatedAt = new Date(firstEvent.createdAt);
|
||||
const secondEventCreatedAt = new Date(secondEvent.createdAt);
|
||||
|
||||
expect(firstEventCreatedAt.getTime()).toBeLessThan(
|
||||
secondEventCreatedAt.getTime(),
|
||||
);
|
||||
|
||||
const [firstEvent2, secondEvent2] =
|
||||
sortedFeatureEvents['average-prod-time-2'].events;
|
||||
|
||||
const firstEventCreatedAt2 = new Date(firstEvent2.createdAt);
|
||||
const secondEventCreatedAt2 = new Date(secondEvent2.createdAt);
|
||||
|
||||
expect(firstEventCreatedAt2.getTime()).toBeLessThan(
|
||||
secondEventCreatedAt2.getTime(),
|
||||
);
|
||||
});
|
||||
|
||||
test('should not count events that are development environments', () => {
|
||||
const projectStatus = new ProjectStatus(features, environments, [
|
||||
createEvent('development', {
|
||||
createdAt: subDays(new Date('2023-01-25T09:37:32.504Z'), 10),
|
||||
}),
|
||||
createEvent('development', {
|
||||
createdAt: subDays(new Date('2023-01-25T09:37:32.504Z'), 10),
|
||||
}),
|
||||
...events,
|
||||
]);
|
||||
|
||||
const timeToProduction = projectStatus.calculateAverageTimeToProd();
|
||||
expect(timeToProduction).toBe(9.75);
|
||||
});
|
||||
});
|
111
src/lib/read-models/project-status/project-status.ts
Normal file
111
src/lib/read-models/project-status/project-status.ts
Normal file
@ -0,0 +1,111 @@
|
||||
import { differenceInDays } from 'date-fns';
|
||||
import { FeatureToggle, IEvent, IProjectEnvironment } from 'lib/types';
|
||||
|
||||
interface IFeatureTimeToProdCalculationMap {
|
||||
[index: string]: IFeatureTimeToProdData;
|
||||
}
|
||||
|
||||
interface IFeatureTimeToProdData {
|
||||
createdAt: string;
|
||||
events: IEvent[];
|
||||
}
|
||||
|
||||
export class ProjectStatus {
|
||||
private features: FeatureToggle[];
|
||||
|
||||
private productionEnvironments: IProjectEnvironment[];
|
||||
|
||||
private events: IEvent[];
|
||||
|
||||
constructor(
|
||||
features: FeatureToggle[],
|
||||
productionEnvironments: IProjectEnvironment[],
|
||||
events: IEvent[],
|
||||
) {
|
||||
this.features = features;
|
||||
this.productionEnvironments = productionEnvironments;
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
calculateAverageTimeToProd(): number {
|
||||
const featureEvents = this.getFeatureEvents();
|
||||
const sortedFeatureEvents =
|
||||
this.sortFeatureEventsByCreatedAt(featureEvents);
|
||||
const timeToProdPerFeature =
|
||||
this.calculateTimeToProdForFeatures(sortedFeatureEvents);
|
||||
|
||||
const sum = timeToProdPerFeature.reduce((acc, curr) => acc + curr, 0);
|
||||
|
||||
return sum / Object.keys(sortedFeatureEvents).length;
|
||||
}
|
||||
|
||||
getFeatureEvents(): IFeatureTimeToProdCalculationMap {
|
||||
return this.filterEvents(this.events).reduce((acc, event) => {
|
||||
if (acc[event.featureName]) {
|
||||
acc[event.featureName].events.push(event);
|
||||
} else {
|
||||
const foundFeature = this.features.find(
|
||||
(feature) => feature.name === event.featureName,
|
||||
);
|
||||
acc[event.featureName] = { events: [event] };
|
||||
acc[event.featureName].createdAt = foundFeature?.createdAt;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
filterEvents(events: IEvent[]): IEvent[] {
|
||||
return events.filter((event) => {
|
||||
const found = this.productionEnvironments.find(
|
||||
(env) => env.name === event.environment,
|
||||
);
|
||||
|
||||
if (found) {
|
||||
return found.type === 'production';
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
calculateTimeToProdForFeatures(
|
||||
featureEvents: IFeatureTimeToProdCalculationMap,
|
||||
): number[] {
|
||||
return Object.keys(featureEvents).map((featureName) => {
|
||||
const feature = featureEvents[featureName];
|
||||
const earliestEvent = feature.events[0];
|
||||
|
||||
const createdAtDate = new Date(feature.createdAt);
|
||||
const eventDate = new Date(earliestEvent.createdAt);
|
||||
const diff = differenceInDays(eventDate, createdAtDate);
|
||||
|
||||
return diff;
|
||||
});
|
||||
}
|
||||
|
||||
sortFeatureEventsByCreatedAt(
|
||||
featureEvents: IFeatureTimeToProdCalculationMap,
|
||||
): IFeatureTimeToProdCalculationMap {
|
||||
return Object.keys(featureEvents).reduce((acc, featureName) => {
|
||||
const feature = featureEvents[featureName];
|
||||
acc[featureName] = {
|
||||
...feature,
|
||||
events: feature.events.sort((a, b) => {
|
||||
const aDate = new Date(a.createdAt);
|
||||
const bDate = new Date(b.createdAt);
|
||||
|
||||
if (aDate > bDate) {
|
||||
return 1;
|
||||
}
|
||||
if (aDate < bDate) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}),
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
}
|
@ -161,6 +161,14 @@ export const createServices = (
|
||||
minutesToMilliseconds(5),
|
||||
);
|
||||
|
||||
if (config.flagResolver.isEnabled('projectStatusApi')) {
|
||||
const ONE_DAY = 1440;
|
||||
schedulerService.schedule(
|
||||
projectService.statusJob.bind(projectHealthService),
|
||||
minutesToMilliseconds(ONE_DAY),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
accessService,
|
||||
accountService,
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
ProjectGroupAddedEvent,
|
||||
ProjectGroupRemovedEvent,
|
||||
ProjectGroupUpdateRoleEvent,
|
||||
FEATURE_ENVIRONMENT_ENABLED,
|
||||
} from '../types/events';
|
||||
import { IUnleashStores, IUnleashConfig, IAccountStore } from '../types';
|
||||
import {
|
||||
@ -46,6 +47,7 @@ import { arraysHaveSameItems } from '../util/arraysHaveSameItems';
|
||||
import { GroupService } from './group-service';
|
||||
import { IGroupModelWithProjectRole, IGroupRole } from 'lib/types/group';
|
||||
import { FavoritesService } from './favorites-service';
|
||||
import { ProjectStatus } from '../read-models/project-status/project-status';
|
||||
|
||||
const getCreatedBy = (user: IUser) => user.email || user.username;
|
||||
|
||||
@ -591,6 +593,48 @@ export default class ProjectService {
|
||||
return this.store.getProjectsByUser(userId);
|
||||
}
|
||||
|
||||
async statusJob(): Promise<void> {
|
||||
const projects = await this.store.getAll();
|
||||
|
||||
await Promise.all(
|
||||
projects.map((project) =>
|
||||
this.calculateAverageTimeToProd(project.id),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async calculateAverageTimeToProd(projectId: string): Promise<number> {
|
||||
// Get all features for project with type release
|
||||
const features = await this.featureToggleStore.getAll({
|
||||
type: 'release',
|
||||
project: projectId,
|
||||
});
|
||||
|
||||
// Get all project environments with type of production
|
||||
const productionEnvironments =
|
||||
await this.environmentStore.getProjectEnvironments(projectId, {
|
||||
type: 'production',
|
||||
});
|
||||
|
||||
// Get all events for features that correspond to feature toggle environment ON
|
||||
// Filter out events that are not a production evironment
|
||||
const events = await this.eventStore.getForFeatures(
|
||||
features.map((feature) => feature.name),
|
||||
productionEnvironments.map((env) => env.name),
|
||||
{
|
||||
type: FEATURE_ENVIRONMENT_ENABLED,
|
||||
projectId,
|
||||
},
|
||||
);
|
||||
|
||||
const projectStatus = new ProjectStatus(
|
||||
features,
|
||||
productionEnvironments,
|
||||
events,
|
||||
);
|
||||
return projectStatus.calculateAverageTimeToProd();
|
||||
}
|
||||
|
||||
async getProjectOverview(
|
||||
projectId: string,
|
||||
archived: boolean = false,
|
||||
|
@ -10,6 +10,10 @@ const flags = {
|
||||
process.env.UNLEASH_EXPERIMENTAL_EMBED_PROXY,
|
||||
true,
|
||||
),
|
||||
projectStatusApi: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_EXPERIMENTAL_PROJECT_STATUS_API,
|
||||
false,
|
||||
),
|
||||
newProjectOverview: parseEnvVarBoolean(
|
||||
process.env.NEW_PROJECT_OVERVIEW,
|
||||
false,
|
||||
|
@ -24,5 +24,8 @@ export interface IEnvironmentStore extends Store<IEnvironment, string> {
|
||||
enable(environments: IEnvironment[]): Promise<void>;
|
||||
count(): Promise<number>;
|
||||
getAllWithCounts(): Promise<IEnvironment[]>;
|
||||
getProjectEnvironments(projectId: string): Promise<IProjectEnvironment[]>;
|
||||
getProjectEnvironments(
|
||||
projectId: string,
|
||||
query?: Object,
|
||||
): Promise<IProjectEnvironment[]>;
|
||||
}
|
||||
|
@ -10,4 +10,9 @@ export interface IEventStore extends Store<IEvent, number>, EventEmitter {
|
||||
count(): Promise<number>;
|
||||
filteredCount(search: SearchEventsSchema): Promise<number>;
|
||||
searchEvents(search: SearchEventsSchema): Promise<IEvent[]>;
|
||||
getForFeatures(
|
||||
features: string[],
|
||||
environments: string[],
|
||||
query: { type: string; projectId: string },
|
||||
): Promise<IEvent[]>;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ export interface IFeatureToggleQuery {
|
||||
archived: boolean;
|
||||
project: string;
|
||||
stale: boolean;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export interface IFeatureToggleStore extends Store<FeatureToggle, string> {
|
||||
|
@ -10,7 +10,7 @@ import EnvironmentStore from '../../../lib/db/environment-store';
|
||||
import { IUnleashStores } from '../../../lib/types';
|
||||
import { IFeatureEnvironmentStore } from '../../../lib/types/stores/feature-environment-store';
|
||||
import { DEFAULT_ENV } from '../../../lib/util/constants';
|
||||
import { IUnleashOptions } from 'lib/server-impl';
|
||||
import { IUnleashOptions, Knex } from 'lib/server-impl';
|
||||
|
||||
// require('db-migrate-shared').log.silence(false);
|
||||
|
||||
@ -76,6 +76,7 @@ export interface ITestDb {
|
||||
stores: IUnleashStores;
|
||||
reset: () => Promise<void>;
|
||||
destroy: () => Promise<void>;
|
||||
rawDatabase: Knex;
|
||||
}
|
||||
|
||||
export default async function init(
|
||||
@ -108,6 +109,7 @@ export default async function init(
|
||||
await setupDatabase(stores);
|
||||
|
||||
return {
|
||||
rawDatabase: testDb,
|
||||
stores,
|
||||
reset: async () => {
|
||||
await resetDatabase(testDb);
|
||||
|
@ -12,6 +12,8 @@ import IncompatibleProjectError from '../../../lib/error/incompatible-project-er
|
||||
import { SegmentService } from '../../../lib/services/segment-service';
|
||||
import { GroupService } from '../../../lib/services/group-service';
|
||||
import { FavoritesService } from '../../../lib/services';
|
||||
import { FeatureEnvironmentEvent } from '../../../lib/types/events';
|
||||
import { addDays } from 'date-fns';
|
||||
|
||||
let stores;
|
||||
let db: ITestDb;
|
||||
@ -72,6 +74,7 @@ afterEach(async () => {
|
||||
const wipeUserPermissions = users.map(async (u) => {
|
||||
await stores.accessStore.unlinkUserRoles(u.id);
|
||||
});
|
||||
await stores.eventStore.deleteAll();
|
||||
await Promise.allSettled(deleteEnvs);
|
||||
await Promise.allSettled(wipeUserPermissions);
|
||||
});
|
||||
@ -1053,3 +1056,59 @@ test('should only count active feature toggles for project', async () => {
|
||||
const theProject = projects.find((p) => p.id === project.id);
|
||||
expect(theProject?.featureCount).toBe(1);
|
||||
});
|
||||
|
||||
const updateEventCreatedAt = async (days: number, featureName: string) => {
|
||||
await db.rawDatabase
|
||||
.table('events')
|
||||
.update({ created_at: addDays(new Date(), days) })
|
||||
.where({ feature_name: featureName });
|
||||
};
|
||||
|
||||
test('should calculate average time to production', async () => {
|
||||
const project = {
|
||||
id: 'average-time-to-prod',
|
||||
name: 'average-time-to-prod',
|
||||
};
|
||||
|
||||
await projectService.createProject(project, user.id);
|
||||
|
||||
const toggles = [
|
||||
{ name: 'average-prod-time' },
|
||||
{ name: 'average-prod-time-2' },
|
||||
{ name: 'average-prod-time-3' },
|
||||
{ name: 'average-prod-time-4' },
|
||||
];
|
||||
|
||||
const featureToggles = await Promise.all(
|
||||
toggles.map((toggle) => {
|
||||
return featureToggleService.createFeatureToggle(
|
||||
project.id,
|
||||
toggle,
|
||||
user,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
featureToggles.map((toggle) => {
|
||||
return stores.eventStore.store(
|
||||
new FeatureEnvironmentEvent({
|
||||
enabled: true,
|
||||
project: project.id,
|
||||
featureName: toggle.name,
|
||||
environment: 'default',
|
||||
createdBy: 'Fredrik',
|
||||
tags: [],
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
await updateEventCreatedAt(6, 'average-prod-time');
|
||||
await updateEventCreatedAt(12, 'average-prod-time-2');
|
||||
await updateEventCreatedAt(7, 'average-prod-time-3');
|
||||
await updateEventCreatedAt(14, 'average-prod-time-4');
|
||||
|
||||
const result = await projectService.calculateAverageTimeToProd(project.id);
|
||||
expect(result).toBe(9.75);
|
||||
});
|
||||
|
15
src/test/fixtures/fake-event-store.ts
vendored
15
src/test/fixtures/fake-event-store.ts
vendored
@ -65,6 +65,21 @@ class FakeEventStore extends AnyEventEmitter implements IEventStore {
|
||||
async searchEvents(): Promise<IEvent[]> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
async getForFeatures(
|
||||
features: string[],
|
||||
environments: string[],
|
||||
query: { type: string; projectId: string },
|
||||
): Promise<IEvent[]> {
|
||||
return this.events.filter((event) => {
|
||||
return (
|
||||
event.type === query.type &&
|
||||
event.project === query.projectId &&
|
||||
features.includes(event.data.featureName) &&
|
||||
environments.includes(event.data.environment)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FakeEventStore;
|
||||
|
Loading…
Reference in New Issue
Block a user