From ec6c439c09491d7915bbd2342b3280b506667421 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Thu, 7 Mar 2024 11:00:18 +0200 Subject: [PATCH] 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) Screenshot 2024-03-06 at 16 52 18 --------- Signed-off-by: andreas-unleash --- .../executiveDashboard/ExecutiveDashboard.tsx | 14 ++- .../components/Widget/Widget.tsx | 4 +- .../UpdatesPerEnvironmentTypeChart.tsx | 73 +++++++++++ .../UpdatesPerEnvironmentTypeChartTooltip.tsx | 117 ++++++++++++++++++ .../useExecutiveSummary.ts | 1 + .../openapi/models/executiveSummarySchema.ts | 3 + ...eSummarySchemaEnvironmentTypeTrendsItem.ts | 16 +++ ...veSummarySchemaMetricsSummaryTrendsItem.ts | 2 + frontend/src/openapi/models/index.ts | 1 + 9 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 frontend/src/component/executiveDashboard/componentsChart/UpdatesPerEnvironmentTypeChart/UpdatesPerEnvironmentTypeChart.tsx create mode 100644 frontend/src/component/executiveDashboard/componentsChart/UpdatesPerEnvironmentTypeChart/UpdatesPerEnvironmentTypeChartTooltip/UpdatesPerEnvironmentTypeChartTooltip.tsx create mode 100644 frontend/src/openapi/models/executiveSummarySchemaEnvironmentTypeTrendsItem.ts diff --git a/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx b/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx index 1d801e41682..407ccf5805f 100644 --- a/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx +++ b/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx @@ -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 = () => { > + theme.spacing(2) }} + > + + ); }; diff --git a/frontend/src/component/executiveDashboard/components/Widget/Widget.tsx b/frontend/src/component/executiveDashboard/components/Widget/Widget.tsx index ca2c4b599c0..120ac49959a 100644 --- a/frontend/src/component/executiveDashboard/components/Widget/Widget.tsx +++ b/frontend/src/component/executiveDashboard/components/Widget/Widget.tsx @@ -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; }> = ({ title, children, tooltip, ...rest }) => ( => { + 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, + ); + + 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 ( + : isLoading} + /> + ); +}; diff --git a/frontend/src/component/executiveDashboard/componentsChart/UpdatesPerEnvironmentTypeChart/UpdatesPerEnvironmentTypeChartTooltip/UpdatesPerEnvironmentTypeChartTooltip.tsx b/frontend/src/component/executiveDashboard/componentsChart/UpdatesPerEnvironmentTypeChart/UpdatesPerEnvironmentTypeChartTooltip/UpdatesPerEnvironmentTypeChartTooltip.tsx new file mode 100644 index 00000000000..ae9d69c9269 --- /dev/null +++ b/frontend/src/component/executiveDashboard/componentsChart/UpdatesPerEnvironmentTypeChart/UpdatesPerEnvironmentTypeChartTooltip/UpdatesPerEnvironmentTypeChartTooltip.tsx @@ -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'; +}) => ( + theme.palette[color].main, + }} + > + {iconChar} + {title} + +); + +const InfoSummary = ({ data }: { data: { key: string; value: number }[] }) => ( + + + {data.map(({ key, value }) => ( +
+
+ {key} +
+
{value}
+
+ ))} +
+
+); + +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 ( + ({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(2), + width: '300px', + })} + > + {limitedData?.map((point, index) => ( + + + + + {'● '} + + {point.title} + + + {point.label} + + + ({ margin: theme.spacing(1.5, 0) })} + /> + + + )) || null} + + ); +}; diff --git a/frontend/src/hooks/api/getters/useExecutiveSummary/useExecutiveSummary.ts b/frontend/src/hooks/api/getters/useExecutiveSummary/useExecutiveSummary.ts index 4ad7312fcd0..ea5e753ea75 100644 --- a/frontend/src/hooks/api/getters/useExecutiveSummary/useExecutiveSummary.ts +++ b/frontend/src/hooks/api/getters/useExecutiveSummary/useExecutiveSummary.ts @@ -34,6 +34,7 @@ export const useExecutiveDashboard = ( flagTrends: [], projectFlagTrends: [], metricsSummaryTrends: [], + environmentTypeTrends: [], }, refetchExecutiveDashboard, loading: !error && !data, diff --git a/frontend/src/openapi/models/executiveSummarySchema.ts b/frontend/src/openapi/models/executiveSummarySchema.ts index 5c150fd147b..0f65daab6c2 100644 --- a/frontend/src/openapi/models/executiveSummarySchema.ts +++ b/frontend/src/openapi/models/executiveSummarySchema.ts @@ -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 */ diff --git a/frontend/src/openapi/models/executiveSummarySchemaEnvironmentTypeTrendsItem.ts b/frontend/src/openapi/models/executiveSummarySchemaEnvironmentTypeTrendsItem.ts new file mode 100644 index 00000000000..10fe5a53345 --- /dev/null +++ b/frontend/src/openapi/models/executiveSummarySchemaEnvironmentTypeTrendsItem.ts @@ -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; +}; diff --git a/frontend/src/openapi/models/executiveSummarySchemaMetricsSummaryTrendsItem.ts b/frontend/src/openapi/models/executiveSummarySchemaMetricsSummaryTrendsItem.ts index c7761643397..7dd31b91af9 100644 --- a/frontend/src/openapi/models/executiveSummarySchemaMetricsSummaryTrendsItem.ts +++ b/frontend/src/openapi/models/executiveSummarySchemaMetricsSummaryTrendsItem.ts @@ -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 */ diff --git a/frontend/src/openapi/models/index.ts b/frontend/src/openapi/models/index.ts index 70d33954a37..11b025f29f5 100644 --- a/frontend/src/openapi/models/index.ts +++ b/frontend/src/openapi/models/index.ts @@ -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';