1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-23 00:22:19 +01:00

refactor: project health domain (#3024)

This commit is contained in:
Tymoteusz Czech 2023-01-31 13:08:20 +01:00 committed by GitHub
parent 50e0c0818a
commit 59e8e2b1ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 94 additions and 74 deletions

View File

@ -0,0 +1,65 @@
import { hoursToMilliseconds } from 'date-fns';
import type { IProjectHealthReport } from 'lib/types';
import type { IFeatureType } from 'lib/types/stores/feature-type-store';
const getPotentiallyStaleCount = (
features: Array<{
stale?: boolean;
createdAt?: Date;
type?: string;
}>,
featureTypes: IFeatureType[],
) => {
const today = new Date().valueOf();
return features.filter((feature) => {
const diff = today - feature.createdAt?.valueOf();
const featureTypeExpectedLifetime = featureTypes.find(
(t) => t.id === feature.type,
)?.lifetimeDays;
return (
!feature.stale &&
diff >= featureTypeExpectedLifetime * hoursToMilliseconds(24)
);
}).length;
};
export const calculateProjectHealth = (
features: Array<{
stale?: boolean;
createdAt?: Date;
type?: string;
}>,
featureTypes: IFeatureType[],
): Pick<
IProjectHealthReport,
'staleCount' | 'potentiallyStaleCount' | 'activeCount'
> => ({
potentiallyStaleCount: getPotentiallyStaleCount(features, featureTypes),
activeCount: features.filter((f) => !f.stale).length,
staleCount: features.filter((f) => f.stale).length,
});
export const getHealthRating = (
features: Array<{
stale?: boolean;
createdAt?: Date;
type?: string;
}>,
featureTypes: IFeatureType[],
): number => {
const { potentiallyStaleCount, activeCount, staleCount } =
calculateProjectHealth(features, featureTypes);
const toggleCount = activeCount + staleCount;
const startPercentage = 100;
const stalePercentage = (staleCount / toggleCount) * 100 || 0;
const potentiallyStalePercentage =
(potentiallyStaleCount / toggleCount) * 100 || 0;
const rating = Math.round(
startPercentage - stalePercentage - potentiallyStalePercentage,
);
return rating;
};

View File

@ -1,18 +1,20 @@
import { IUnleashStores } from '../types/stores'; import { IUnleashStores } from '../types/stores';
import { IUnleashConfig } from '../types/option'; import { IUnleashConfig } from '../types/option';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { import type { IProject, IProjectHealthReport } from '../types/model';
FeatureToggle, import type { IFeatureToggleStore } from '../types/stores/feature-toggle-store';
IFeatureOverview, import type {
IProject, IFeatureType,
IProjectHealthReport, IFeatureTypeStore,
} from '../types/model'; } from '../types/stores/feature-type-store';
import { IFeatureToggleStore } from '../types/stores/feature-toggle-store'; import type { IProjectStore } from '../types/stores/project-store';
import { IFeatureTypeStore } from '../types/stores/feature-type-store';
import { IProjectStore } from '../types/stores/project-store';
import { hoursToMilliseconds } from 'date-fns'; import { hoursToMilliseconds } from 'date-fns';
import Timer = NodeJS.Timer; import Timer = NodeJS.Timer;
import ProjectService from './project-service'; import ProjectService from './project-service';
import {
calculateProjectHealth,
getHealthRating,
} from '../domain/calculate-project-health/calculate-project-health';
export default class ProjectHealthService { export default class ProjectHealthService {
private logger: Logger; private logger: Logger;
@ -23,7 +25,7 @@ export default class ProjectHealthService {
private featureToggleStore: IFeatureToggleStore; private featureToggleStore: IFeatureToggleStore;
private featureTypes: Map<string, number>; private featureTypes: IFeatureType[];
private healthRatingTimer: Timer; private healthRatingTimer: Timer;
@ -45,7 +47,7 @@ export default class ProjectHealthService {
this.projectStore = projectStore; this.projectStore = projectStore;
this.featureTypeStore = featureTypeStore; this.featureTypeStore = featureTypeStore;
this.featureToggleStore = featureToggleStore; this.featureToggleStore = featureToggleStore;
this.featureTypes = new Map(); this.featureTypes = [];
this.healthRatingTimer = setInterval( this.healthRatingTimer = setInterval(
() => this.setHealthRating(), () => this.setHealthRating(),
hoursToMilliseconds(1), hoursToMilliseconds(1),
@ -57,85 +59,38 @@ export default class ProjectHealthService {
async getProjectHealthReport( async getProjectHealthReport(
projectId: string, projectId: string,
): Promise<IProjectHealthReport> { ): Promise<IProjectHealthReport> {
if (this.featureTypes.length === 0) {
this.featureTypes = await this.featureTypeStore.getAll();
}
const overview = await this.projectService.getProjectOverview( const overview = await this.projectService.getProjectOverview(
projectId, projectId,
false, false,
undefined, undefined,
); );
const healthRating = calculateProjectHealth(
overview.features,
this.featureTypes,
);
return { return {
...overview, ...overview,
potentiallyStaleCount: await this.potentiallyStaleCount( ...healthRating,
overview.features,
),
activeCount: this.activeCount(overview.features),
staleCount: this.staleCount(overview.features),
}; };
} }
private async potentiallyStaleCount(
features: Pick<FeatureToggle, 'createdAt' | 'stale' | 'type'>[],
): Promise<number> {
const today = new Date().valueOf();
if (this.featureTypes.size === 0) {
const types = await this.featureTypeStore.getAll();
types.forEach((type) => {
this.featureTypes.set(
type.name.toLowerCase(),
type.lifetimeDays,
);
});
}
return features.filter((feature) => {
const diff = today - feature.createdAt.valueOf();
const featureTypeExpectedLifetime = this.featureTypes.get(
feature.type,
);
return (
!feature.stale &&
diff >= featureTypeExpectedLifetime * hoursToMilliseconds(24)
);
}).length;
}
private activeCount(features: IFeatureOverview[]): number {
return features.filter((f) => !f.stale).length;
}
private staleCount(features: IFeatureOverview[]): number {
return features.filter((f) => f.stale).length;
}
async calculateHealthRating(project: IProject): Promise<number> { async calculateHealthRating(project: IProject): Promise<number> {
if (this.featureTypes.length === 0) {
this.featureTypes = await this.featureTypeStore.getAll();
}
const toggles = await this.featureToggleStore.getAll({ const toggles = await this.featureToggleStore.getAll({
project: project.id, project: project.id,
archived: false, archived: false,
}); });
const activeToggles = toggles.filter((feature) => !feature.stale); return getHealthRating(toggles, this.featureTypes);
const staleToggles = toggles.length - activeToggles.length;
const potentiallyStaleToggles = await this.potentiallyStaleCount(
activeToggles,
);
return this.getHealthRating(
toggles.length,
staleToggles,
potentiallyStaleToggles,
);
}
private getHealthRating(
toggleCount: number,
staleToggleCount: number,
potentiallyStaleCount: number,
): number {
const startPercentage = 100;
const stalePercentage = (staleToggleCount / toggleCount) * 100 || 0;
const potentiallyStalePercentage =
(potentiallyStaleCount / toggleCount) * 100 || 0;
const rating = Math.round(
startPercentage - stalePercentage - potentiallyStalePercentage,
);
return rating;
} }
async setHealthRating(): Promise<void> { async setHealthRating(): Promise<void> {