mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +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