mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-10 01:16:39 +02:00
fix: chart info naming bug fix (#6660)
Rename param Signed-off-by: andreas-unleash <andreas@getunleash.ai>
This commit is contained in:
parent
2f7580e6b1
commit
9233d4ca33
@ -41,7 +41,7 @@ interface IChartsProps {
|
|||||||
averageHealth?: string;
|
averageHealth?: string;
|
||||||
flagsPerUser?: string;
|
flagsPerUser?: string;
|
||||||
};
|
};
|
||||||
avgDaysToProduction: number;
|
medianTimeToProduction: number;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
projects: string[];
|
projects: string[];
|
||||||
}
|
}
|
||||||
@ -71,7 +71,7 @@ export const Charts: VFC<IChartsProps> = ({
|
|||||||
userTrends,
|
userTrends,
|
||||||
groupedProjectsData,
|
groupedProjectsData,
|
||||||
flagTrends,
|
flagTrends,
|
||||||
avgDaysToProduction,
|
medianTimeToProduction,
|
||||||
groupedMetricsData,
|
groupedMetricsData,
|
||||||
environmentTypeTrends,
|
environmentTypeTrends,
|
||||||
loading,
|
loading,
|
||||||
@ -165,8 +165,10 @@ export const Charts: VFC<IChartsProps> = ({
|
|||||||
isAggregate={showAllProjects}
|
isAggregate={showAllProjects}
|
||||||
/>
|
/>
|
||||||
</ChartWidget>
|
</ChartWidget>
|
||||||
<Widget {...chartInfo.averageTimeToProduction}>
|
<Widget {...chartInfo.medianTimeToProduction}>
|
||||||
<TimeToProduction daysToProduction={avgDaysToProduction} />
|
<TimeToProduction
|
||||||
|
daysToProduction={medianTimeToProduction}
|
||||||
|
/>
|
||||||
</Widget>
|
</Widget>
|
||||||
<ChartWidget
|
<ChartWidget
|
||||||
{...(showAllProjects
|
{...(showAllProjects
|
||||||
|
@ -50,7 +50,7 @@ export const chartInfo = {
|
|||||||
tooltip:
|
tooltip:
|
||||||
'How the overall health changes over time for the selected projects.',
|
'How the overall health changes over time for the selected projects.',
|
||||||
},
|
},
|
||||||
averageTimeToProduction: {
|
medianTimeToProduction: {
|
||||||
title: 'Median 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 the median 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.',
|
||||||
@ -63,7 +63,7 @@ export const chartInfo = {
|
|||||||
timeToProductionPerProject: {
|
timeToProductionPerProject: {
|
||||||
title: 'Time to production per project',
|
title: 'Time to production per project',
|
||||||
tooltip:
|
tooltip:
|
||||||
'How the median time to production changes over time for the selected projects.',
|
'How the average time to production changes over time for the selected projects.',
|
||||||
},
|
},
|
||||||
metrics: {
|
metrics: {
|
||||||
title: 'Flag evaluation metrics',
|
title: 'Flag evaluation metrics',
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
import { useAvgTimeToProduction } from './useAvgTimeToProduction';
|
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
|
||||||
|
|
||||||
describe('useAvgTimeToProduction', () => {
|
|
||||||
test('returns 0 when projectsData is empty', () => {
|
|
||||||
const projectsData = {};
|
|
||||||
const { result } = renderHook(() =>
|
|
||||||
useAvgTimeToProduction(projectsData),
|
|
||||||
);
|
|
||||||
expect(result.current).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('calculates average time to production based on the latest date correctly', () => {
|
|
||||||
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' },
|
|
||||||
],
|
|
||||||
} as any;
|
|
||||||
const { result } = renderHook(() =>
|
|
||||||
useAvgTimeToProduction(projectsData),
|
|
||||||
);
|
|
||||||
// Expect average of the latest timeToProductions (20 from project1 and 25 from project2)
|
|
||||||
expect(result.current).toBe(22.5);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('ignores projects without time to production data in their latest entries', () => {
|
|
||||||
const projectsData = {
|
|
||||||
project1: [
|
|
||||||
{ timeToProduction: 10, date: '2023-01-01' },
|
|
||||||
{ timeToProduction: 20, date: '2023-02-01' },
|
|
||||||
],
|
|
||||||
project2: [
|
|
||||||
{ date: '2023-01-15' },
|
|
||||||
{ timeToProduction: 25, date: '2023-01-10' },
|
|
||||||
],
|
|
||||||
} as any;
|
|
||||||
const { result } = renderHook(() =>
|
|
||||||
useAvgTimeToProduction(projectsData),
|
|
||||||
);
|
|
||||||
// Since project2's latest entry doesn't have timeToProduction, only project1's latest is considered
|
|
||||||
expect(result.current).toBe(20);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,48 +0,0 @@
|
|||||||
import { useMemo } from 'react';
|
|
||||||
import type {
|
|
||||||
ExecutiveSummarySchema,
|
|
||||||
ExecutiveSummarySchemaProjectFlagTrendsItem,
|
|
||||||
} from 'openapi';
|
|
||||||
import type { GroupedDataByProject } from './useGroupedProjectTrends';
|
|
||||||
|
|
||||||
const validTrend = (trend: ExecutiveSummarySchemaProjectFlagTrendsItem) =>
|
|
||||||
Boolean(trend) && Boolean(trend.timeToProduction);
|
|
||||||
|
|
||||||
export const useAvgTimeToProduction = (
|
|
||||||
projectsData: GroupedDataByProject<
|
|
||||||
ExecutiveSummarySchema['projectFlagTrends']
|
|
||||||
>,
|
|
||||||
) =>
|
|
||||||
useMemo(() => {
|
|
||||||
let totalProjects = Object.keys(projectsData).length;
|
|
||||||
|
|
||||||
if (totalProjects === 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalAvgTimeToProduction = Object.entries(projectsData).reduce(
|
|
||||||
(acc, [_, trends]) => {
|
|
||||||
const latestTrend = trends.reduce(
|
|
||||||
(latest, current) =>
|
|
||||||
new Date(latest.date) < new Date(current.date)
|
|
||||||
? current
|
|
||||||
: latest,
|
|
||||||
trends[0],
|
|
||||||
);
|
|
||||||
|
|
||||||
// If there's no valid latest trend, this project won't contribute to the average
|
|
||||||
if (!validTrend(latestTrend)) {
|
|
||||||
totalProjects--;
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeToProduction = latestTrend.timeToProduction || 0;
|
|
||||||
return acc + timeToProduction;
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
|
|
||||||
const overallAverage = totalAvgTimeToProduction / totalProjects;
|
|
||||||
|
|
||||||
return overallAverage;
|
|
||||||
}, [projectsData]);
|
|
@ -3,7 +3,7 @@ import type { ExecutiveSummarySchema } from 'openapi';
|
|||||||
import { useFilteredTrends } from './useFilteredTrends';
|
import { useFilteredTrends } from './useFilteredTrends';
|
||||||
import { useGroupedProjectTrends } from './useGroupedProjectTrends';
|
import { useGroupedProjectTrends } from './useGroupedProjectTrends';
|
||||||
import { useFilteredFlagsSummary } from './useFilteredFlagsSummary';
|
import { useFilteredFlagsSummary } from './useFilteredFlagsSummary';
|
||||||
import { useAvgTimeToProduction } from './useAvgTimeToProduction';
|
import { useMedianTimeToProduction } from './useMedianTimeToProduction';
|
||||||
|
|
||||||
export const useDashboardData = (
|
export const useDashboardData = (
|
||||||
executiveDashboardData: ExecutiveSummarySchema,
|
executiveDashboardData: ExecutiveSummarySchema,
|
||||||
@ -27,7 +27,8 @@ export const useDashboardData = (
|
|||||||
executiveDashboardData.users,
|
executiveDashboardData.users,
|
||||||
);
|
);
|
||||||
|
|
||||||
const avgDaysToProduction = useAvgTimeToProduction(groupedProjectsData);
|
const medianTimeToProduction =
|
||||||
|
useMedianTimeToProduction(groupedProjectsData);
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@ -39,7 +40,7 @@ export const useDashboardData = (
|
|||||||
users: executiveDashboardData.users,
|
users: executiveDashboardData.users,
|
||||||
environmentTypeTrends: executiveDashboardData.environmentTypeTrends,
|
environmentTypeTrends: executiveDashboardData.environmentTypeTrends,
|
||||||
summary,
|
summary,
|
||||||
avgDaysToProduction,
|
medianTimeToProduction,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
executiveDashboardData,
|
executiveDashboardData,
|
||||||
@ -49,7 +50,7 @@ export const useDashboardData = (
|
|||||||
metricsData,
|
metricsData,
|
||||||
groupedMetricsData,
|
groupedMetricsData,
|
||||||
summary,
|
summary,
|
||||||
avgDaysToProduction,
|
medianTimeToProduction,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,43 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import type {
|
||||||
|
ExecutiveSummarySchema,
|
||||||
|
ExecutiveSummarySchemaProjectFlagTrendsItem,
|
||||||
|
} from 'openapi';
|
||||||
|
import type { GroupedDataByProject } from './useGroupedProjectTrends';
|
||||||
|
|
||||||
|
const validTrend = (trend: ExecutiveSummarySchemaProjectFlagTrendsItem) =>
|
||||||
|
Boolean(trend) && Boolean(trend.timeToProduction);
|
||||||
|
|
||||||
|
export const useMedianTimeToProduction = (
|
||||||
|
projectsData: GroupedDataByProject<
|
||||||
|
ExecutiveSummarySchema['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