mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-31 00:16:47 +01:00
fix: make TTP stat show last week calculation (#6766)
Currently the median time to production stat is showing the aggregated median across all dates. This pr changes the calculation to only use the last week summary like the rest of the stat widgets. <img width="1665" alt="Screenshot 2024-04-03 at 11 25 31" src="https://github.com/Unleash/unleash/assets/104830839/c6869b48-99bd-4f5b-a25e-7e0e3a2dc9ef"> --------- Signed-off-by: andreas-unleash <andreas@getunleash.ai>
This commit is contained in:
parent
fe6aaf7739
commit
717e541003
@ -40,8 +40,8 @@ interface IChartsProps {
|
||||
averageUsers: number;
|
||||
averageHealth?: string;
|
||||
flagsPerUser?: string;
|
||||
medianTimeToProduction?: number;
|
||||
};
|
||||
medianTimeToProduction: number;
|
||||
loading: boolean;
|
||||
projects: string[];
|
||||
}
|
||||
@ -71,7 +71,6 @@ export const InsightsCharts: VFC<IChartsProps> = ({
|
||||
userTrends,
|
||||
groupedProjectsData,
|
||||
flagTrends,
|
||||
medianTimeToProduction,
|
||||
groupedMetricsData,
|
||||
environmentTypeTrends,
|
||||
loading,
|
||||
@ -167,7 +166,7 @@ export const InsightsCharts: VFC<IChartsProps> = ({
|
||||
</ChartWidget>
|
||||
<Widget {...chartInfo.medianTimeToProduction}>
|
||||
<TimeToProduction
|
||||
daysToProduction={medianTimeToProduction}
|
||||
daysToProduction={summary.medianTimeToProduction}
|
||||
/>
|
||||
</Widget>
|
||||
<ChartWidget
|
||||
|
@ -16,6 +16,7 @@ describe('useFilteredFlagTrends', () => {
|
||||
potentiallyStale: 0,
|
||||
users: 1,
|
||||
date: '',
|
||||
timeToProduction: 4,
|
||||
},
|
||||
{
|
||||
week: '2024-01',
|
||||
@ -26,6 +27,7 @@ describe('useFilteredFlagTrends', () => {
|
||||
potentiallyStale: 0,
|
||||
users: 2,
|
||||
date: '',
|
||||
timeToProduction: 5,
|
||||
},
|
||||
{
|
||||
week: '2024-02',
|
||||
@ -36,6 +38,7 @@ describe('useFilteredFlagTrends', () => {
|
||||
potentiallyStale: 1,
|
||||
users: 1,
|
||||
date: '',
|
||||
timeToProduction: 4,
|
||||
},
|
||||
{
|
||||
week: '2024-02',
|
||||
@ -46,6 +49,7 @@ describe('useFilteredFlagTrends', () => {
|
||||
potentiallyStale: 0,
|
||||
users: 3,
|
||||
date: '',
|
||||
timeToProduction: 2,
|
||||
},
|
||||
],
|
||||
{ total: 1 } as unknown as InstanceInsightsSchemaUsers,
|
||||
@ -60,6 +64,7 @@ describe('useFilteredFlagTrends', () => {
|
||||
averageUsers: 2,
|
||||
averageHealth: '79',
|
||||
flagsPerUser: '14.00',
|
||||
medianTimeToProduction: 3,
|
||||
});
|
||||
});
|
||||
|
||||
@ -76,6 +81,7 @@ describe('useFilteredFlagTrends', () => {
|
||||
potentiallyStale: 0,
|
||||
users: 0,
|
||||
date: '',
|
||||
timeToProduction: 4,
|
||||
},
|
||||
],
|
||||
{ total: 1 } as unknown as InstanceInsightsSchemaUsers,
|
||||
@ -90,6 +96,7 @@ describe('useFilteredFlagTrends', () => {
|
||||
averageUsers: 0,
|
||||
averageHealth: '100',
|
||||
flagsPerUser: '5.00',
|
||||
medianTimeToProduction: 4,
|
||||
});
|
||||
});
|
||||
|
||||
@ -106,6 +113,7 @@ describe('useFilteredFlagTrends', () => {
|
||||
potentiallyStale: 0,
|
||||
users: 0,
|
||||
date: '',
|
||||
timeToProduction: 2,
|
||||
},
|
||||
{
|
||||
week: '2024-01',
|
||||
@ -116,6 +124,7 @@ describe('useFilteredFlagTrends', () => {
|
||||
potentiallyStale: 0,
|
||||
users: 3,
|
||||
date: '',
|
||||
timeToProduction: 5,
|
||||
},
|
||||
],
|
||||
{ total: 1 } as unknown as InstanceInsightsSchemaUsers,
|
||||
@ -130,6 +139,7 @@ describe('useFilteredFlagTrends', () => {
|
||||
averageUsers: 1.5,
|
||||
averageHealth: '100',
|
||||
flagsPerUser: '10.00',
|
||||
medianTimeToProduction: 3.5,
|
||||
});
|
||||
});
|
||||
|
||||
@ -146,6 +156,7 @@ describe('useFilteredFlagTrends', () => {
|
||||
potentiallyStale: 0,
|
||||
users: 0,
|
||||
date: '',
|
||||
timeToProduction: 0,
|
||||
},
|
||||
],
|
||||
{ total: 1 } as unknown as InstanceInsightsSchemaUsers,
|
||||
@ -160,6 +171,7 @@ describe('useFilteredFlagTrends', () => {
|
||||
averageUsers: 0,
|
||||
averageHealth: undefined,
|
||||
flagsPerUser: '0.00',
|
||||
medianTimeToProduction: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -4,6 +4,10 @@ import type {
|
||||
InstanceInsightsSchemaUsers,
|
||||
} from 'openapi';
|
||||
|
||||
const validTimeToProduction = (
|
||||
item: InstanceInsightsSchemaProjectFlagTrendsItem,
|
||||
) => Boolean(item) && typeof item.timeToProduction === 'number';
|
||||
|
||||
// NOTE: should we move project filtering to the backend?
|
||||
export const useFilteredFlagsSummary = (
|
||||
filteredProjectFlagTrends: InstanceInsightsSchemaProjectFlagTrendsItem[],
|
||||
@ -42,6 +46,20 @@ export const useFilteredFlagsSummary = (
|
||||
},
|
||||
);
|
||||
|
||||
const timesToProduction: number[] = lastWeekSummary
|
||||
.filter(validTimeToProduction)
|
||||
.map((item) => item.timeToProduction!); // Non-null assertion is safe due to filter
|
||||
|
||||
// Calculate median timeToProduction for lastWeekSummary
|
||||
timesToProduction.sort((a, b) => a - b);
|
||||
const midIndex = Math.floor(timesToProduction.length / 2);
|
||||
const medianTimeToProduction =
|
||||
timesToProduction.length % 2 === 0
|
||||
? (timesToProduction[midIndex - 1] +
|
||||
timesToProduction[midIndex]) /
|
||||
2
|
||||
: timesToProduction[midIndex];
|
||||
|
||||
const flagsPerUserCalculation = sum.total / users.total;
|
||||
const flagsPerUser = Number.isNaN(flagsPerUserCalculation)
|
||||
? 'N/A'
|
||||
@ -54,5 +72,6 @@ export const useFilteredFlagsSummary = (
|
||||
averageHealth: sum.total
|
||||
? ((sum.active / (sum.total || 1)) * 100).toFixed(0)
|
||||
: undefined,
|
||||
medianTimeToProduction,
|
||||
};
|
||||
}, [filteredProjectFlagTrends]);
|
||||
|
@ -3,7 +3,6 @@ import type { InstanceInsightsSchema } from 'openapi';
|
||||
import { useFilteredTrends } from './useFilteredTrends';
|
||||
import { useGroupedProjectTrends } from './useGroupedProjectTrends';
|
||||
import { useFilteredFlagsSummary } from './useFilteredFlagsSummary';
|
||||
import { useMedianTimeToProduction } from './useMedianTimeToProduction';
|
||||
|
||||
export const useInsightsData = (
|
||||
executiveDashboardData: InstanceInsightsSchema,
|
||||
@ -27,9 +26,6 @@ export const useInsightsData = (
|
||||
executiveDashboardData.users,
|
||||
);
|
||||
|
||||
const medianTimeToProduction =
|
||||
useMedianTimeToProduction(groupedProjectsData);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
...executiveDashboardData,
|
||||
@ -40,7 +36,6 @@ export const useInsightsData = (
|
||||
users: executiveDashboardData.users,
|
||||
environmentTypeTrends: executiveDashboardData.environmentTypeTrends,
|
||||
summary,
|
||||
medianTimeToProduction,
|
||||
}),
|
||||
[
|
||||
executiveDashboardData,
|
||||
@ -50,7 +45,6 @@ export const useInsightsData = (
|
||||
metricsData,
|
||||
groupedMetricsData,
|
||||
summary,
|
||||
medianTimeToProduction,
|
||||
],
|
||||
);
|
||||
};
|
||||
|
@ -1,63 +0,0 @@
|
||||
import { useMedianTimeToProduction } from './useMedianTimeToProduction';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
|
||||
describe('useMedianTimeToProduction', () => {
|
||||
test('returns 0 when projectsData is empty', () => {
|
||||
const projectsData = {};
|
||||
const { result } = renderHook(() =>
|
||||
useMedianTimeToProduction(projectsData),
|
||||
);
|
||||
expect(result.current).toBe(0);
|
||||
});
|
||||
|
||||
test('calculates median time to production correctly with even number of projects', () => {
|
||||
const projectsData = {
|
||||
project1: [
|
||||
{ timeToProduction: 10, date: '2023-01-01' },
|
||||
{ timeToProduction: 20, date: '2023-02-01' },
|
||||
],
|
||||
project2: [
|
||||
{ timeToProduction: 15, date: '2023-01-15' },
|
||||
{ timeToProduction: 25, date: '2023-02-15' },
|
||||
],
|
||||
project3: [{ timeToProduction: 30, date: '2023-01-20' }],
|
||||
} as any;
|
||||
const { result } = renderHook(() =>
|
||||
useMedianTimeToProduction(projectsData),
|
||||
);
|
||||
// With sorted timeToProductions [10, 15, 20, 25, 30], median is the middle value, 20.
|
||||
expect(result.current).toBe(20);
|
||||
});
|
||||
|
||||
test('calculates median time to production correctly with odd number of projects', () => {
|
||||
const projectsData = {
|
||||
project1: [
|
||||
{ timeToProduction: 10, date: '2023-01-01' },
|
||||
{ timeToProduction: 20, date: '2023-02-01' },
|
||||
],
|
||||
project2: [{ timeToProduction: 15, date: '2023-01-15' }],
|
||||
} as any;
|
||||
const { result } = renderHook(() =>
|
||||
useMedianTimeToProduction(projectsData),
|
||||
);
|
||||
// With sorted timeToProductions [10, 15, 20], median is the middle value, 15.
|
||||
expect(result.current).toBe(15);
|
||||
});
|
||||
|
||||
test('correctly handles all valid time to production values', () => {
|
||||
const projectsData = {
|
||||
project1: [{ timeToProduction: 10, date: '2023-01-01' }],
|
||||
project2: [{ timeToProduction: 25, date: '2023-01-10' }],
|
||||
project3: [{ date: '2023-01-15' }],
|
||||
project4: [
|
||||
{ timeToProduction: 30, date: '2023-02-01' },
|
||||
{ timeToProduction: 20, date: '2023-02-02' },
|
||||
],
|
||||
} as any;
|
||||
const { result } = renderHook(() =>
|
||||
useMedianTimeToProduction(projectsData),
|
||||
);
|
||||
// With sorted timeToProductions [10, 20, 25, 30], the median is the average of 20 and 25, i.e., 22.5.
|
||||
expect(result.current).toBe(22.5);
|
||||
});
|
||||
});
|
@ -1,43 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
import type {
|
||||
InstanceInsightsSchema,
|
||||
InstanceInsightsSchemaProjectFlagTrendsItem,
|
||||
} from 'openapi';
|
||||
import type { GroupedDataByProject } from './useGroupedProjectTrends';
|
||||
|
||||
const validTrend = (trend: InstanceInsightsSchemaProjectFlagTrendsItem) =>
|
||||
Boolean(trend) && Boolean(trend.timeToProduction);
|
||||
|
||||
export const useMedianTimeToProduction = (
|
||||
projectsData: GroupedDataByProject<
|
||||
InstanceInsightsSchema['projectFlagTrends']
|
||||
>,
|
||||
) =>
|
||||
useMemo(() => {
|
||||
const timesToProduction: number[] = [];
|
||||
|
||||
Object.values(projectsData).forEach((trends) => {
|
||||
trends.forEach((trend) => {
|
||||
if (validTrend(trend)) {
|
||||
timesToProduction.push(trend.timeToProduction!);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (timesToProduction.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
timesToProduction.sort((a, b) => a - b);
|
||||
|
||||
const midIndex = Math.floor(timesToProduction.length / 2);
|
||||
|
||||
const median =
|
||||
timesToProduction.length % 2 === 0
|
||||
? (timesToProduction[midIndex - 1] +
|
||||
timesToProduction[midIndex]) /
|
||||
2
|
||||
: timesToProduction[midIndex];
|
||||
|
||||
return median;
|
||||
}, [projectsData]);
|
Loading…
Reference in New Issue
Block a user