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
+
-
+
);
};