diff --git a/src/lib/domain/calculate-project-health/calculate-project-health.ts b/src/lib/domain/calculate-project-health/calculate-project-health.ts new file mode 100644 index 0000000000..36a00defd3 --- /dev/null +++ b/src/lib/domain/calculate-project-health/calculate-project-health.ts @@ -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; +}; diff --git a/src/lib/services/project-health-service.ts b/src/lib/services/project-health-service.ts index 86ede7b754..1d0de140d2 100644 --- a/src/lib/services/project-health-service.ts +++ b/src/lib/services/project-health-service.ts @@ -1,18 +1,20 @@ import { IUnleashStores } from '../types/stores'; import { IUnleashConfig } from '../types/option'; import { Logger } from '../logger'; -import { - FeatureToggle, - IFeatureOverview, - IProject, - IProjectHealthReport, -} from '../types/model'; -import { IFeatureToggleStore } from '../types/stores/feature-toggle-store'; -import { IFeatureTypeStore } from '../types/stores/feature-type-store'; -import { IProjectStore } from '../types/stores/project-store'; +import type { IProject, IProjectHealthReport } from '../types/model'; +import type { IFeatureToggleStore } from '../types/stores/feature-toggle-store'; +import type { + IFeatureType, + IFeatureTypeStore, +} from '../types/stores/feature-type-store'; +import type { IProjectStore } from '../types/stores/project-store'; import { hoursToMilliseconds } from 'date-fns'; import Timer = NodeJS.Timer; import ProjectService from './project-service'; +import { + calculateProjectHealth, + getHealthRating, +} from '../domain/calculate-project-health/calculate-project-health'; export default class ProjectHealthService { private logger: Logger; @@ -23,7 +25,7 @@ export default class ProjectHealthService { private featureToggleStore: IFeatureToggleStore; - private featureTypes: Map; + private featureTypes: IFeatureType[]; private healthRatingTimer: Timer; @@ -45,7 +47,7 @@ export default class ProjectHealthService { this.projectStore = projectStore; this.featureTypeStore = featureTypeStore; this.featureToggleStore = featureToggleStore; - this.featureTypes = new Map(); + this.featureTypes = []; this.healthRatingTimer = setInterval( () => this.setHealthRating(), hoursToMilliseconds(1), @@ -57,85 +59,38 @@ export default class ProjectHealthService { async getProjectHealthReport( projectId: string, ): Promise { + if (this.featureTypes.length === 0) { + this.featureTypes = await this.featureTypeStore.getAll(); + } + const overview = await this.projectService.getProjectOverview( projectId, false, undefined, ); + + const healthRating = calculateProjectHealth( + overview.features, + this.featureTypes, + ); + return { ...overview, - potentiallyStaleCount: await this.potentiallyStaleCount( - overview.features, - ), - activeCount: this.activeCount(overview.features), - staleCount: this.staleCount(overview.features), + ...healthRating, }; } - private async potentiallyStaleCount( - features: Pick[], - ): Promise { - 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 { + if (this.featureTypes.length === 0) { + this.featureTypes = await this.featureTypeStore.getAll(); + } + const toggles = await this.featureToggleStore.getAll({ project: project.id, archived: false, }); - const activeToggles = toggles.filter((feature) => !feature.stale); - 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; + return getHealthRating(toggles, this.featureTypes); } async setHealthRating(): Promise {