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

Add project health service

This commit is contained in:
Fredrik Oseberg 2021-08-09 14:14:50 +02:00
parent c1c56acf15
commit 8c26410f50
3 changed files with 154 additions and 0 deletions

View File

@ -23,6 +23,7 @@ import ResetTokenService from './reset-token-service';
import SettingService from './setting-service';
import SessionService from './session-service';
import UserFeedbackService from './user-feedback-service';
import ProjectHealthService from './project-health-service';
export const createServices = (
stores: IUnleashStores,
@ -54,6 +55,7 @@ export const createServices = (
const healthService = new HealthService(stores, config);
const settingService = new SettingService(stores, config);
const userFeedbackService = new UserFeedbackService(stores, config);
const projectHealthService = new ProjectHealthService(stores, config);
return {
accessService,
@ -77,6 +79,7 @@ export const createServices = (
settingService,
sessionService,
userFeedbackService,
projectHealthService,
};
};

View File

@ -0,0 +1,149 @@
import { IUnleashStores } from '../types/stores';
import { IUnleashConfig } from '../types/option';
import ProjectStore, { IProject } from '../db/project-store';
import { Logger } from '../logger';
import {
FeatureToggle,
IFeatureOverview,
IProjectHealthReport,
IProjectOverview,
} from '../types/model';
import {
MILLISECONDS_IN_DAY,
MILLISECONDS_IN_ONE_HOUR,
} from '../util/constants';
import FeatureTypeStore from '../db/feature-type-store';
import Timer = NodeJS.Timer;
import FeatureToggleStore from '../db/feature-toggle-store';
export default class ProjectHealthService {
private logger: Logger;
private projectStore: ProjectStore;
private featureTypeStore: FeatureTypeStore;
private featureToggleStore: FeatureToggleStore;
private featureTypes: Map<string, number>;
private healthRatingTimer: Timer;
constructor(
{
projectStore,
featureTypeStore,
featureToggleStore,
}: Pick<
IUnleashStores,
'projectStore' | 'featureTypeStore' | 'featureToggleStore'
>,
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
) {
this.logger = getLogger('services/project-health-service.ts');
this.projectStore = projectStore;
this.featureTypeStore = featureTypeStore;
this.featureToggleStore = featureToggleStore;
this.featureTypes = new Map();
this.healthRatingTimer = setInterval(
() => this.setHealthRating(),
MILLISECONDS_IN_ONE_HOUR,
).unref();
}
async getProjectHealthReport(
projectId: string,
): Promise<IProjectHealthReport> {
//const overview = await this.getProjectOverview(projectId, false);
const features = await this.featureToggleStore.getFeatures();
return {
// ...overview,
potentiallyStaleCount: await this.potentiallyStaleCount(features),
activeCount: this.activeCount(features),
staleCount: this.staleCount(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 * MILLISECONDS_IN_DAY
);
}).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> {
const toggles = await this.featureToggleStore.getFeaturesBy({
project: project.id,
});
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;
}
async setHealthRating(): Promise<void> {
const projects = await this.projectStore.getAll();
await Promise.all(
projects.map(async project => {
const newHealth = await this.calculateHealthRating(project);
await this.projectStore.updateHealth({
id: project.id,
health: newHealth,
});
}),
);
}
destroy(): void {
clearInterval(this.healthRatingTimer);
}
}

View File

@ -19,6 +19,7 @@ import HealthService from '../services/health-service';
import SettingService from '../services/setting-service';
import SessionService from '../services/session-service';
import UserFeedbackService from '../services/user-feedback-service';
import ProjectHealthService from '../services/project-health-service';
export interface IUnleashServices {
accessService: AccessService;
@ -42,4 +43,5 @@ export interface IUnleashServices {
userService: UserService;
versionService: VersionService;
userFeedbackService: UserFeedbackService;
projectHealthService: ProjectHealthService;
}