diff --git a/frontend/src/component/project/Project/ProjectDoraMetrics/ProjectDoraMetrics.tsx b/frontend/src/component/project/Project/ProjectDoraMetrics/ProjectDoraMetrics.tsx index 9219075a1673..9d5d00db968a 100644 --- a/frontend/src/component/project/Project/ProjectDoraMetrics/ProjectDoraMetrics.tsx +++ b/frontend/src/component/project/Project/ProjectDoraMetrics/ProjectDoraMetrics.tsx @@ -35,6 +35,9 @@ const resolveDoraMetrics = (input: number) => { } }; +/** + * @Deprecated in favor of LeadTimeForChanges.tsx + */ export const ProjectDoraMetrics = () => { const projectId = useRequiredPathParam('projectId'); diff --git a/frontend/src/component/project/Project/ProjectInfo/FlagTypesWidget.tsx b/frontend/src/component/project/Project/ProjectInfo/FlagTypesWidget.tsx index 2400264b6309..43503eea63a6 100644 --- a/frontend/src/component/project/Project/ProjectInfo/FlagTypesWidget.tsx +++ b/frontend/src/component/project/Project/ProjectInfo/FlagTypesWidget.tsx @@ -52,7 +52,9 @@ const ToggleTypesRow = ({ type, Icon, count }: IToggleTypeRowProps) => { ); }; - +/** + * @Deprecated in favor of FlagTypesUsed.tsx + */ export const FlagTypesWidget = ({ featureTypeCounts, }: IFlagTypesWidgetProps) => { diff --git a/frontend/src/component/project/Project/ProjectInsights/FlagTypesUsed/FlagTypesUsed.test.tsx b/frontend/src/component/project/Project/ProjectInsights/FlagTypesUsed/FlagTypesUsed.test.tsx new file mode 100644 index 000000000000..d3c51ff4a34f --- /dev/null +++ b/frontend/src/component/project/Project/ProjectInsights/FlagTypesUsed/FlagTypesUsed.test.tsx @@ -0,0 +1,36 @@ +import { screen } from '@testing-library/react'; +import { render } from 'utils/testRenderer'; +import { testServerRoute, testServerSetup } from 'utils/testServer'; +import type { ProjectOverviewSchema } from 'openapi'; +import { Route, Routes } from 'react-router-dom'; +import { FlagTypesUsed } from './FlagTypesUsed'; + +const server = testServerSetup(); + +const setupApi = (overview: ProjectOverviewSchema) => { + testServerRoute(server, '/api/admin/projects/default/overview', overview); +}; + +test('Show outdated SDKs and apps using them', async () => { + setupApi({ + name: 'default', + version: 2, + featureTypeCounts: [ + { + type: 'release', + count: 57, + }, + ], + }); + render( + + } /> + , + { + route: '/projects/default', + }, + ); + + await screen.findByText('Release'); + await screen.findByText('57'); +}); diff --git a/frontend/src/component/project/Project/ProjectInsights/FlagTypesUsed/FlagTypesUsed.tsx b/frontend/src/component/project/Project/ProjectInsights/FlagTypesUsed/FlagTypesUsed.tsx new file mode 100644 index 000000000000..93ff0e2d581a --- /dev/null +++ b/frontend/src/component/project/Project/ProjectInsights/FlagTypesUsed/FlagTypesUsed.tsx @@ -0,0 +1,125 @@ +import { useMemo } from 'react'; +import { styled, type SvgIconTypeMap, Typography } from '@mui/material'; +import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons'; + +import type { OverridableComponent } from '@mui/material/OverridableComponent'; +import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; +import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview'; + +export const StyledProjectInfoWidgetContainer = styled('div')(({ theme }) => ({ + margin: '0', + [theme.breakpoints.down('md')]: { + display: 'flex', + flexDirection: 'column', + position: 'relative', + }, +})); + +export const StyledWidgetTitle = styled(Typography)(({ theme }) => ({ + marginBottom: theme.spacing(2.5), +})); + +export const StyledCount = styled('span')(({ theme }) => ({ + fontSize: theme.typography.h2.fontSize, + fontWeight: 'bold', + color: theme.palette.text.primary, +})); + +const StyledTypeCount = styled(StyledCount)(({ theme }) => ({ + marginLeft: 'auto', + fontWeight: theme.typography.fontWeightRegular, + color: theme.palette.text.secondary, +})); + +interface IFlagTypeRowProps { + type: string; + Icon: OverridableComponent; + count: number; +} + +const StyledParagraphGridRow = styled('div')(({ theme }) => ({ + display: 'flex', + gap: theme.spacing(1.5), + width: '100%', + margin: theme.spacing(1, 0), + fontSize: theme.fontSizes.smallBody, + color: theme.palette.text.secondary, + alignItems: 'center', + [theme.breakpoints.down('md')]: { + margin: 0, + }, +})); + +const FlagTypesRow = ({ type, Icon, count }: IFlagTypeRowProps) => { + const getTitleText = (str: string) => { + return str.charAt(0).toUpperCase() + str.slice(1).replace('-', ' '); + }; + + return ( + + +
{getTitleText(type)}
+ {count} +
+ ); +}; + +export const FlagTypesUsed = () => { + const projectId = useRequiredPathParam('projectId'); + const { project } = useProjectOverview(projectId); + + const { featureTypeCounts } = project; + + const featureTypeStats = useMemo(() => { + const release = + featureTypeCounts.find( + (featureType) => featureType.type === 'release', + )?.count || 0; + + const experiment = + featureTypeCounts.find( + (featureType) => featureType.type === 'experiment', + )?.count || 0; + + const operational = + featureTypeCounts.find( + (featureType) => featureType.type === 'operational', + )?.count || 0; + + const kill = + featureTypeCounts.find( + (featureType) => featureType.type === 'kill-switch', + )?.count || 0; + + const permission = + featureTypeCounts.find( + (featureType) => featureType.type === 'permission', + )?.count || 0; + + return { + release, + experiment, + operational, + 'kill-switch': kill, + permission, + }; + }, [featureTypeCounts]); + + return ( + + + Flag types used + + {Object.keys(featureTypeStats).map((type) => ( + + ))} + + ); +}; diff --git a/frontend/src/component/project/Project/ProjectInsights/LeadTimeForChanges/LeadTimeForChanges.test.tsx b/frontend/src/component/project/Project/ProjectInsights/LeadTimeForChanges/LeadTimeForChanges.test.tsx index 6fcad5f43f81..d1ef989cebc7 100644 --- a/frontend/src/component/project/Project/ProjectInsights/LeadTimeForChanges/LeadTimeForChanges.test.tsx +++ b/frontend/src/component/project/Project/ProjectInsights/LeadTimeForChanges/LeadTimeForChanges.test.tsx @@ -33,7 +33,7 @@ test('Show outdated SDKs and apps using them', async () => { }, ); - await screen.findByText('Lead time for changes (per release toggle)'); + await screen.findByText('Lead time for changes (per release flag)'); await screen.findByText('ABCD'); await screen.findByText('57 days'); await screen.findByText('Low'); diff --git a/frontend/src/component/project/Project/ProjectInsights/LeadTimeForChanges/LeadTimeForChanges.tsx b/frontend/src/component/project/Project/ProjectInsights/LeadTimeForChanges/LeadTimeForChanges.tsx index 0f1c60efc84e..6e15627510ee 100644 --- a/frontend/src/component/project/Project/ProjectInsights/LeadTimeForChanges/LeadTimeForChanges.tsx +++ b/frontend/src/component/project/Project/ProjectInsights/LeadTimeForChanges/LeadTimeForChanges.tsx @@ -17,15 +17,11 @@ import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColum import theme from 'themes/theme'; const Container = styled(Box)(({ theme }) => ({ - gridColumn: 'span 6', - 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), + maxHeight: theme.spacing(45), })); const resolveDoraMetrics = (input: number) => { @@ -96,7 +92,7 @@ export const LeadTimeForChanges = () => { align: 'center', Cell: ({ row: { original } }: any) => ( { ), - width: 200, + width: 220, disableGlobalFilter: true, disableSortBy: true, }, @@ -222,7 +218,7 @@ export const LeadTimeForChanges = () => { return ( - Lead time for changes (per release toggle) + Lead time for changes (per release flag) diff --git a/frontend/src/component/project/Project/ProjectInsights/ProjectInsights.tsx b/frontend/src/component/project/Project/ProjectInsights/ProjectInsights.tsx index aa7d30a6c7b6..1f8e917eaa0e 100644 --- a/frontend/src/component/project/Project/ProjectInsights/ProjectInsights.tsx +++ b/frontend/src/component/project/Project/ProjectInsights/ProjectInsights.tsx @@ -2,6 +2,7 @@ import { Box, styled } from '@mui/material'; import { ChangeRequests } from './ChangeRequests/ChangeRequests'; import { LeadTimeForChanges } from './LeadTimeForChanges/LeadTimeForChanges'; import { ProjectHealth } from './ProjectHealth/ProjectHealth'; +import { FlagTypesUsed } from './FlagTypesUsed/FlagTypesUsed'; const Container = styled(Box)(({ theme }) => ({ backgroundColor: theme.palette.background.paper, @@ -15,42 +16,42 @@ const Grid = styled(Box)(({ theme }) => ({ gridTemplateColumns: 'repeat(10, 1fr)', })); -const Overview = styled(Box)(({ theme }) => ({ +const FullWidthContainer = styled(Container)(() => ({ gridColumn: '1 / -1', })); -const HealthCard = styled(Container)(({ theme }) => ({ - gridColumn: 'span 4', +const WideContainer = styled(Container)(() => ({ + gridColumn: 'span 6', })); -const ToggleTypesUsedCard = styled(Container)(({ theme }) => ({ - gridColumn: 'span 2', +const MediumWideContainer = styled(Container)(() => ({ + gridColumn: 'span 4', })); -const ProjectMembersCard = styled(Container)(({ theme }) => ({ +const NarrowContainer = styled(Container)(() => ({ gridColumn: 'span 2', })); -const ChangeRequestsCard = styled(Container)(({ theme }) => ({ - gridColumn: 'span 6', -})); - export const ProjectInsights = () => { return ( - + Total changes / avg time to production / feature flags /stale flags - - + + - - - Toggle types used - Project members - + + + + + + + + Project members + - + ); };