mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-09 00:18:00 +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 { ProjectHealthChart } from './componentsChart/ProjectHealthChart/ProjectHealthChart';
|
||||||
import { MetricsSummaryChart } from './componentsChart/MetricsSummaryChart/MetricsSummaryChart';
|
import { MetricsSummaryChart } from './componentsChart/MetricsSummaryChart/MetricsSummaryChart';
|
||||||
import { UsersPerProjectChart } from './componentsChart/UsersPerProjectChart/UsersPerProjectChart';
|
import { UsersPerProjectChart } from './componentsChart/UsersPerProjectChart/UsersPerProjectChart';
|
||||||
|
import { UpdatesPerEnvironmentTypeChart } from './componentsChart/UpdatesPerEnvironmentTypeChart/UpdatesPerEnvironmentTypeChart';
|
||||||
|
|
||||||
const StyledGrid = styled(Box)(({ theme }) => ({
|
const StyledGrid = styled(Box)(({ theme }) => ({
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
@ -65,7 +66,8 @@ export const ExecutiveDashboard: VFC = () => {
|
|||||||
executiveDashboardData.metricsSummaryTrends,
|
executiveDashboardData.metricsSummaryTrends,
|
||||||
projects,
|
projects,
|
||||||
);
|
);
|
||||||
const { users } = executiveDashboardData;
|
|
||||||
|
const { users, environmentTypeTrends } = executiveDashboardData;
|
||||||
|
|
||||||
const summary = useFilteredFlagsSummary(projectsData);
|
const summary = useFilteredFlagsSummary(projectsData);
|
||||||
const isOneProjectSelected = projects.length === 1;
|
const isOneProjectSelected = projects.length === 1;
|
||||||
@ -194,6 +196,16 @@ export const ExecutiveDashboard: VFC = () => {
|
|||||||
>
|
>
|
||||||
<MetricsSummaryChart metricsSummaryTrends={metricsData} />
|
<MetricsSummaryChart metricsSummaryTrends={metricsData} />
|
||||||
</Widget>
|
</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 { 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 { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { Theme } from '@mui/material/styles/createTheme';
|
||||||
|
|
||||||
const StyledPaper = styled(Paper)(({ theme }) => ({
|
const StyledPaper = styled(Paper)(({ theme }) => ({
|
||||||
padding: theme.spacing(3),
|
padding: theme.spacing(3),
|
||||||
@ -13,6 +14,7 @@ const StyledPaper = styled(Paper)(({ theme }) => ({
|
|||||||
export const Widget: FC<{
|
export const Widget: FC<{
|
||||||
title: ReactNode;
|
title: ReactNode;
|
||||||
tooltip?: ReactNode;
|
tooltip?: ReactNode;
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
}> = ({ title, children, tooltip, ...rest }) => (
|
}> = ({ title, children, tooltip, ...rest }) => (
|
||||||
<StyledPaper elevation={0} {...rest}>
|
<StyledPaper elevation={0} {...rest}>
|
||||||
<Typography
|
<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: [],
|
flagTrends: [],
|
||||||
projectFlagTrends: [],
|
projectFlagTrends: [],
|
||||||
metricsSummaryTrends: [],
|
metricsSummaryTrends: [],
|
||||||
|
environmentTypeTrends: [],
|
||||||
},
|
},
|
||||||
refetchExecutiveDashboard,
|
refetchExecutiveDashboard,
|
||||||
loading: !error && !data,
|
loading: !error && !data,
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
* Do not edit manually.
|
* Do not edit manually.
|
||||||
* See `gen:api` script in package.json
|
* See `gen:api` script in package.json
|
||||||
*/
|
*/
|
||||||
|
import type { ExecutiveSummarySchemaEnvironmentTypeTrendsItem } from './executiveSummarySchemaEnvironmentTypeTrendsItem';
|
||||||
import type { ExecutiveSummarySchemaFlags } from './executiveSummarySchemaFlags';
|
import type { ExecutiveSummarySchemaFlags } from './executiveSummarySchemaFlags';
|
||||||
import type { ExecutiveSummarySchemaFlagTrendsItem } from './executiveSummarySchemaFlagTrendsItem';
|
import type { ExecutiveSummarySchemaFlagTrendsItem } from './executiveSummarySchemaFlagTrendsItem';
|
||||||
import type { ExecutiveSummarySchemaMetricsSummaryTrendsItem } from './executiveSummarySchemaMetricsSummaryTrendsItem';
|
import type { ExecutiveSummarySchemaMetricsSummaryTrendsItem } from './executiveSummarySchemaMetricsSummaryTrendsItem';
|
||||||
@ -14,6 +15,8 @@ import type { ExecutiveSummarySchemaUserTrendsItem } from './executiveSummarySch
|
|||||||
* Executive summary of Unleash usage
|
* Executive summary of Unleash usage
|
||||||
*/
|
*/
|
||||||
export interface ExecutiveSummarySchema {
|
export interface ExecutiveSummarySchema {
|
||||||
|
/** How updates per environment type changed over time */
|
||||||
|
environmentTypeTrends: ExecutiveSummarySchemaEnvironmentTypeTrendsItem[];
|
||||||
/** High level flag count statistics */
|
/** High level flag count statistics */
|
||||||
flags: ExecutiveSummarySchemaFlags;
|
flags: ExecutiveSummarySchemaFlags;
|
||||||
/** How number of flags changed over time */
|
/** 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 = {
|
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 id of the project the impressions summary belong to */
|
||||||
project: string;
|
project: string;
|
||||||
/** Total number of applications the impression data belong to */
|
/** Total number of applications the impression data belong to */
|
||||||
|
@ -515,6 +515,7 @@ export * from './eventsSchema';
|
|||||||
export * from './eventsSchemaVersion';
|
export * from './eventsSchemaVersion';
|
||||||
export * from './executiveSummarySchema';
|
export * from './executiveSummarySchema';
|
||||||
export * from './executiveSummarySchemaFlagTrendsItem';
|
export * from './executiveSummarySchemaFlagTrendsItem';
|
||||||
|
export * from './executiveSummarySchemaEnvironmentTypeTrendsItem';
|
||||||
export * from './executiveSummarySchemaFlags';
|
export * from './executiveSummarySchemaFlags';
|
||||||
export * from './executiveSummarySchemaMetricsSummaryTrendsItem';
|
export * from './executiveSummarySchemaMetricsSummaryTrendsItem';
|
||||||
export * from './executiveSummarySchemaProjectFlagTrendsItem';
|
export * from './executiveSummarySchemaProjectFlagTrendsItem';
|
||||||
|
Loading…
Reference in New Issue
Block a user