Skip to content

Commit

Permalink
feat: updates per environment type chart (#6449)
Browse files Browse the repository at this point in the history
Creates the updates per environment type chart.
(forgive the sample data)

Closes #
[1-2034](https://linear.app/unleash/issue/1-2034/widget-updates-per-environment-type-frontend)

<img width="1385" alt="Screenshot 2024-03-06 at 16 52 18"
src="https://github.com/Unleash/unleash/assets/104830839/b05479f8-de8b-4de7-98a3-a1285737db0d">

---------

Signed-off-by: andreas-unleash <andreas@getunleash.ai>
  • Loading branch information
andreas-unleash committed Mar 7, 2024
1 parent 4a8faac commit ec6c439
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 2 deletions.
14 changes: 13 additions & 1 deletion frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx
Expand Up @@ -25,6 +25,7 @@ import { FlagsProjectChart } from './componentsChart/FlagsProjectChart/FlagsProj
import { ProjectHealthChart } from './componentsChart/ProjectHealthChart/ProjectHealthChart';
import { MetricsSummaryChart } from './componentsChart/MetricsSummaryChart/MetricsSummaryChart';
import { UsersPerProjectChart } from './componentsChart/UsersPerProjectChart/UsersPerProjectChart';
import { UpdatesPerEnvironmentTypeChart } from './componentsChart/UpdatesPerEnvironmentTypeChart/UpdatesPerEnvironmentTypeChart';

const StyledGrid = styled(Box)(({ theme }) => ({
display: 'grid',
Expand Down Expand Up @@ -65,7 +66,8 @@ export const ExecutiveDashboard: VFC = () => {
executiveDashboardData.metricsSummaryTrends,
projects,
);
const { users } = executiveDashboardData;

const { users, environmentTypeTrends } = executiveDashboardData;

const summary = useFilteredFlagsSummary(projectsData);
const isOneProjectSelected = projects.length === 1;
Expand Down Expand Up @@ -194,6 +196,16 @@ export const ExecutiveDashboard: VFC = () => {
>
<MetricsSummaryChart metricsSummaryTrends={metricsData} />
</Widget>
<Widget
title='Updates per environment type'
tooltip='Summary of all configuration updates per environment type'
sx={{ mt: (theme) => theme.spacing(2) }}
>
<UpdatesPerEnvironmentTypeChart
environmentTypeTrends={environmentTypeTrends}
isLoading={loading}
/>
</Widget>
</>
);
};
@@ -1,7 +1,8 @@
import { FC, ReactNode } from 'react';
import { Paper, Typography, styled } from '@mui/material';
import { Paper, Typography, styled, SxProps } from '@mui/material';
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { Theme } from '@mui/material/styles/createTheme';

const StyledPaper = styled(Paper)(({ theme }) => ({
padding: theme.spacing(3),
Expand All @@ -13,6 +14,7 @@ const StyledPaper = styled(Paper)(({ theme }) => ({
export const Widget: FC<{
title: ReactNode;
tooltip?: ReactNode;
sx?: SxProps<Theme>;
}> = ({ title, children, tooltip, ...rest }) => (
<StyledPaper elevation={0} {...rest}>
<Typography
Expand Down
@@ -0,0 +1,73 @@
import { useMemo, type VFC } from 'react';
import 'chartjs-adapter-date-fns';
import { useTheme } from '@mui/material';
import {
ExecutiveSummarySchema,
ExecutiveSummarySchemaEnvironmentTypeTrendsItem,
} from 'openapi';
import { LineChart, NotEnoughData } from '../../components/LineChart/LineChart';
import { usePlaceholderData } from 'component/executiveDashboard/hooks/usePlaceholderData';
import { getProjectColor } from '../../executive-dashboard-utils';

interface IUpdatesPerEnvironmnetTypeChart {
environmentTypeTrends: ExecutiveSummarySchema['environmentTypeTrends'];
isLoading?: boolean;
}

const groupByDate = (
items: ExecutiveSummarySchemaEnvironmentTypeTrendsItem[],
): Record<string, ExecutiveSummarySchemaEnvironmentTypeTrendsItem[]> => {
if (!items) {
return {};
}

const grouped = items.reduce(
(acc, item) => {
const key = item.environmentType;

if (!acc[key]) {
acc[key] = [];
}

acc[key].push(item);

return acc;
},
{} as Record<string, ExecutiveSummarySchemaEnvironmentTypeTrendsItem[]>,
);

return grouped;
};

export const UpdatesPerEnvironmentTypeChart: VFC<
IUpdatesPerEnvironmnetTypeChart
> = ({ environmentTypeTrends, isLoading }) => {
const theme = useTheme();
const notEnoughData = environmentTypeTrends?.length < 2;
const placeholderData = usePlaceholderData({ fill: true, type: 'double' });

const data = useMemo(() => {
const grouped = groupByDate(environmentTypeTrends);
const labels = environmentTypeTrends?.map((item) => item.date);
const datasets = Object.entries(grouped).map(
([environmentType, trends]) => {
const color = getProjectColor(environmentType);
return {
label: environmentType,
data: trends.map((item) => item.totalUpdates),
borderColor: color,
backgroundColor: color,
fill: false,
};
},
);
return { labels, datasets };
}, [theme, environmentTypeTrends]);

return (
<LineChart
data={notEnoughData || isLoading ? placeholderData : data}
cover={notEnoughData ? <NotEnoughData /> : isLoading}
/>
);
};
@@ -0,0 +1,117 @@
import { type VFC } from 'react';
import { ExecutiveSummarySchemaEnvironmentTypeTrendsItem } from 'openapi';
import { Box, Divider, Paper, styled, Typography } from '@mui/material';
import { TooltipState } from '../../../components/LineChart/ChartTooltip/ChartTooltip';

const StyledTooltipItemContainer = styled(Paper)(({ theme }) => ({
padding: theme.spacing(2),
}));

const StyledItemHeader = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
gap: theme.spacing(2),
alignItems: 'center',
}));

const InfoLine = ({
iconChar,
title,
color,
}: {
iconChar: string;
title: string;
color: 'info' | 'success' | 'error';
}) => (
<Typography
variant='body2'
component='p'
sx={{
color: (theme) => theme.palette[color].main,
}}
>
<Typography component='span'>{iconChar}</Typography>
<strong>{title}</strong>
</Typography>
);

const InfoSummary = ({ data }: { data: { key: string; value: number }[] }) => (
<Typography variant={'body1'} component={'p'}>
<Box display={'flex'} flexDirection={'row'}>
{data.map(({ key, value }) => (
<div style={{ flex: 1, flexDirection: 'column' }}>
<div
style={{
flex: 1,
textAlign: 'center',
marginBottom: '4px',
}}
>
{key}
</div>
<div style={{ flex: 1, textAlign: 'center' }}>{value}</div>
</div>
))}
</Box>
</Typography>
);

export const UpdatesPerEnvironmentTypeChartTooltip: VFC<{
tooltip: TooltipState | null;
}> = ({ tooltip }) => {
const data = tooltip?.dataPoints.map((point) => {
return {
label: point.label,
title: point.dataset.label,
color: point.dataset.borderColor,
value: point.raw as ExecutiveSummarySchemaEnvironmentTypeTrendsItem,
};
});

const limitedData = data?.slice(0, 5);

return (
<Box
sx={(theme) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(2),
width: '300px',
})}
>
{limitedData?.map((point, index) => (
<StyledTooltipItemContainer
elevation={3}
key={`${point.title}-${index}`}
>
<StyledItemHeader>
<Typography variant='body2' component='span'>
<Typography
sx={{ color: point.color }}
component='span'
>
{'● '}
</Typography>
<strong>{point.title}</strong>
</Typography>
<Typography
variant='body2'
color='textSecondary'
component='span'
>
{point.label}
</Typography>
</StyledItemHeader>
<Divider
sx={(theme) => ({ margin: theme.spacing(1.5, 0) })}
/>
<InfoLine
iconChar={'● '}
title={`Total updates: ${point.value.totalUpdates}`}
color={'info'}
/>
</StyledTooltipItemContainer>
)) || null}
</Box>
);
};
Expand Up @@ -34,6 +34,7 @@ export const useExecutiveDashboard = (
flagTrends: [],
projectFlagTrends: [],
metricsSummaryTrends: [],
environmentTypeTrends: [],
},
refetchExecutiveDashboard,
loading: !error && !data,
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/openapi/models/executiveSummarySchema.ts
Expand Up @@ -3,6 +3,7 @@
* Do not edit manually.
* See `gen:api` script in package.json
*/
import type { ExecutiveSummarySchemaEnvironmentTypeTrendsItem } from './executiveSummarySchemaEnvironmentTypeTrendsItem';
import type { ExecutiveSummarySchemaFlags } from './executiveSummarySchemaFlags';
import type { ExecutiveSummarySchemaFlagTrendsItem } from './executiveSummarySchemaFlagTrendsItem';
import type { ExecutiveSummarySchemaMetricsSummaryTrendsItem } from './executiveSummarySchemaMetricsSummaryTrendsItem';
Expand All @@ -14,6 +15,8 @@ import type { ExecutiveSummarySchemaUserTrendsItem } from './executiveSummarySch
* Executive summary of Unleash usage
*/
export interface ExecutiveSummarySchema {
/** How updates per environment type changed over time */
environmentTypeTrends: ExecutiveSummarySchemaEnvironmentTypeTrendsItem[];
/** High level flag count statistics */
flags: ExecutiveSummarySchemaFlags;
/** How number of flags changed over time */
Expand Down
@@ -0,0 +1,16 @@
/**
* Generated by Orval
* Do not edit manually.
* See `gen:api` script in package.json
*/

export type ExecutiveSummarySchemaEnvironmentTypeTrendsItem = {
/** A UTC date when the stats were captured. Time is the very end of a given day. */
date: string;
/** Environment type the data belongs too */
environmentType: string;
/** Total number of times configuration has been updated in the environment type */
totalUpdates: number;
/** Year and week in a given year for which the stats were calculated */
week: string;
};
Expand Up @@ -5,6 +5,8 @@
*/

export type ExecutiveSummarySchemaMetricsSummaryTrendsItem = {
/** A UTC date when metrics summary was captured. Time is the very end of a given day. */
date: string;
/** Project id of the project the impressions summary belong to */
project: string;
/** Total number of applications the impression data belong to */
Expand Down
1 change: 1 addition & 0 deletions frontend/src/openapi/models/index.ts
Expand Up @@ -515,6 +515,7 @@ export * from './eventsSchema';
export * from './eventsSchemaVersion';
export * from './executiveSummarySchema';
export * from './executiveSummarySchemaFlagTrendsItem';
export * from './executiveSummarySchemaEnvironmentTypeTrendsItem';
export * from './executiveSummarySchemaFlags';
export * from './executiveSummarySchemaMetricsSummaryTrendsItem';
export * from './executiveSummarySchemaProjectFlagTrendsItem';
Expand Down

0 comments on commit ec6c439

Please sign in to comment.