mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-14 00:19:16 +01:00
chore: put project lifecycle read model in own directory + add fake (#8700)
This PR moves the project lifecycle summary to its own subdirectory and adds files for types (interface) and a fake implementation. It also adds a query for archived flags within the last 30 days taken from `getStatusUpdates` in `src/lib/features/project/project-service.ts` and maps the gathered data onto the expected structure. The expected types have also been adjusted to account for no data. Next step will be hooking it up to the project status service, adding schema, and exposing it in the controller.
This commit is contained in:
parent
8f18c2b194
commit
f92441fa50
@ -0,0 +1,24 @@
|
||||
import type { Db, IUnleashConfig } from '../../../server-impl';
|
||||
import FeatureToggleStore from '../../feature-toggle/feature-toggle-store';
|
||||
import { FakeProjectLifecycleSummaryReadModel } from './fake-project-lifecycle-summary-read-model';
|
||||
import type { IProjectLifecycleSummaryReadModel } from './project-lifecycle-read-model-type';
|
||||
import { ProjectLifecycleSummaryReadModel } from './project-lifecycle-summary-read-model';
|
||||
|
||||
export const createProjectLifecycleSummaryReadModel = (
|
||||
db: Db,
|
||||
config: IUnleashConfig,
|
||||
): IProjectLifecycleSummaryReadModel => {
|
||||
const { eventBus, getLogger, flagResolver } = config;
|
||||
const featureToggleStore = new FeatureToggleStore(
|
||||
db,
|
||||
eventBus,
|
||||
getLogger,
|
||||
flagResolver,
|
||||
);
|
||||
return new ProjectLifecycleSummaryReadModel(db, featureToggleStore);
|
||||
};
|
||||
|
||||
export const createFakeProjectLifecycleSummaryReadModel =
|
||||
(): IProjectLifecycleSummaryReadModel => {
|
||||
return new FakeProjectLifecycleSummaryReadModel();
|
||||
};
|
@ -0,0 +1,25 @@
|
||||
import type {
|
||||
IProjectLifecycleSummaryReadModel,
|
||||
ProjectLifecycleSummary,
|
||||
} from './project-lifecycle-read-model-type';
|
||||
|
||||
export class FakeProjectLifecycleSummaryReadModel
|
||||
implements IProjectLifecycleSummaryReadModel
|
||||
{
|
||||
async getProjectLifecycleSummary(): Promise<ProjectLifecycleSummary> {
|
||||
const placeholderData = {
|
||||
averageDays: 0,
|
||||
currentFlags: 0,
|
||||
};
|
||||
return {
|
||||
initial: placeholderData,
|
||||
preLive: placeholderData,
|
||||
live: placeholderData,
|
||||
completed: placeholderData,
|
||||
archived: {
|
||||
currentFlags: 0,
|
||||
last30Days: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
export interface IProjectLifecycleSummaryReadModel {
|
||||
getProjectLifecycleSummary(
|
||||
projectId: string,
|
||||
): Promise<ProjectLifecycleSummary>;
|
||||
}
|
||||
|
||||
type StageDataWithAverageDays = {
|
||||
averageDays: number | null;
|
||||
currentFlags: number;
|
||||
};
|
||||
|
||||
export type ProjectLifecycleSummary = {
|
||||
initial: StageDataWithAverageDays;
|
||||
preLive: StageDataWithAverageDays;
|
||||
live: StageDataWithAverageDays;
|
||||
completed: StageDataWithAverageDays;
|
||||
archived: {
|
||||
currentFlags: number;
|
||||
last30Days: number;
|
||||
};
|
||||
};
|
@ -1,14 +1,21 @@
|
||||
import { addDays, addMinutes } from 'date-fns';
|
||||
import dbInit, { type ITestDb } from '../../../test/e2e/helpers/database-init';
|
||||
import getLogger from '../../../test/fixtures/no-logger';
|
||||
import dbInit, {
|
||||
type ITestDb,
|
||||
} from '../../../../test/e2e/helpers/database-init';
|
||||
import getLogger from '../../../../test/fixtures/no-logger';
|
||||
import { ProjectLifecycleSummaryReadModel } from './project-lifecycle-summary-read-model';
|
||||
import type { StageName } from '../../types';
|
||||
import { randomId } from '../../util';
|
||||
import type { IFeatureToggleStore, StageName } from '../../../types';
|
||||
import { randomId } from '../../../util';
|
||||
|
||||
let db: ITestDb;
|
||||
let readModel: ProjectLifecycleSummaryReadModel;
|
||||
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('project_lifecycle_summary_read_model_serial', getLogger);
|
||||
readModel = new ProjectLifecycleSummaryReadModel(
|
||||
db.rawDatabase,
|
||||
{} as unknown as IFeatureToggleStore,
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@ -93,8 +100,6 @@ describe('Average time calculation', () => {
|
||||
}
|
||||
}
|
||||
|
||||
const readModel = new ProjectLifecycleSummaryReadModel(db.rawDatabase);
|
||||
|
||||
const result = await readModel.getAverageTimeInEachStage(project.id);
|
||||
|
||||
expect(result).toMatchObject({
|
||||
@ -110,7 +115,6 @@ describe('Average time calculation', () => {
|
||||
name: 'project',
|
||||
id: randomId(),
|
||||
});
|
||||
const readModel = new ProjectLifecycleSummaryReadModel(db.rawDatabase);
|
||||
|
||||
const result1 = await readModel.getAverageTimeInEachStage(project.id);
|
||||
|
||||
@ -160,7 +164,6 @@ describe('Average time calculation', () => {
|
||||
name: 'project',
|
||||
id: randomId(),
|
||||
});
|
||||
const readModel = new ProjectLifecycleSummaryReadModel(db.rawDatabase);
|
||||
|
||||
const flag = await db.stores.featureToggleStore.create(project.id, {
|
||||
name: randomId(),
|
||||
@ -260,8 +263,6 @@ describe('count current flags in each stage', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
const readModel = new ProjectLifecycleSummaryReadModel(db.rawDatabase);
|
||||
|
||||
const result = await readModel.getCurrentFlagsInEachStage(project.id);
|
||||
|
||||
expect(result).toMatchObject({
|
@ -1,48 +1,40 @@
|
||||
import * as permissions from '../../types/permissions';
|
||||
import type { Db } from '../../db/db';
|
||||
import type { Db } from '../../../db/db';
|
||||
import type { IFeatureToggleStore } from '../../../types';
|
||||
import { subDays } from 'date-fns';
|
||||
import type {
|
||||
IProjectLifecycleSummaryReadModel,
|
||||
ProjectLifecycleSummary,
|
||||
} from './project-lifecycle-read-model-type';
|
||||
|
||||
const { ADMIN } = permissions;
|
||||
type FlagsInStage = {
|
||||
initial: number;
|
||||
'pre-live': number;
|
||||
live: number;
|
||||
completed: number;
|
||||
archived: number;
|
||||
};
|
||||
|
||||
export type IProjectLifecycleSummaryReadModel = {};
|
||||
|
||||
type ProjectLifecycleSummary = {
|
||||
initial: {
|
||||
averageDays: number;
|
||||
currentFlags: number;
|
||||
};
|
||||
preLive: {
|
||||
averageDays: number;
|
||||
currentFlags: number;
|
||||
};
|
||||
live: {
|
||||
averageDays: number;
|
||||
currentFlags: number;
|
||||
};
|
||||
completed: {
|
||||
averageDays: number;
|
||||
currentFlags: number;
|
||||
};
|
||||
archived: {
|
||||
currentFlags: number;
|
||||
archivedFlagsOverLastMonth: number;
|
||||
};
|
||||
type AverageTimeInStage = {
|
||||
initial: number | null;
|
||||
'pre-live': number | null;
|
||||
live: number | null;
|
||||
completed: number | null;
|
||||
};
|
||||
|
||||
export class ProjectLifecycleSummaryReadModel
|
||||
implements IProjectLifecycleSummaryReadModel
|
||||
{
|
||||
private db: Db;
|
||||
private featureToggleStore: IFeatureToggleStore;
|
||||
|
||||
constructor(db: Db) {
|
||||
constructor(db: Db, featureToggleStore: IFeatureToggleStore) {
|
||||
this.db = db;
|
||||
this.featureToggleStore = featureToggleStore;
|
||||
}
|
||||
|
||||
async getAverageTimeInEachStage(projectId: string): Promise<{
|
||||
initial: number | null;
|
||||
'pre-live': number | null;
|
||||
live: number | null;
|
||||
completed: number | null;
|
||||
}> {
|
||||
async getAverageTimeInEachStage(
|
||||
projectId: string,
|
||||
): Promise<AverageTimeInStage> {
|
||||
const q = this.db
|
||||
.with(
|
||||
'stage_durations',
|
||||
@ -88,7 +80,7 @@ export class ProjectLifecycleSummaryReadModel
|
||||
);
|
||||
}
|
||||
|
||||
async getCurrentFlagsInEachStage(projectId: string) {
|
||||
async getCurrentFlagsInEachStage(projectId: string): Promise<FlagsInStage> {
|
||||
const query = this.db('feature_lifecycles as fl')
|
||||
.innerJoin('features as f', 'fl.feature', 'f.name')
|
||||
.where('f.project', projectId)
|
||||
@ -110,11 +102,18 @@ export class ProjectLifecycleSummaryReadModel
|
||||
completed: 0,
|
||||
archived: 0,
|
||||
},
|
||||
);
|
||||
) as FlagsInStage;
|
||||
}
|
||||
|
||||
async getArchivedFlagsOverLastMonth(projectId: string) {
|
||||
return 0;
|
||||
async getArchivedFlagsLast30Days(projectId: string): Promise<number> {
|
||||
const dateMinusThirtyDays = subDays(new Date(), 30).toISOString();
|
||||
|
||||
return this.featureToggleStore.countByDate({
|
||||
project: projectId,
|
||||
archived: true,
|
||||
dateAccessor: 'archived_at',
|
||||
date: dateMinusThirtyDays,
|
||||
});
|
||||
}
|
||||
|
||||
async getProjectLifecycleSummary(
|
||||
@ -123,34 +122,33 @@ export class ProjectLifecycleSummaryReadModel
|
||||
const [
|
||||
averageTimeInEachStage,
|
||||
currentFlagsInEachStage,
|
||||
archivedFlagsOverLastMonth,
|
||||
archivedFlagsLast30Days,
|
||||
] = await Promise.all([
|
||||
this.getAverageTimeInEachStage(projectId),
|
||||
this.getCurrentFlagsInEachStage(projectId),
|
||||
this.getArchivedFlagsOverLastMonth(projectId),
|
||||
this.getArchivedFlagsLast30Days(projectId),
|
||||
]);
|
||||
|
||||
// collate the data
|
||||
return {
|
||||
initial: {
|
||||
averageDays: 0,
|
||||
currentFlags: 0,
|
||||
averageDays: averageTimeInEachStage.initial,
|
||||
currentFlags: currentFlagsInEachStage.initial,
|
||||
},
|
||||
preLive: {
|
||||
averageDays: 0,
|
||||
currentFlags: 0,
|
||||
averageDays: averageTimeInEachStage['pre-live'],
|
||||
currentFlags: currentFlagsInEachStage['pre-live'],
|
||||
},
|
||||
live: {
|
||||
averageDays: 0,
|
||||
currentFlags: 0,
|
||||
averageDays: averageTimeInEachStage.live,
|
||||
currentFlags: currentFlagsInEachStage.live,
|
||||
},
|
||||
completed: {
|
||||
averageDays: 0,
|
||||
currentFlags: 0,
|
||||
averageDays: averageTimeInEachStage.completed,
|
||||
currentFlags: currentFlagsInEachStage.completed,
|
||||
},
|
||||
archived: {
|
||||
currentFlags: 0,
|
||||
archivedFlagsOverLastMonth: 0,
|
||||
currentFlags: currentFlagsInEachStage.archived,
|
||||
last30Days: archivedFlagsLast30Days,
|
||||
},
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user