mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +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