mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-20 00:08:02 +01:00
feat: flag types used for project insights (#6607)
![image](https://github.com/Unleash/unleash/assets/964450/b9eecb3e-eb02-4cc7-96eb-033c010734c0)
This commit is contained in:
parent
84005e27cc
commit
407b348a45
@ -35,6 +35,9 @@ const resolveDoraMetrics = (input: number) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @Deprecated in favor of LeadTimeForChanges.tsx
|
||||
*/
|
||||
export const ProjectDoraMetrics = () => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
|
||||
|
@ -52,7 +52,9 @@ const ToggleTypesRow = ({ type, Icon, count }: IToggleTypeRowProps) => {
|
||||
</StyledParagraphGridRow>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @Deprecated in favor of FlagTypesUsed.tsx
|
||||
*/
|
||||
export const FlagTypesWidget = ({
|
||||
featureTypeCounts,
|
||||
}: IFlagTypesWidgetProps) => {
|
||||
|
@ -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');
|
||||
});
|
@ -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>
|
||||
);
|
||||
};
|
@ -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');
|
||||
|
@ -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) => (
|
||||
<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
|
||||
@ -110,7 +106,7 @@ export const LeadTimeForChanges = () => {
|
||||
</Box>
|
||||
</Tooltip>
|
||||
),
|
||||
width: 200,
|
||||
width: 220,
|
||||
disableGlobalFilter: true,
|
||||
disableSortBy: true,
|
||||
},
|
||||
@ -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} />
|
||||
|
@ -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 }) => ({
|
||||
const WideContainer = styled(Container)(() => ({
|
||||
gridColumn: 'span 6',
|
||||
}));
|
||||
|
||||
const MediumWideContainer = styled(Container)(() => ({
|
||||
gridColumn: 'span 4',
|
||||
}));
|
||||
|
||||
const ToggleTypesUsedCard = styled(Container)(({ theme }) => ({
|
||||
const NarrowContainer = styled(Container)(() => ({
|
||||
gridColumn: 'span 2',
|
||||
}));
|
||||
|
||||
const ProjectMembersCard = styled(Container)(({ theme }) => ({
|
||||
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>
|
||||
</MediumWideContainer>
|
||||
<WideContainer>
|
||||
<LeadTimeForChanges />
|
||||
<ToggleTypesUsedCard>Toggle types used</ToggleTypesUsedCard>
|
||||
<ProjectMembersCard>Project members</ProjectMembersCard>
|
||||
<ChangeRequestsCard>
|
||||
</WideContainer>
|
||||
<NarrowContainer>
|
||||
<FlagTypesUsed />
|
||||
</NarrowContainer>
|
||||
<NarrowContainer>Project members</NarrowContainer>
|
||||
<WideContainer>
|
||||
<ChangeRequests />
|
||||
</ChangeRequestsCard>
|
||||
</WideContainer>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user