mirror of
https://github.com/Unleash/unleash.git
synced 2025-03-04 00:18:40 +01:00
Project health tests (#3028)
This commit is contained in:
parent
627958d30f
commit
ab9712812a
203
src/lib/domain/project-health/project-health.test.ts
Normal file
203
src/lib/domain/project-health/project-health.test.ts
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
import { subDays } from 'date-fns';
|
||||||
|
import type { IFeatureType } from 'lib/types/stores/feature-type-store';
|
||||||
|
import {
|
||||||
|
calculateProjectHealth,
|
||||||
|
calculateHealthRating,
|
||||||
|
} from './project-health';
|
||||||
|
|
||||||
|
const exampleFeatureTypes: IFeatureType[] = [
|
||||||
|
{
|
||||||
|
id: 'default',
|
||||||
|
name: 'Default',
|
||||||
|
description: '',
|
||||||
|
lifetimeDays: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'short-lived',
|
||||||
|
name: 'Short lived',
|
||||||
|
description: '',
|
||||||
|
lifetimeDays: 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'non-expiring',
|
||||||
|
name: 'Long lived',
|
||||||
|
description: '',
|
||||||
|
lifetimeDays: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('calculateProjectHealth', () => {
|
||||||
|
it('works with empty features', () => {
|
||||||
|
expect(calculateProjectHealth([], exampleFeatureTypes)).toEqual({
|
||||||
|
activeCount: 0,
|
||||||
|
staleCount: 0,
|
||||||
|
potentiallyStaleCount: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('counts active toggles', () => {
|
||||||
|
const features = [{ stale: false }, {}];
|
||||||
|
|
||||||
|
expect(calculateProjectHealth(features, exampleFeatureTypes)).toEqual({
|
||||||
|
activeCount: 2,
|
||||||
|
staleCount: 0,
|
||||||
|
potentiallyStaleCount: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('counts stale toggles', () => {
|
||||||
|
const features = [{ stale: true }, { stale: false }, {}];
|
||||||
|
|
||||||
|
expect(calculateProjectHealth(features, exampleFeatureTypes)).toEqual({
|
||||||
|
activeCount: 2,
|
||||||
|
staleCount: 1,
|
||||||
|
potentiallyStaleCount: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('takes feature type into account when calculating potentially stale toggles', () => {
|
||||||
|
expect(
|
||||||
|
calculateProjectHealth(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
stale: false,
|
||||||
|
createdAt: subDays(Date.now(), 15),
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
exampleFeatureTypes,
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
activeCount: 1,
|
||||||
|
staleCount: 0,
|
||||||
|
potentiallyStaleCount: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
calculateProjectHealth(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
stale: false,
|
||||||
|
createdAt: subDays(Date.now(), 31),
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
exampleFeatureTypes,
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
activeCount: 1,
|
||||||
|
staleCount: 0,
|
||||||
|
potentiallyStaleCount: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
calculateProjectHealth(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
stale: false,
|
||||||
|
createdAt: subDays(Date.now(), 15),
|
||||||
|
type: 'short-lived',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
exampleFeatureTypes,
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
activeCount: 1,
|
||||||
|
staleCount: 0,
|
||||||
|
potentiallyStaleCount: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't count stale toggles as potentially stale or stale as active", () => {
|
||||||
|
const features = [
|
||||||
|
{
|
||||||
|
stale: true,
|
||||||
|
createdAt: subDays(Date.now(), 31),
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stale: false,
|
||||||
|
createdAt: subDays(Date.now(), 31),
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(calculateProjectHealth(features, exampleFeatureTypes)).toEqual({
|
||||||
|
activeCount: 1,
|
||||||
|
staleCount: 1,
|
||||||
|
potentiallyStaleCount: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('counts non-expiring types properly', () => {
|
||||||
|
const features = [
|
||||||
|
{
|
||||||
|
createdAt: subDays(Date.now(), 366),
|
||||||
|
type: 'non-expiring',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
createdAt: subDays(Date.now(), 366),
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(calculateProjectHealth(features, exampleFeatureTypes)).toEqual({
|
||||||
|
activeCount: 2,
|
||||||
|
staleCount: 0,
|
||||||
|
potentiallyStaleCount: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('calculateHealthRating', () => {
|
||||||
|
it('works with empty feature toggles', () => {
|
||||||
|
expect(calculateHealthRating([], exampleFeatureTypes)).toEqual(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works with stale and active feature toggles', () => {
|
||||||
|
expect(
|
||||||
|
calculateHealthRating(
|
||||||
|
[{ stale: true }, { stale: true }],
|
||||||
|
exampleFeatureTypes,
|
||||||
|
),
|
||||||
|
).toEqual(0);
|
||||||
|
expect(
|
||||||
|
calculateHealthRating(
|
||||||
|
[{ stale: true }, { stale: false }],
|
||||||
|
exampleFeatureTypes,
|
||||||
|
),
|
||||||
|
).toEqual(50);
|
||||||
|
expect(
|
||||||
|
calculateHealthRating(
|
||||||
|
[{ stale: false }, { stale: true }, { stale: false }],
|
||||||
|
exampleFeatureTypes,
|
||||||
|
),
|
||||||
|
).toEqual(67);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('counts potentially stale toggles', () => {
|
||||||
|
expect(
|
||||||
|
calculateHealthRating(
|
||||||
|
[
|
||||||
|
{ createdAt: subDays(Date.now(), 1), type: 'default' },
|
||||||
|
{
|
||||||
|
stale: true,
|
||||||
|
createdAt: subDays(Date.now(), 1),
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stale: true,
|
||||||
|
createdAt: subDays(Date.now(), 31),
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stale: false,
|
||||||
|
createdAt: subDays(Date.now(), 31),
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
exampleFeatureTypes,
|
||||||
|
),
|
||||||
|
).toEqual(25);
|
||||||
|
});
|
||||||
|
});
|
@ -2,12 +2,14 @@ import { hoursToMilliseconds } from 'date-fns';
|
|||||||
import type { IProjectHealthReport } from 'lib/types';
|
import type { IProjectHealthReport } from 'lib/types';
|
||||||
import type { IFeatureType } from 'lib/types/stores/feature-type-store';
|
import type { IFeatureType } from 'lib/types/stores/feature-type-store';
|
||||||
|
|
||||||
const getPotentiallyStaleCount = (
|
type IPartialFeatures = Array<{
|
||||||
features: Array<{
|
|
||||||
stale?: boolean;
|
stale?: boolean;
|
||||||
createdAt?: Date;
|
createdAt?: Date;
|
||||||
type?: string;
|
type?: string;
|
||||||
}>,
|
}>;
|
||||||
|
|
||||||
|
const getPotentiallyStaleCount = (
|
||||||
|
features: IPartialFeatures,
|
||||||
featureTypes: IFeatureType[],
|
featureTypes: IFeatureType[],
|
||||||
) => {
|
) => {
|
||||||
const today = new Date().valueOf();
|
const today = new Date().valueOf();
|
||||||
@ -20,17 +22,14 @@ const getPotentiallyStaleCount = (
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
!feature.stale &&
|
!feature.stale &&
|
||||||
|
featureTypeExpectedLifetime !== null &&
|
||||||
diff >= featureTypeExpectedLifetime * hoursToMilliseconds(24)
|
diff >= featureTypeExpectedLifetime * hoursToMilliseconds(24)
|
||||||
);
|
);
|
||||||
}).length;
|
}).length;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const calculateProjectHealth = (
|
export const calculateProjectHealth = (
|
||||||
features: Array<{
|
features: IPartialFeatures,
|
||||||
stale?: boolean;
|
|
||||||
createdAt?: Date;
|
|
||||||
type?: string;
|
|
||||||
}>,
|
|
||||||
featureTypes: IFeatureType[],
|
featureTypes: IFeatureType[],
|
||||||
): Pick<
|
): Pick<
|
||||||
IProjectHealthReport,
|
IProjectHealthReport,
|
||||||
@ -41,12 +40,8 @@ export const calculateProjectHealth = (
|
|||||||
staleCount: features.filter((f) => f.stale).length,
|
staleCount: features.filter((f) => f.stale).length,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getHealthRating = (
|
export const calculateHealthRating = (
|
||||||
features: Array<{
|
features: IPartialFeatures,
|
||||||
stale?: boolean;
|
|
||||||
createdAt?: Date;
|
|
||||||
type?: string;
|
|
||||||
}>,
|
|
||||||
featureTypes: IFeatureType[],
|
featureTypes: IFeatureType[],
|
||||||
): number => {
|
): number => {
|
||||||
const { potentiallyStaleCount, activeCount, staleCount } =
|
const { potentiallyStaleCount, activeCount, staleCount } =
|
@ -11,8 +11,8 @@ import type { IProjectStore } from '../types/stores/project-store';
|
|||||||
import ProjectService from './project-service';
|
import ProjectService from './project-service';
|
||||||
import {
|
import {
|
||||||
calculateProjectHealth,
|
calculateProjectHealth,
|
||||||
getHealthRating,
|
calculateHealthRating,
|
||||||
} from '../domain/calculate-project-health/calculate-project-health';
|
} from '../domain/project-health/project-health';
|
||||||
|
|
||||||
export default class ProjectHealthService {
|
export default class ProjectHealthService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
@ -82,7 +82,7 @@ export default class ProjectHealthService {
|
|||||||
archived: false,
|
archived: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return getHealthRating(toggles, this.featureTypes);
|
return calculateHealthRating(toggles, this.featureTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setHealthRating(): Promise<void> {
|
async setHealthRating(): Promise<void> {
|
||||||
|
Loading…
Reference in New Issue
Block a user