mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-09 00:18:00 +01:00
feat: ttp for all projects should show median value per day (#6656)
Separates out the calculation of the median to its own file and tested Closes # [1-2212](https://linear.app/unleash/issue/1-2212/timetoproduction-for-all-projects-should-show-the-change-in-the-median) --------- Signed-off-by: andreas-unleash <andreas@getunleash.ai>
This commit is contained in:
parent
bce25bf0f1
commit
ce4a243165
@ -51,19 +51,19 @@ export const chartInfo = {
|
|||||||
'How the overall health changes over time for the selected projects.',
|
'How the overall health changes over time for the selected projects.',
|
||||||
},
|
},
|
||||||
averageTimeToProduction: {
|
averageTimeToProduction: {
|
||||||
title: 'Average time to production',
|
title: 'Median time to production',
|
||||||
tooltip:
|
tooltip:
|
||||||
'How long does it currently take on average from when a feature flag was created until it was enabled in a "production" type environment. This is calculated only from feature flags of the type "release" and is averaged across the selected projects.',
|
'How long does it currently take on average from when a feature flag was created until it was enabled in a "production" type environment. This is calculated only from feature flags of the type "release" and is the median across the selected projects.',
|
||||||
},
|
},
|
||||||
timeToProduction: {
|
timeToProduction: {
|
||||||
title: 'Time to production',
|
title: 'Time to production',
|
||||||
tooltip:
|
tooltip:
|
||||||
'How the average time to production changes over time across all projects.',
|
'How the median time to production changes over time across all projects.',
|
||||||
},
|
},
|
||||||
timeToProductionPerProject: {
|
timeToProductionPerProject: {
|
||||||
title: 'Time to production per project',
|
title: 'Time to production per project',
|
||||||
tooltip:
|
tooltip:
|
||||||
'How the average time to production changes over time for the selected projects.',
|
'How the median time to production changes over time for the selected projects.',
|
||||||
},
|
},
|
||||||
metrics: {
|
metrics: {
|
||||||
title: 'Flag evaluation metrics',
|
title: 'Flag evaluation metrics',
|
||||||
|
@ -11,6 +11,7 @@ import type { GroupedDataByProject } from '../../hooks/useGroupedProjectTrends';
|
|||||||
import { usePlaceholderData } from '../../hooks/usePlaceholderData';
|
import { usePlaceholderData } from '../../hooks/usePlaceholderData';
|
||||||
import { TimeToProductionTooltip } from './TimeToProductionTooltip/TimeToProductionTooltip';
|
import { TimeToProductionTooltip } from './TimeToProductionTooltip/TimeToProductionTooltip';
|
||||||
import { useTheme } from '@mui/material';
|
import { useTheme } from '@mui/material';
|
||||||
|
import { medianTimeToProduction } from './median-time-to-production';
|
||||||
|
|
||||||
interface ITimeToProductionChartProps {
|
interface ITimeToProductionChartProps {
|
||||||
projectFlagTrends: GroupedDataByProject<
|
projectFlagTrends: GroupedDataByProject<
|
||||||
@ -19,34 +20,6 @@ interface ITimeToProductionChartProps {
|
|||||||
isAggregate?: boolean;
|
isAggregate?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type GroupedDataByDate<T> = Record<string, T[]>;
|
|
||||||
|
|
||||||
type DateResult<T> = Record<string, T>;
|
|
||||||
|
|
||||||
function averageTimeToProduction(
|
|
||||||
projectsData: ExecutiveSummarySchema['projectFlagTrends'],
|
|
||||||
): DateResult<number> {
|
|
||||||
// Group the data by date
|
|
||||||
const groupedData: GroupedDataByDate<number> = {};
|
|
||||||
projectsData.forEach((item) => {
|
|
||||||
const { date, timeToProduction } = item;
|
|
||||||
if (!groupedData[date]) {
|
|
||||||
groupedData[date] = [];
|
|
||||||
}
|
|
||||||
if (timeToProduction !== undefined) {
|
|
||||||
groupedData[date].push(timeToProduction);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Calculate the average time to production for each date
|
|
||||||
const averageByDate: DateResult<number> = {};
|
|
||||||
Object.entries(groupedData).forEach(([date, times]) => {
|
|
||||||
const sum = times.reduce((acc, curr) => acc + curr, 0);
|
|
||||||
const average = sum / times.length;
|
|
||||||
averageByDate[date] = average;
|
|
||||||
});
|
|
||||||
return averageByDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TimeToProductionChart: VFC<ITimeToProductionChartProps> = ({
|
export const TimeToProductionChart: VFC<ITimeToProductionChartProps> = ({
|
||||||
projectFlagTrends,
|
projectFlagTrends,
|
||||||
isAggregate,
|
isAggregate,
|
||||||
@ -59,7 +32,7 @@ export const TimeToProductionChart: VFC<ITimeToProductionChartProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const aggregatedPerDay = useMemo(() => {
|
const aggregatedPerDay = useMemo(() => {
|
||||||
const result = averageTimeToProduction(
|
const result = medianTimeToProduction(
|
||||||
Object.values(projectsDatasets.datasets).flatMap(
|
Object.values(projectsDatasets.datasets).flatMap(
|
||||||
(item) => item.data,
|
(item) => item.data,
|
||||||
),
|
),
|
||||||
@ -74,7 +47,7 @@ export const TimeToProductionChart: VFC<ITimeToProductionChartProps> = ({
|
|||||||
return {
|
return {
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: 'Time to production',
|
label: 'Median time to production',
|
||||||
data,
|
data,
|
||||||
borderColor: theme.palette.primary.light,
|
borderColor: theme.palette.primary.light,
|
||||||
backgroundColor: fillGradientPrimary,
|
backgroundColor: fillGradientPrimary,
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
import { medianTimeToProduction } from './median-time-to-production';
|
||||||
|
import type { ExecutiveSummarySchema } from 'openapi';
|
||||||
|
|
||||||
|
describe('medianTimeToProduction', () => {
|
||||||
|
it('calculates the median with a single date and an odd number of projects', () => {
|
||||||
|
const projectsData = [
|
||||||
|
{ date: '2023-03-21', timeToProduction: 10 },
|
||||||
|
{ date: '2023-03-21', timeToProduction: 20 },
|
||||||
|
{ date: '2023-03-21', timeToProduction: 30 },
|
||||||
|
] as unknown as ExecutiveSummarySchema['projectFlagTrends'];
|
||||||
|
const expected = { '2023-03-21': 20 };
|
||||||
|
expect(medianTimeToProduction(projectsData)).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calculates the median with a single date and an even number of projects', () => {
|
||||||
|
const projectsData = [
|
||||||
|
{ date: '2023-03-22', timeToProduction: 10 },
|
||||||
|
{ date: '2023-03-22', timeToProduction: 20 },
|
||||||
|
{ date: '2023-03-22', timeToProduction: 30 },
|
||||||
|
{ date: '2023-03-22', timeToProduction: 40 },
|
||||||
|
] as unknown as ExecutiveSummarySchema['projectFlagTrends'];
|
||||||
|
const expected = { '2023-03-22': 25 };
|
||||||
|
expect(medianTimeToProduction(projectsData)).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calculates the median for multiple dates with varying numbers of projects', () => {
|
||||||
|
const projectsData = [
|
||||||
|
{ date: '2023-03-23', timeToProduction: 5 },
|
||||||
|
{ date: '2023-03-23', timeToProduction: 15 },
|
||||||
|
{ date: '2023-03-24', timeToProduction: 10 },
|
||||||
|
{ date: '2023-03-24', timeToProduction: 20 },
|
||||||
|
{ date: '2023-03-24', timeToProduction: 30 },
|
||||||
|
{ date: '2023-03-25', timeToProduction: 25 },
|
||||||
|
] as unknown as ExecutiveSummarySchema['projectFlagTrends'];
|
||||||
|
const expected = {
|
||||||
|
'2023-03-23': 10,
|
||||||
|
'2023-03-24': 20,
|
||||||
|
'2023-03-25': 25,
|
||||||
|
};
|
||||||
|
expect(medianTimeToProduction(projectsData)).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,35 @@
|
|||||||
|
import type { ExecutiveSummarySchema } from 'openapi';
|
||||||
|
|
||||||
|
type GroupedDataByDate<T> = Record<string, T[]>;
|
||||||
|
|
||||||
|
type DateResult<T> = Record<string, T>;
|
||||||
|
|
||||||
|
export function medianTimeToProduction(
|
||||||
|
projectsData: ExecutiveSummarySchema['projectFlagTrends'],
|
||||||
|
): DateResult<number> {
|
||||||
|
const groupedData: GroupedDataByDate<number> = {};
|
||||||
|
projectsData.forEach((item) => {
|
||||||
|
const { date, timeToProduction } = item;
|
||||||
|
if (!groupedData[date]) {
|
||||||
|
groupedData[date] = [];
|
||||||
|
}
|
||||||
|
if (timeToProduction !== undefined) {
|
||||||
|
groupedData[date].push(timeToProduction);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calculate the median time to production for each date
|
||||||
|
const medianByDate: DateResult<number> = {};
|
||||||
|
Object.entries(groupedData).forEach(([date, times]) => {
|
||||||
|
const sortedTimes = times.sort((a, b) => a - b);
|
||||||
|
const midIndex = Math.floor(sortedTimes.length / 2);
|
||||||
|
|
||||||
|
const median =
|
||||||
|
sortedTimes.length % 2 === 0
|
||||||
|
? (sortedTimes[midIndex - 1] + sortedTimes[midIndex]) / 2
|
||||||
|
: sortedTimes[midIndex];
|
||||||
|
|
||||||
|
medianByDate[date] = median;
|
||||||
|
});
|
||||||
|
return medianByDate;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user