mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-31 00:16:47 +01:00
feat: updates per environment type chart (#6449)
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>
This commit is contained in:
parent
4a8faacbd8
commit
ec6c439c09
@ -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',
|
||||
@ -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;
|
||||
@ -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),
|
||||
@ -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
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -34,6 +34,7 @@ export const useExecutiveDashboard = (
|
||||
flagTrends: [],
|
||||
projectFlagTrends: [],
|
||||
metricsSummaryTrends: [],
|
||||
environmentTypeTrends: [],
|
||||
},
|
||||
refetchExecutiveDashboard,
|
||||
loading: !error && !data,
|
||||
|
@ -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';
|
||||
@ -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 */
|
||||
|
@ -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;
|
||||
};
|
@ -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 */
|
||||
|
@ -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';
|
||||
|
Loading…
Reference in New Issue
Block a user