mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-10 01:16:39 +02:00
refactor: switching to new stats calculations (#3477)
This commit is contained in:
parent
52cc633a0a
commit
f1133556bd
@ -135,7 +135,6 @@ class ProjectStatsStore implements IProjectStatsStore {
|
||||
.innerJoin('features', 'features.name', '=', 'events.feature_name')
|
||||
.where('events.type', '=', 'feature-environment-enabled')
|
||||
.where('environments.type', '=', 'production')
|
||||
// kill-switch is long lived
|
||||
.where('features.type', '=', 'release')
|
||||
// exclude events for features that were previously deleted
|
||||
.where(this.db.raw('events.created_at > features.created_at'))
|
||||
|
@ -0,0 +1,26 @@
|
||||
import { calculateAverageTimeToProd } from './time-to-production';
|
||||
|
||||
describe('calculate average time to production', () => {
|
||||
test('should calculate average correctly', () => {
|
||||
const timeToProduction = calculateAverageTimeToProd([
|
||||
{
|
||||
created: new Date('2022-12-05T09:37:32.483Z'),
|
||||
enabled: new Date('2023-01-25T09:37:32.504Z'),
|
||||
},
|
||||
{
|
||||
created: new Date('2023-01-19T09:37:32.484Z'),
|
||||
enabled: new Date('2023-01-31T09:37:32.506Z'),
|
||||
},
|
||||
{
|
||||
created: new Date('2023-01-19T09:37:32.484Z'),
|
||||
enabled: new Date('2023-02-02T09:37:32.509Z'),
|
||||
},
|
||||
{
|
||||
created: new Date('2023-01-19T09:37:32.486Z'),
|
||||
enabled: new Date('2023-01-26T09:37:32.508Z'),
|
||||
},
|
||||
]);
|
||||
|
||||
expect(timeToProduction).toBe(21);
|
||||
});
|
||||
});
|
@ -0,0 +1,20 @@
|
||||
import { differenceInDays } from 'date-fns';
|
||||
import { ICreateEnabledDates } from '../../../types/stores/project-stats-store-type';
|
||||
|
||||
const calculateTimeToProdForFeatures = (
|
||||
items: ICreateEnabledDates[],
|
||||
): number[] =>
|
||||
items.map((item) => differenceInDays(item.enabled, item.created));
|
||||
|
||||
export const calculateAverageTimeToProd = (
|
||||
items: ICreateEnabledDates[],
|
||||
): number => {
|
||||
const timeToProdPerFeature = calculateTimeToProdForFeatures(items);
|
||||
if (timeToProdPerFeature.length) {
|
||||
const sum = timeToProdPerFeature.reduce((acc, curr) => acc + curr, 0);
|
||||
|
||||
return Number((sum / Object.keys(items).length).toFixed(1));
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
@ -1,227 +0,0 @@
|
||||
import { addDays, subDays } from 'date-fns';
|
||||
import { IEvent } from 'lib/types';
|
||||
import { TimeToProduction } from './time-to-production';
|
||||
|
||||
const modifyEventCreatedAt = (events: IEvent[], days: number): IEvent[] => {
|
||||
return events.map((event) => {
|
||||
const newEvent = { ...event };
|
||||
newEvent.createdAt = addDays(newEvent.createdAt, days);
|
||||
newEvent.id = newEvent.id + days;
|
||||
return newEvent;
|
||||
});
|
||||
};
|
||||
|
||||
const createEvent = (env: string, overrides: Partial<IEvent>) => {
|
||||
return {
|
||||
id: Math.floor(Math.random() * 1000),
|
||||
type: 'feature-environment-enabled',
|
||||
createdBy: 'Fredrik',
|
||||
createdAt: new Date('2023-01-25T09:37:32.504Z'),
|
||||
data: null,
|
||||
preData: null,
|
||||
tags: [],
|
||||
featureName: 'average-prod-time',
|
||||
project: 'average-time-to-prod',
|
||||
environment: env,
|
||||
...overrides,
|
||||
};
|
||||
};
|
||||
|
||||
const events = [
|
||||
{
|
||||
id: 65,
|
||||
type: 'feature-environment-enabled',
|
||||
createdBy: 'Fredrik',
|
||||
createdAt: new Date('2023-01-25T09:37:32.504Z'),
|
||||
data: null,
|
||||
preData: null,
|
||||
tags: [],
|
||||
featureName: 'average-prod-time',
|
||||
project: 'average-time-to-prod',
|
||||
environment: 'default',
|
||||
},
|
||||
{
|
||||
id: 66,
|
||||
type: 'feature-environment-enabled',
|
||||
createdBy: 'Fredrik',
|
||||
createdAt: new Date('2023-01-31T09:37:32.506Z'),
|
||||
data: null,
|
||||
preData: null,
|
||||
tags: [],
|
||||
featureName: 'average-prod-time-2',
|
||||
project: 'average-time-to-prod',
|
||||
environment: 'default',
|
||||
},
|
||||
{
|
||||
id: 67,
|
||||
type: 'feature-environment-enabled',
|
||||
createdBy: 'Fredrik',
|
||||
createdAt: new Date('2023-01-26T09:37:32.508Z'),
|
||||
data: null,
|
||||
preData: null,
|
||||
tags: [],
|
||||
featureName: 'average-prod-time-3',
|
||||
project: 'average-time-to-prod',
|
||||
environment: 'default',
|
||||
},
|
||||
{
|
||||
id: 68,
|
||||
type: 'feature-environment-enabled',
|
||||
createdBy: 'Fredrik',
|
||||
createdAt: new Date('2023-02-02T09:37:32.509Z'),
|
||||
data: null,
|
||||
preData: null,
|
||||
tags: [],
|
||||
featureName: 'average-prod-time-4',
|
||||
project: 'average-time-to-prod',
|
||||
environment: 'default',
|
||||
},
|
||||
];
|
||||
|
||||
const environments = [
|
||||
{
|
||||
name: 'default',
|
||||
type: 'production',
|
||||
sortOrder: 1,
|
||||
enabled: true,
|
||||
protected: true,
|
||||
projectApiTokenCount: 0,
|
||||
projectEnabledToggleCount: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const features = [
|
||||
{
|
||||
name: 'average-prod-time',
|
||||
type: 'release',
|
||||
project: 'average-time-to-prod',
|
||||
stale: false,
|
||||
createdAt: new Date('2022-12-05T09:37:32.483Z'),
|
||||
impressionData: false,
|
||||
archived: false,
|
||||
},
|
||||
{
|
||||
name: 'average-prod-time-4',
|
||||
type: 'release',
|
||||
project: 'average-time-to-prod',
|
||||
stale: false,
|
||||
createdAt: new Date('2023-01-19T09:37:32.484Z'),
|
||||
impressionData: false,
|
||||
archived: false,
|
||||
},
|
||||
{
|
||||
name: 'average-prod-time-2',
|
||||
type: 'release',
|
||||
project: 'average-time-to-prod',
|
||||
stale: false,
|
||||
createdAt: new Date('2023-01-19T09:37:32.484Z'),
|
||||
impressionData: false,
|
||||
archived: false,
|
||||
},
|
||||
{
|
||||
name: 'average-prod-time-3',
|
||||
type: 'release',
|
||||
project: 'average-time-to-prod',
|
||||
stale: false,
|
||||
createdAt: new Date('2023-01-19T09:37:32.486Z'),
|
||||
impressionData: false,
|
||||
archived: false,
|
||||
},
|
||||
];
|
||||
|
||||
describe('calculate average time to production', () => {
|
||||
test('should build a map of feature events', () => {
|
||||
const projectStatus = new TimeToProduction(
|
||||
features,
|
||||
environments,
|
||||
events,
|
||||
);
|
||||
|
||||
const featureEvents = projectStatus.getFeatureEvents();
|
||||
|
||||
expect(Object.keys(featureEvents).length).toBe(4);
|
||||
expect(featureEvents['average-prod-time'].createdAt).toBeTruthy();
|
||||
expect(featureEvents['average-prod-time'].events).toBeInstanceOf(Array);
|
||||
});
|
||||
|
||||
test('[legacy] should calculate average correctly', () => {
|
||||
const projectStatus = new TimeToProduction(
|
||||
features,
|
||||
environments,
|
||||
events,
|
||||
);
|
||||
|
||||
const timeToProduction = projectStatus.calculateAverageTimeToProd();
|
||||
|
||||
expect(timeToProduction).toBe(21);
|
||||
});
|
||||
|
||||
test('should calculate average correctly', () => {
|
||||
const timeToProduction = TimeToProduction.calculateAverageTimeToProd([
|
||||
{
|
||||
created: new Date('2022-12-05T09:37:32.483Z'),
|
||||
enabled: new Date('2023-01-25T09:37:32.504Z'),
|
||||
},
|
||||
{
|
||||
created: new Date('2023-01-19T09:37:32.484Z'),
|
||||
enabled: new Date('2023-01-31T09:37:32.506Z'),
|
||||
},
|
||||
{
|
||||
created: new Date('2023-01-19T09:37:32.484Z'),
|
||||
enabled: new Date('2023-02-02T09:37:32.509Z'),
|
||||
},
|
||||
{
|
||||
created: new Date('2023-01-19T09:37:32.486Z'),
|
||||
enabled: new Date('2023-01-26T09:37:32.508Z'),
|
||||
},
|
||||
]);
|
||||
|
||||
expect(timeToProduction).toBe(21);
|
||||
});
|
||||
|
||||
test('should sort events by createdAt', () => {
|
||||
const projectStatus = new TimeToProduction(features, environments, [
|
||||
...modifyEventCreatedAt(events, 5),
|
||||
...events,
|
||||
]);
|
||||
|
||||
const featureEvents = projectStatus.getFeatureEvents();
|
||||
const sortedFeatureEvents =
|
||||
projectStatus.sortFeatureEventsByCreatedAt(featureEvents);
|
||||
|
||||
const [firstEvent, secondEvent] =
|
||||
sortedFeatureEvents['average-prod-time'].events;
|
||||
|
||||
const firstEventCreatedAt = new Date(firstEvent.createdAt);
|
||||
const secondEventCreatedAt = new Date(secondEvent.createdAt);
|
||||
|
||||
expect(firstEventCreatedAt.getTime()).toBeLessThan(
|
||||
secondEventCreatedAt.getTime(),
|
||||
);
|
||||
|
||||
const [firstEvent2, secondEvent2] =
|
||||
sortedFeatureEvents['average-prod-time-2'].events;
|
||||
|
||||
const firstEventCreatedAt2 = new Date(firstEvent2.createdAt);
|
||||
const secondEventCreatedAt2 = new Date(secondEvent2.createdAt);
|
||||
|
||||
expect(firstEventCreatedAt2.getTime()).toBeLessThan(
|
||||
secondEventCreatedAt2.getTime(),
|
||||
);
|
||||
});
|
||||
|
||||
test('should not count events that are development environments', () => {
|
||||
const projectStatus = new TimeToProduction(features, environments, [
|
||||
createEvent('development', {
|
||||
createdAt: subDays(new Date('2023-01-25T09:37:32.504Z'), 10),
|
||||
}),
|
||||
createEvent('development', {
|
||||
createdAt: subDays(new Date('2023-01-25T09:37:32.504Z'), 10),
|
||||
}),
|
||||
...events,
|
||||
]);
|
||||
|
||||
const timeToProduction = projectStatus.calculateAverageTimeToProd();
|
||||
expect(timeToProduction).toBe(21);
|
||||
});
|
||||
});
|
@ -1,150 +0,0 @@
|
||||
import { differenceInDays } from 'date-fns';
|
||||
import { FeatureToggle, IEvent, IProjectEnvironment } from 'lib/types';
|
||||
import { ICreateEnabledDates } from '../../types/stores/project-stats-store-type';
|
||||
|
||||
interface IFeatureTimeToProdCalculationMap {
|
||||
[index: string]: IFeatureTimeToProdData;
|
||||
}
|
||||
|
||||
interface IFeatureTimeToProdData {
|
||||
createdAt: string;
|
||||
events: IEvent[];
|
||||
}
|
||||
|
||||
export class TimeToProduction {
|
||||
private features: FeatureToggle[];
|
||||
|
||||
private productionEnvironments: IProjectEnvironment[];
|
||||
|
||||
private events: IEvent[];
|
||||
|
||||
// todo: remove
|
||||
constructor(
|
||||
features: FeatureToggle[],
|
||||
productionEnvironments: IProjectEnvironment[],
|
||||
events: IEvent[],
|
||||
) {
|
||||
this.features = features;
|
||||
this.productionEnvironments = productionEnvironments;
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
// todo: remove
|
||||
calculateAverageTimeToProd(): number {
|
||||
const featureEvents = this.getFeatureEvents();
|
||||
const sortedFeatureEvents =
|
||||
this.sortFeatureEventsByCreatedAt(featureEvents);
|
||||
|
||||
const timeToProdPerFeature =
|
||||
this.calculateTimeToProdForFeatures(sortedFeatureEvents);
|
||||
if (timeToProdPerFeature.length) {
|
||||
const sum = timeToProdPerFeature.reduce(
|
||||
(acc, curr) => acc + curr,
|
||||
0,
|
||||
);
|
||||
|
||||
return Number(
|
||||
(sum / Object.keys(sortedFeatureEvents).length).toFixed(1),
|
||||
);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static calculateAverageTimeToProd(items: ICreateEnabledDates[]): number {
|
||||
const timeToProdPerFeature =
|
||||
TimeToProduction.calculateTimeToProdForFeatures(items);
|
||||
if (timeToProdPerFeature.length) {
|
||||
const sum = timeToProdPerFeature.reduce(
|
||||
(acc, curr) => acc + curr,
|
||||
0,
|
||||
);
|
||||
|
||||
return Number((sum / Object.keys(items).length).toFixed(1));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// todo: remove, as DB query can handle it
|
||||
getFeatureEvents(): IFeatureTimeToProdCalculationMap {
|
||||
return this.getProductionEvents(this.events).reduce((acc, event) => {
|
||||
if (acc[event.featureName]) {
|
||||
acc[event.featureName].events.push(event);
|
||||
} else {
|
||||
const foundFeature = this.features.find(
|
||||
(feature) => feature.name === event.featureName,
|
||||
);
|
||||
acc[event.featureName] = { events: [event] };
|
||||
acc[event.featureName].createdAt = foundFeature?.createdAt;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
// todo: remove it as DB query can handle it
|
||||
private getProductionEvents(events: IEvent[]): IEvent[] {
|
||||
return events.filter((event) => {
|
||||
const found = this.productionEnvironments.find(
|
||||
(env) => env.name === event.environment,
|
||||
);
|
||||
|
||||
if (found) {
|
||||
return found.type === 'production';
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private calculateTimeToProdForFeatures(
|
||||
featureEvents: IFeatureTimeToProdCalculationMap,
|
||||
): number[] {
|
||||
return Object.keys(featureEvents).map((featureName) => {
|
||||
const feature = featureEvents[featureName];
|
||||
|
||||
const earliestEvent = feature.events[0];
|
||||
|
||||
const createdAtDate = new Date(feature.createdAt);
|
||||
const eventDate = new Date(earliestEvent.createdAt);
|
||||
const diff = differenceInDays(eventDate, createdAtDate);
|
||||
|
||||
return diff;
|
||||
});
|
||||
}
|
||||
|
||||
private static calculateTimeToProdForFeatures(
|
||||
items: ICreateEnabledDates[],
|
||||
): number[] {
|
||||
return items.map((item) =>
|
||||
differenceInDays(item.enabled, item.created),
|
||||
);
|
||||
}
|
||||
|
||||
// todo: remove as DB query can handle it
|
||||
sortFeatureEventsByCreatedAt(
|
||||
featureEvents: IFeatureTimeToProdCalculationMap,
|
||||
): IFeatureTimeToProdCalculationMap {
|
||||
return Object.keys(featureEvents).reduce((acc, featureName) => {
|
||||
const feature = featureEvents[featureName];
|
||||
acc[featureName] = {
|
||||
...feature,
|
||||
events: feature.events.sort((a, b) => {
|
||||
const aDate = new Date(a.createdAt);
|
||||
const bDate = new Date(b.createdAt);
|
||||
|
||||
if (aDate > bDate) {
|
||||
return 1;
|
||||
}
|
||||
if (aDate < bDate) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}),
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
}
|
@ -8,7 +8,6 @@ import { projectSchema } from './project-schema';
|
||||
import NotFoundError from '../error/notfound-error';
|
||||
import {
|
||||
DEFAULT_PROJECT,
|
||||
FEATURE_ENVIRONMENT_ENABLED,
|
||||
FeatureToggle,
|
||||
IAccountStore,
|
||||
IEnvironmentStore,
|
||||
@ -54,7 +53,7 @@ import { arraysHaveSameItems } from '../util';
|
||||
import { GroupService } from './group-service';
|
||||
import { IGroupModelWithProjectRole, IGroupRole } from 'lib/types/group';
|
||||
import { FavoritesService } from './favorites-service';
|
||||
import { TimeToProduction } from '../read-models/time-to-production/time-to-production';
|
||||
import { calculateAverageTimeToProd } from '../features/feature-toggle/time-to-production/time-to-production';
|
||||
import { IProjectStatsStore } from 'lib/types/stores/project-stats-store-type';
|
||||
import { uniqueByKey } from '../util/unique';
|
||||
|
||||
@ -688,20 +687,6 @@ export default class ProjectService {
|
||||
}
|
||||
|
||||
async getStatusUpdates(projectId: string): Promise<ICalculateStatus> {
|
||||
// Get all features for project with type release
|
||||
// todo: remove after release of the improved query
|
||||
const features = await this.featureToggleStore.getAll({
|
||||
type: 'release',
|
||||
project: projectId,
|
||||
});
|
||||
|
||||
// todo: remove after release of the improved query
|
||||
const archivedFeatures = await this.featureToggleStore.getAll({
|
||||
archived: true,
|
||||
type: 'release',
|
||||
project: projectId,
|
||||
});
|
||||
|
||||
const dateMinusThirtyDays = subDays(new Date(), 30).toISOString();
|
||||
const dateMinusSixtyDays = subDays(new Date(), 60).toISOString();
|
||||
|
||||
@ -759,57 +744,10 @@ export default class ProjectService {
|
||||
]),
|
||||
]);
|
||||
|
||||
// Get all project environments with type of production
|
||||
// todo: remove after release of the improved query
|
||||
const productionEnvironments =
|
||||
await this.environmentStore.getProjectEnvironments(projectId, {
|
||||
type: 'production',
|
||||
});
|
||||
|
||||
// Get all events for features that correspond to feature toggle environment ON
|
||||
// Filter out events that are not a production evironment
|
||||
// todo: remove after release of the improved query
|
||||
const allFeatures = [...features, ...archivedFeatures];
|
||||
|
||||
// todo: remove after release of the improved query
|
||||
const eventsData = await this.eventStore.query([
|
||||
{
|
||||
op: 'forFeatures',
|
||||
parameters: {
|
||||
features: allFeatures.map((feature) => feature.name),
|
||||
environments: productionEnvironments.map((env) => env.name),
|
||||
type: FEATURE_ENVIRONMENT_ENABLED,
|
||||
projectId,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// todo: remove after release of the improved query
|
||||
const timeToProduction = new TimeToProduction(
|
||||
allFeatures,
|
||||
productionEnvironments,
|
||||
eventsData,
|
||||
const avgTimeToProdCurrentWindow = calculateAverageTimeToProd(
|
||||
await this.projectStatsStore.getTimeToProdDates(projectId),
|
||||
);
|
||||
|
||||
const avgTimeToProdCurrentWindowFast =
|
||||
TimeToProduction.calculateAverageTimeToProd(
|
||||
await this.projectStatsStore.getTimeToProdDates(projectId),
|
||||
);
|
||||
const avgTimeToProdCurrentWindowSlow =
|
||||
timeToProduction.calculateAverageTimeToProd();
|
||||
|
||||
const avgTimeToProdCurrentWindow = this.flagResolver.isEnabled(
|
||||
'projectStatusApiImprovements',
|
||||
)
|
||||
? avgTimeToProdCurrentWindowFast
|
||||
: avgTimeToProdCurrentWindowSlow;
|
||||
|
||||
if (avgTimeToProdCurrentWindowFast != avgTimeToProdCurrentWindowSlow) {
|
||||
this.logger.warn(
|
||||
`Lead time calculation difference, old ${avgTimeToProdCurrentWindowSlow}, new ${avgTimeToProdCurrentWindowFast}`,
|
||||
);
|
||||
}
|
||||
|
||||
const projectMembersAddedCurrentWindow =
|
||||
await this.store.getMembersCountByProjectAfterDate(
|
||||
projectId,
|
||||
|
@ -13,7 +13,7 @@ import { SegmentService } from '../../../lib/services/segment-service';
|
||||
import { GroupService } from '../../../lib/services/group-service';
|
||||
import { FavoritesService } from '../../../lib/services';
|
||||
import { FeatureEnvironmentEvent } from '../../../lib/types/events';
|
||||
import { subDays } from 'date-fns';
|
||||
import { addDays, subDays } from 'date-fns';
|
||||
import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model';
|
||||
|
||||
let stores;
|
||||
@ -1269,6 +1269,94 @@ test('should calculate average time to production', async () => {
|
||||
expect(result.updates.avgTimeToProdCurrentWindow).toBe(11.4);
|
||||
});
|
||||
|
||||
test('should calculate average time to production ignoring some items', async () => {
|
||||
const project = {
|
||||
id: 'average-time-to-prod-corner-cases',
|
||||
name: 'average-time-to-prod',
|
||||
mode: 'open' as const,
|
||||
};
|
||||
const makeEvent = (featureName: string) => ({
|
||||
enabled: true,
|
||||
project: project.id,
|
||||
featureName,
|
||||
environment: 'default',
|
||||
createdBy: 'Fredrik',
|
||||
tags: [],
|
||||
});
|
||||
|
||||
await projectService.createProject(project, user.id);
|
||||
await stores.environmentStore.create({
|
||||
name: 'customEnv',
|
||||
type: 'development',
|
||||
});
|
||||
await environmentService.addEnvironmentToProject('customEnv', project.id);
|
||||
|
||||
// actual toggle we take for calculations
|
||||
const toggle = { name: 'main-toggle' };
|
||||
await featureToggleService.createFeatureToggle(project.id, toggle, user);
|
||||
await updateFeature(toggle.name, {
|
||||
created_at: subDays(new Date(), 20),
|
||||
});
|
||||
await stores.eventStore.store(
|
||||
new FeatureEnvironmentEvent(makeEvent(toggle.name)),
|
||||
);
|
||||
// ignore events added after first enabled
|
||||
await updateEventCreatedAt(addDays(new Date(), 1), toggle.name);
|
||||
await stores.eventStore.store(
|
||||
new FeatureEnvironmentEvent(makeEvent(toggle.name)),
|
||||
);
|
||||
|
||||
// ignore toggles enabled in non-prod envs
|
||||
const devToggle = { name: 'dev-toggle' };
|
||||
await featureToggleService.createFeatureToggle(project.id, devToggle, user);
|
||||
await stores.eventStore.store(
|
||||
new FeatureEnvironmentEvent({
|
||||
...makeEvent(devToggle.name),
|
||||
environment: 'customEnv',
|
||||
}),
|
||||
);
|
||||
|
||||
// ignore toggles from other projects
|
||||
const otherProjectToggle = { name: 'other-project' };
|
||||
await featureToggleService.createFeatureToggle(
|
||||
'default',
|
||||
otherProjectToggle,
|
||||
user,
|
||||
);
|
||||
await stores.eventStore.store(
|
||||
new FeatureEnvironmentEvent(makeEvent(otherProjectToggle.name)),
|
||||
);
|
||||
|
||||
// ignore non-release toggles
|
||||
const nonReleaseToggle = { name: 'permission-toggle', type: 'permission' };
|
||||
await featureToggleService.createFeatureToggle(
|
||||
project.id,
|
||||
nonReleaseToggle,
|
||||
user,
|
||||
);
|
||||
await stores.eventStore.store(
|
||||
new FeatureEnvironmentEvent(makeEvent(nonReleaseToggle.name)),
|
||||
);
|
||||
|
||||
// ignore toggles with events before toggle creation time
|
||||
const previouslyDeleteToggle = { name: 'previously-deleted' };
|
||||
await featureToggleService.createFeatureToggle(
|
||||
project.id,
|
||||
previouslyDeleteToggle,
|
||||
user,
|
||||
);
|
||||
await stores.eventStore.store(
|
||||
new FeatureEnvironmentEvent(makeEvent(previouslyDeleteToggle.name)),
|
||||
);
|
||||
await updateEventCreatedAt(
|
||||
subDays(new Date(), 30),
|
||||
previouslyDeleteToggle.name,
|
||||
);
|
||||
|
||||
const result = await projectService.getStatusUpdates(project.id);
|
||||
expect(result.updates.avgTimeToProdCurrentWindow).toBe(20);
|
||||
});
|
||||
|
||||
test('should get correct amount of features created in current and past window', async () => {
|
||||
const project = {
|
||||
id: 'features-created',
|
||||
|
Loading…
Reference in New Issue
Block a user