diff --git a/frontend/src/component/project/Project/ProjectInsights/LeadTimeForChanges/LeadTimeForChanges.test.tsx b/frontend/src/component/project/Project/ProjectInsights/LeadTimeForChanges/LeadTimeForChanges.test.tsx new file mode 100644 index 0000000000..6fcad5f43f --- /dev/null +++ b/frontend/src/component/project/Project/ProjectInsights/LeadTimeForChanges/LeadTimeForChanges.test.tsx @@ -0,0 +1,41 @@ +import { screen } from '@testing-library/react'; +import { render } from 'utils/testRenderer'; +import { testServerRoute, testServerSetup } from 'utils/testServer'; +import type { ProjectDoraMetricsSchema } from 'openapi'; +import { LeadTimeForChanges } from './LeadTimeForChanges'; +import { Route, Routes } from 'react-router-dom'; + +const server = testServerSetup(); + +const setupApi = (outdatedSdks: ProjectDoraMetricsSchema) => { + testServerRoute(server, '/api/admin/projects/default/dora', outdatedSdks); +}; + +test('Show outdated SDKs and apps using them', async () => { + setupApi({ + features: [ + { + name: 'ABCD', + timeToProduction: 57, + }, + ], + projectAverage: 67, + }); + render( + + } + /> + , + { + route: '/projects/default', + }, + ); + + await screen.findByText('Lead time for changes (per release toggle)'); + await screen.findByText('ABCD'); + await screen.findByText('57 days'); + await screen.findByText('Low'); + await screen.findByText('10 days'); +}); diff --git a/frontend/src/component/project/Project/ProjectInsights/LeadTimeForChanges/LeadTimeForChanges.tsx b/frontend/src/component/project/Project/ProjectInsights/LeadTimeForChanges/LeadTimeForChanges.tsx new file mode 100644 index 0000000000..3512221cc9 --- /dev/null +++ b/frontend/src/component/project/Project/ProjectInsights/LeadTimeForChanges/LeadTimeForChanges.tsx @@ -0,0 +1,261 @@ +import { Box, styled, Tooltip, Typography, useMediaQuery } from '@mui/material'; +import { useProjectDoraMetrics } from 'hooks/api/getters/useProjectDoraMetrics/useProjectDoraMetrics'; +import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; +import { useMemo } from 'react'; +import { useTable, useGlobalFilter, useSortBy } from 'react-table'; +import { + Table, + SortableTableHeader, + TableBody, + TableCell, + TableRow, + TablePlaceholder, +} from 'component/common/Table'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { Badge } from 'component/common/Badge/Badge'; +import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns'; +import theme from 'themes/theme'; + +const Container = styled(Box)(({ theme }) => ({ + gridColumn: 'span 5', + backgroundColor: theme.palette.background.paper, + padding: theme.spacing(3), + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(2), + borderRadius: theme.shape.borderRadiusLarge, + overflowY: 'auto', + maxHeight: theme.spacing(100), +})); + +const resolveDoraMetrics = (input: number) => { + const ONE_MONTH = 30; + const ONE_WEEK = 7; + + if (input >= ONE_MONTH) { + return Low; + } + + if (input <= ONE_MONTH && input >= ONE_WEEK + 1) { + return Medium; + } + + if (input <= ONE_WEEK) { + return High; + } +}; + +export const LeadTimeForChanges = () => { + const projectId = useRequiredPathParam('projectId'); + + const { dora, loading } = useProjectDoraMetrics(projectId); + + const data = useMemo(() => { + if (loading) { + return Array(5).fill({ + name: 'Featurename', + timeToProduction: 'Data for production', + }); + } + + return dora.features; + }, [dora, loading]); + + const columns = useMemo( + () => [ + { + Header: 'Name', + accessor: 'name', + width: '40%', + Cell: ({ + row: { + original: { name }, + }, + }: any) => { + return ( + + {name} + + ); + }, + sortType: 'alphanumeric', + }, + { + Header: 'Time to production', + id: 'timetoproduction', + align: 'center', + Cell: ({ row: { original } }: any) => ( + + + {original.timeToProduction} days + + + ), + width: 200, + disableGlobalFilter: true, + disableSortBy: true, + }, + { + Header: `Deviation`, + id: 'deviation', + align: 'center', + Cell: ({ row: { original } }: any) => ( + + + {Math.round( + (dora.projectAverage + ? dora.projectAverage + : 0) - original.timeToProduction, + )}{' '} + days + + + ), + width: 300, + disableGlobalFilter: true, + disableSortBy: true, + }, + { + Header: 'DORA', + id: 'dora', + align: 'center', + Cell: ({ row: { original } }: any) => ( + + + {resolveDoraMetrics(original.timeToProduction)} + + + ), + width: 200, + disableGlobalFilter: true, + disableSortBy: true, + }, + ], + [JSON.stringify(dora.features), loading], + ); + + const initialState = useMemo( + () => ({ + sortBy: [ + { + id: 'name', + desc: false, + }, + ], + }), + [], + ); + + const isExtraSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); + + const { + getTableProps, + getTableBodyProps, + headerGroups, + rows, + prepareRow, + state: { globalFilter }, + setHiddenColumns, + } = useTable( + { + columns: columns as any[], + data, + initialState, + autoResetGlobalFilter: false, + autoResetSortBy: false, + disableSortRemove: true, + }, + useGlobalFilter, + useSortBy, + ); + + useConditionallyHiddenColumns( + [ + { + condition: isExtraSmallScreen, + columns: ['deviation'], + }, + ], + setHiddenColumns, + columns, + ); + + return ( + + + Lead time for changes (per release toggle) + + + + + {rows.map((row) => { + prepareRow(row); + return ( + + {row.cells.map((cell) => ( + + {cell.render('Cell')} + + ))} + + ); + })} + +
+ 0} + show={ + + No features with data found “ + {globalFilter} + ” + + } + /> + } + /> +
+ ); +}; diff --git a/frontend/src/component/project/Project/ProjectInsights/ProjectInsights.tsx b/frontend/src/component/project/Project/ProjectInsights/ProjectInsights.tsx index dd83b8beba..cf40a9b82e 100644 --- a/frontend/src/component/project/Project/ProjectInsights/ProjectInsights.tsx +++ b/frontend/src/component/project/Project/ProjectInsights/ProjectInsights.tsx @@ -1,4 +1,5 @@ import { Box, styled } from '@mui/material'; +import { LeadTimeForChanges } from './LeadTimeForChanges/LeadTimeForChanges'; const Grid = styled(Box)(({ theme }) => ({ display: 'grid', @@ -14,10 +15,6 @@ const Health = styled(Box)(({ theme }) => ({ gridColumn: 'span 5', })); -const LeadTime = styled(Box)(({ theme }) => ({ - gridColumn: 'span 5', -})); - const ToggleTypesUsed = styled(Box)(({ theme }) => ({ gridColumn: 'span 2', })); @@ -38,7 +35,7 @@ export const ProjectInsights = () => { flags Project Health - Lead time + Toggle types used Project members Change Requests