Skip to content

Commit

Permalink
feat: flag types used for project insights (#6607)
Browse files Browse the repository at this point in the history
  • Loading branch information
sjaanus authored Mar 19, 2024
1 parent 84005e2 commit 407b348
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ const resolveDoraMetrics = (input: number) => {
}
};

/**
* @Deprecated in favor of LeadTimeForChanges.tsx
*/
export const ProjectDoraMetrics = () => {
const projectId = useRequiredPathParam('projectId');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ const ToggleTypesRow = ({ type, Icon, count }: IToggleTypeRowProps) => {
</StyledParagraphGridRow>
);
};

/**
* @Deprecated in favor of FlagTypesUsed.tsx
*/
export const FlagTypesWidget = ({
featureTypeCounts,
}: IFlagTypesWidgetProps) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -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(
<Routes>
<Route path={'/projects/:projectId'} element={<FlagTypesUsed />} />
</Routes>,
{
route: '/projects/default',
},
);

await screen.findByText('Release');
await screen.findByText('57');
});
Original file line number Diff line number Diff line change
@@ -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<SvgIconTypeMap>;
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 (
<StyledParagraphGridRow data-loading>
<Icon fontSize='small' data-loading />
<div>{getTitleText(type)}</div>
<StyledTypeCount>{count}</StyledTypeCount>
</StyledParagraphGridRow>
);
};

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 (
<StyledProjectInfoWidgetContainer>
<StyledWidgetTitle variant='h3' data-loading>
Flag types used
</StyledWidgetTitle>
{Object.keys(featureTypeStats).map((type) => (
<FlagTypesRow
type={type}
key={type}
Icon={getFeatureTypeIcons(type)}
count={
featureTypeStats[type as keyof typeof featureTypeStats]
}
/>
))}
</StyledProjectInfoWidgetContainer>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -96,7 +92,7 @@ export const LeadTimeForChanges = () => {
align: 'center',
Cell: ({ row: { original } }: any) => (
<Tooltip
title='The time from the feature toggle of type release was created until it was turned on in a production environment'
title='The time from the feature flag of type release was created until it was turned on in a production environment'
arrow
>
<Box
Expand All @@ -110,7 +106,7 @@ export const LeadTimeForChanges = () => {
</Box>
</Tooltip>
),
width: 200,
width: 220,
disableGlobalFilter: true,
disableSortBy: true,
},
Expand Down Expand Up @@ -222,7 +218,7 @@ export const LeadTimeForChanges = () => {
return (
<Container>
<Typography variant='h3'>
Lead time for changes (per release toggle)
Lead time for changes (per release flag)
</Typography>
<Table {...getTableProps()}>
<SortableTableHeader headerGroups={headerGroups} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 (
<Grid>
<Overview>
<FullWidthContainer>
Total changes / avg time to production / feature flags /stale
flags
</Overview>
<HealthCard>
</FullWidthContainer>
<MediumWideContainer>
<ProjectHealth />
</HealthCard>
<LeadTimeForChanges />
<ToggleTypesUsedCard>Toggle types used</ToggleTypesUsedCard>
<ProjectMembersCard>Project members</ProjectMembersCard>
<ChangeRequestsCard>
</MediumWideContainer>
<WideContainer>
<LeadTimeForChanges />
</WideContainer>
<NarrowContainer>
<FlagTypesUsed />
</NarrowContainer>
<NarrowContainer>Project members</NarrowContainer>
<WideContainer>
<ChangeRequests />
</ChangeRequestsCard>
</WideContainer>
</Grid>
);
};

0 comments on commit 407b348

Please sign in to comment.