From d7dbeca481fc78bc7b4bb1383437f4efc3799cad Mon Sep 17 00:00:00 2001 From: Fredrik Oseberg Date: Tue, 30 Jan 2024 13:59:14 +0100 Subject: [PATCH 1/9] feat: add project flags component --- .../executiveDashboard/ExecutiveDashboard.tsx | 6 + .../FlagsProjectChart/FlagsProjectChart.tsx | 3 + .../FlagsProjectChartComponent.tsx | 155 ++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChart.tsx create mode 100644 frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChartComponent.tsx diff --git a/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx b/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx index bc8e69da8f44..79143e5e3d1d 100644 --- a/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx +++ b/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx @@ -13,6 +13,7 @@ import { useExecutiveDashboard } from 'hooks/api/getters/useExecutiveSummary/use import { UserStats } from './UserStats/UserStats'; import { FlagStats } from './FlagStats/FlagStats'; import { Widget } from './Widget/Widget'; +import FlagsProjectChart from './FlagsProjectChart/FlagsProjectChartComponent'; const StyledGrid = styled(Box)(({ theme }) => ({ display: 'grid', @@ -71,6 +72,8 @@ export const ExecutiveDashboard: VFC = () => { const { gridTemplateColumns, chartSpan, userTrendsOrder, flagStatsOrder } = useDashboardGrid(); + console.log(executiveDashboardData); + return ( <> ({ paddingBottom: theme.spacing(4) })}> @@ -107,6 +110,9 @@ export const ExecutiveDashboard: VFC = () => { /> + ); }; diff --git a/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChart.tsx b/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChart.tsx new file mode 100644 index 000000000000..694e0687e801 --- /dev/null +++ b/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChart.tsx @@ -0,0 +1,3 @@ +import { lazy } from 'react'; + +export const FlagsChart = lazy(() => import('./FlagsChartComponent')); diff --git a/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChartComponent.tsx b/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChartComponent.tsx new file mode 100644 index 000000000000..8659bc4ac060 --- /dev/null +++ b/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChartComponent.tsx @@ -0,0 +1,155 @@ +import { useMemo, type VFC } from 'react'; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend, + TimeScale, +} from 'chart.js'; +import { Line } from 'react-chartjs-2'; +import 'chartjs-adapter-date-fns'; +import { Paper, Theme, Typography, useTheme } from '@mui/material'; +import { + useLocationSettings, + type ILocationSettings, +} from 'hooks/useLocationSettings'; +import { formatDateYMD } from 'utils/formatDate'; +import { ExecutiveSummarySchema } from 'openapi'; + +const getRandomColor = () => { + const letters = '0123456789ABCDEF'; + let color = '#'; + for (let i = 0; i < 6; i++) { + color += letters[Math.floor(Math.random() * 16)]; + } + return color; +}; + +const createData = ( + theme: Theme, + flagTrends: ExecutiveSummarySchema['projectFlagTrends'] = [], +) => { + // Group flag trends by project + const groupedFlagTrends = flagTrends.reduce((groups, item) => { + if (!groups[item.project]) { + groups[item.project] = []; + } + groups[item.project].push(item); + return groups; + }, {}); + + // Create a dataset for each project + const datasets = Object.entries(groupedFlagTrends).map( + ([project, trends], index) => { + const color = getRandomColor(); + return { + label: project, + data: trends.map((item) => item.total), + borderColor: color, + backgroundColor: color, + fill: true, + }; + }, + ); + + return { + labels: flagTrends.map((item) => item.date), + datasets, + }; +}; + +const createOptions = (theme: Theme, locationSettings: ILocationSettings) => + ({ + responsive: true, + plugins: { + legend: { + position: 'bottom', + }, + tooltip: { + callbacks: { + title: (tooltipItems: any) => { + const item = tooltipItems?.[0]; + const date = + item?.chart?.data?.labels?.[item.dataIndex]; + return date + ? formatDateYMD(date, locationSettings.locale) + : ''; + }, + }, + }, + }, + locale: locationSettings.locale, + interaction: { + intersect: false, + axis: 'x', + }, + color: theme.palette.text.secondary, + scales: { + y: { + type: 'linear', + grid: { + color: theme.palette.divider, + borderColor: theme.palette.divider, + }, + ticks: { color: theme.palette.text.secondary }, + }, + x: { + type: 'time', + time: { + unit: 'month', + }, + grid: { + color: theme.palette.divider, + borderColor: theme.palette.divider, + }, + ticks: { + color: theme.palette.text.secondary, + }, + }, + }, + }) as const; + +interface IFlagsChartComponentProps { + projectFlagTrends: ExecutiveSummarySchema['projectFlagTrends']; +} + +const FlagsProjectChart: VFC = ({ + projectFlagTrends, +}) => { + const theme = useTheme(); + const { locationSettings } = useLocationSettings(); + const data = useMemo( + () => createData(theme, projectFlagTrends), + [theme, projectFlagTrends], + ); + const options = createOptions(theme, locationSettings); + + return ( + ({ padding: theme.spacing(4) })}> + ({ marginBottom: theme.spacing(3) })} + > + Number of flags per project + + + + ); +}; + +ChartJS.register( + CategoryScale, + LinearScale, + PointElement, + LineElement, + TimeScale, + Title, + Tooltip, + Legend, +); + +export default FlagsProjectChart; From 27d4e7bca084203ac55ecc8fe2ac458ff9601084 Mon Sep 17 00:00:00 2001 From: Fredrik Oseberg Date: Tue, 30 Jan 2024 14:09:30 +0100 Subject: [PATCH 2/9] chore: orval types --- .../executiveDashboard/ExecutiveDashboard.tsx | 5 ++--- .../openapi/models/executiveSummarySchema.ts | 3 +++ .../executiveSummarySchemaFlagTrendsItem.ts | 2 +- ...utiveSummarySchemaProjectFlagTrendsItem.ts | 20 +++++++++++++++++++ frontend/src/openapi/models/index.ts | 1 + 5 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 frontend/src/openapi/models/executiveSummarySchemaProjectFlagTrendsItem.ts diff --git a/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx b/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx index 79143e5e3d1d..05f36cef1363 100644 --- a/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx +++ b/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx @@ -13,7 +13,7 @@ import { useExecutiveDashboard } from 'hooks/api/getters/useExecutiveSummary/use import { UserStats } from './UserStats/UserStats'; import { FlagStats } from './FlagStats/FlagStats'; import { Widget } from './Widget/Widget'; -import FlagsProjectChart from './FlagsProjectChart/FlagsProjectChartComponent'; +import { FlagsProjectChart } from './FlagsProjectChart/FlagsProjectChart'; const StyledGrid = styled(Box)(({ theme }) => ({ display: 'grid', @@ -72,8 +72,6 @@ export const ExecutiveDashboard: VFC = () => { const { gridTemplateColumns, chartSpan, userTrendsOrder, flagStatsOrder } = useDashboardGrid(); - console.log(executiveDashboardData); - return ( <> ({ paddingBottom: theme.spacing(4) })}> @@ -110,6 +108,7 @@ export const ExecutiveDashboard: VFC = () => { /> + diff --git a/frontend/src/openapi/models/executiveSummarySchema.ts b/frontend/src/openapi/models/executiveSummarySchema.ts index fec4155686d8..65dcad10bc54 100644 --- a/frontend/src/openapi/models/executiveSummarySchema.ts +++ b/frontend/src/openapi/models/executiveSummarySchema.ts @@ -5,6 +5,7 @@ */ import type { ExecutiveSummarySchemaFlags } from './executiveSummarySchemaFlags'; import type { ExecutiveSummarySchemaFlagTrendsItem } from './executiveSummarySchemaFlagTrendsItem'; +import type { ExecutiveSummarySchemaProjectFlagTrendsItem } from './executiveSummarySchemaProjectFlagTrendsItem'; import type { ExecutiveSummarySchemaUsers } from './executiveSummarySchemaUsers'; import type { ExecutiveSummarySchemaUserTrendsItem } from './executiveSummarySchemaUserTrendsItem'; @@ -16,6 +17,8 @@ export interface ExecutiveSummarySchema { flags: ExecutiveSummarySchemaFlags; /** How number of flags changed over time */ flagTrends: ExecutiveSummarySchemaFlagTrendsItem[]; + /** How number of flags per project changed over time */ + projectFlagTrends: ExecutiveSummarySchemaProjectFlagTrendsItem[]; /** High level user count statistics */ users: ExecutiveSummarySchemaUsers; /** How number of users changed over time */ diff --git a/frontend/src/openapi/models/executiveSummarySchemaFlagTrendsItem.ts b/frontend/src/openapi/models/executiveSummarySchemaFlagTrendsItem.ts index 72de2a78497b..a3e836004112 100644 --- a/frontend/src/openapi/models/executiveSummarySchemaFlagTrendsItem.ts +++ b/frontend/src/openapi/models/executiveSummarySchemaFlagTrendsItem.ts @@ -10,7 +10,7 @@ export type ExecutiveSummarySchemaFlagTrendsItem = { /** A UTC date when the stats were captured. Time is the very end of a given day. */ date: string; /** The number of time calculated potentially stale flags on a particular day */ - potentiallyStale?: number; + potentiallyStale: number; /** The number of user marked stale flags on a particular day */ stale: number; /** The number of all flags on a particular day */ diff --git a/frontend/src/openapi/models/executiveSummarySchemaProjectFlagTrendsItem.ts b/frontend/src/openapi/models/executiveSummarySchemaProjectFlagTrendsItem.ts new file mode 100644 index 000000000000..3d6b70a15d45 --- /dev/null +++ b/frontend/src/openapi/models/executiveSummarySchemaProjectFlagTrendsItem.ts @@ -0,0 +1,20 @@ +/** + * Generated by Orval + * Do not edit manually. + * See `gen:api` script in package.json + */ + +export type ExecutiveSummarySchemaProjectFlagTrendsItem = { + /** The number of active flags on a particular day */ + active: number; + /** A UTC date when the stats were captured. Time is the very end of a given day. */ + date: string; + /** The number of time calculated potentially stale flags on a particular day */ + potentiallyStale: number; + /** Project id of the project the flag trends belong to */ + project: string; + /** The number of user marked stale flags on a particular day */ + stale: number; + /** The number of all flags on a particular day */ + total: number; +}; diff --git a/frontend/src/openapi/models/index.ts b/frontend/src/openapi/models/index.ts index 3732a99d7e33..55b9ed1edf3a 100644 --- a/frontend/src/openapi/models/index.ts +++ b/frontend/src/openapi/models/index.ts @@ -497,6 +497,7 @@ export * from './eventsSchemaVersion'; export * from './executiveSummarySchema'; export * from './executiveSummarySchemaFlagTrendsItem'; export * from './executiveSummarySchemaFlags'; +export * from './executiveSummarySchemaProjectFlagTrendsItem'; export * from './executiveSummarySchemaUserTrendsItem'; export * from './executiveSummarySchemaUsers'; export * from './exportFeatures404'; From 76d02c006defecc4497e3d2d021a919b64281c1f Mon Sep 17 00:00:00 2001 From: Fredrik Oseberg Date: Tue, 30 Jan 2024 14:14:10 +0100 Subject: [PATCH 3/9] fix: export --- .../FlagsProjectChart/FlagsProjectChart.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChart.tsx b/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChart.tsx index 694e0687e801..0200b68fd58f 100644 --- a/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChart.tsx +++ b/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChart.tsx @@ -1,3 +1,5 @@ import { lazy } from 'react'; -export const FlagsChart = lazy(() => import('./FlagsChartComponent')); +export const FlagsProjectChart = lazy( + () => import('./FlagsProjectChartComponent'), +); From 457b724c0c9a53536f576cdeef77a874d558583c Mon Sep 17 00:00:00 2001 From: Fredrik Oseberg Date: Tue, 30 Jan 2024 14:53:10 +0100 Subject: [PATCH 4/9] fix: regen --- .../FlagsProjectChart/FlagsProjectChartComponent.tsx | 2 -- .../models/executiveSummarySchemaProjectFlagTrendsItem.ts | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChartComponent.tsx b/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChartComponent.tsx index 8659bc4ac060..1ee178430f50 100644 --- a/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChartComponent.tsx +++ b/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChartComponent.tsx @@ -33,7 +33,6 @@ const createData = ( theme: Theme, flagTrends: ExecutiveSummarySchema['projectFlagTrends'] = [], ) => { - // Group flag trends by project const groupedFlagTrends = flagTrends.reduce((groups, item) => { if (!groups[item.project]) { groups[item.project] = []; @@ -42,7 +41,6 @@ const createData = ( return groups; }, {}); - // Create a dataset for each project const datasets = Object.entries(groupedFlagTrends).map( ([project, trends], index) => { const color = getRandomColor(); diff --git a/frontend/src/openapi/models/executiveSummarySchemaProjectFlagTrendsItem.ts b/frontend/src/openapi/models/executiveSummarySchemaProjectFlagTrendsItem.ts index 3d6b70a15d45..75fde6c2fe09 100644 --- a/frontend/src/openapi/models/executiveSummarySchemaProjectFlagTrendsItem.ts +++ b/frontend/src/openapi/models/executiveSummarySchemaProjectFlagTrendsItem.ts @@ -9,12 +9,16 @@ export type ExecutiveSummarySchemaProjectFlagTrendsItem = { active: number; /** A UTC date when the stats were captured. Time is the very end of a given day. */ date: string; + /** An indicator of the [project's health](https://docs.getunleash.io/reference/technical-debt#health-rating) on a scale from 0 to 100 */ + health?: number; /** The number of time calculated potentially stale flags on a particular day */ potentiallyStale: number; /** Project id of the project the flag trends belong to */ project: string; /** The number of user marked stale flags on a particular day */ stale: number; + /** The average time from when a feature was created to when it was enabled in the "production" environment during the current window */ + timeToProduction?: number; /** The number of all flags on a particular day */ total: number; }; From 477ee69ff0f6fd2be74e4977c8538aabc0a4bd71 Mon Sep 17 00:00:00 2001 From: Fredrik Oseberg Date: Tue, 30 Jan 2024 15:06:13 +0100 Subject: [PATCH 5/9] fix: types --- .../FlagsProjectChartComponent.tsx | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChartComponent.tsx b/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChartComponent.tsx index 1ee178430f50..569ba4248cbe 100644 --- a/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChartComponent.tsx +++ b/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChartComponent.tsx @@ -18,7 +18,10 @@ import { type ILocationSettings, } from 'hooks/useLocationSettings'; import { formatDateYMD } from 'utils/formatDate'; -import { ExecutiveSummarySchema } from 'openapi'; +import { + ExecutiveSummarySchema, + ExecutiveSummarySchemaProjectFlagTrendsItem, +} from 'openapi'; const getRandomColor = () => { const letters = '0123456789ABCDEF'; @@ -33,16 +36,27 @@ const createData = ( theme: Theme, flagTrends: ExecutiveSummarySchema['projectFlagTrends'] = [], ) => { - const groupedFlagTrends = flagTrends.reduce((groups, item) => { - if (!groups[item.project]) { - groups[item.project] = []; - } - groups[item.project].push(item); - return groups; - }, {}); + const groupedFlagTrends = + flagTrends.reduce < + Record( + ( + groups: Record< + string, + ExecutiveSummarySchemaProjectFlagTrendsItem[] + >, + item: ExecutiveSummarySchemaProjectFlagTrendsItem, + ) => { + if (!groups[item.project]) { + groups[item.project] = []; + } + groups[item.project].push(item); + return groups; + }, + {}, + ); const datasets = Object.entries(groupedFlagTrends).map( - ([project, trends], index) => { + ([project, trends]) => { const color = getRandomColor(); return { label: project, From 5d755f8bdf3f6b83ada7ff749403e8350c01a0fc Mon Sep 17 00:00:00 2001 From: Fredrik Oseberg Date: Tue, 30 Jan 2024 15:13:02 +0100 Subject: [PATCH 6/9] fix: types --- .../FlagsProjectChartComponent.tsx | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChartComponent.tsx b/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChartComponent.tsx index 569ba4248cbe..307cf935b8c7 100644 --- a/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChartComponent.tsx +++ b/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChartComponent.tsx @@ -36,24 +36,15 @@ const createData = ( theme: Theme, flagTrends: ExecutiveSummarySchema['projectFlagTrends'] = [], ) => { - const groupedFlagTrends = - flagTrends.reduce < - Record( - ( - groups: Record< - string, - ExecutiveSummarySchemaProjectFlagTrendsItem[] - >, - item: ExecutiveSummarySchemaProjectFlagTrendsItem, - ) => { - if (!groups[item.project]) { - groups[item.project] = []; - } - groups[item.project].push(item); - return groups; - }, - {}, - ); + const groupedFlagTrends = flagTrends.reduce< + Record + >((groups, item) => { + if (!groups[item.project]) { + groups[item.project] = []; + } + groups[item.project].push(item); + return groups; + }, {}); const datasets = Object.entries(groupedFlagTrends).map( ([project, trends]) => { From 87fea8ef0caee342129f1fe4eecff5c16d28783d Mon Sep 17 00:00:00 2001 From: Fredrik Oseberg Date: Tue, 30 Jan 2024 15:22:17 +0100 Subject: [PATCH 7/9] fix: default to utc --- .../executiveDashboard/FlagsChart/FlagsChartComponent.tsx | 2 +- .../FlagsProjectChart/FlagsProjectChartComponent.tsx | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/component/executiveDashboard/FlagsChart/FlagsChartComponent.tsx b/frontend/src/component/executiveDashboard/FlagsChart/FlagsChartComponent.tsx index e818ea5f0b50..409bc2c58346 100644 --- a/frontend/src/component/executiveDashboard/FlagsChart/FlagsChartComponent.tsx +++ b/frontend/src/component/executiveDashboard/FlagsChart/FlagsChartComponent.tsx @@ -64,7 +64,7 @@ const createOptions = (theme: Theme, locationSettings: ILocationSettings) => const date = item?.chart?.data?.labels?.[item.dataIndex]; return date - ? formatDateYMD(date, locationSettings.locale) + ? formatDateYMD(date, locationSettings.locale, 'UTC') : ''; }, }, diff --git a/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChartComponent.tsx b/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChartComponent.tsx index 307cf935b8c7..07b5424126cc 100644 --- a/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChartComponent.tsx +++ b/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChartComponent.tsx @@ -79,7 +79,11 @@ const createOptions = (theme: Theme, locationSettings: ILocationSettings) => const date = item?.chart?.data?.labels?.[item.dataIndex]; return date - ? formatDateYMD(date, locationSettings.locale) + ? formatDateYMD( + date, + locationSettings.locale, + 'UTC', + ) : ''; }, }, From 7639d13e10037bc09654bc1fb1d55ade79e3e824 Mon Sep 17 00:00:00 2001 From: Fredrik Oseberg Date: Tue, 30 Jan 2024 15:27:03 +0100 Subject: [PATCH 8/9] fix: biome --- .../executiveDashboard/FlagsChart/FlagsChartComponent.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/component/executiveDashboard/FlagsChart/FlagsChartComponent.tsx b/frontend/src/component/executiveDashboard/FlagsChart/FlagsChartComponent.tsx index 409bc2c58346..bcb52b0db937 100644 --- a/frontend/src/component/executiveDashboard/FlagsChart/FlagsChartComponent.tsx +++ b/frontend/src/component/executiveDashboard/FlagsChart/FlagsChartComponent.tsx @@ -64,7 +64,11 @@ const createOptions = (theme: Theme, locationSettings: ILocationSettings) => const date = item?.chart?.data?.labels?.[item.dataIndex]; return date - ? formatDateYMD(date, locationSettings.locale, 'UTC') + ? formatDateYMD( + date, + locationSettings.locale, + 'UTC', + ) : ''; }, }, From 04ca10d5922f27890a71276ce43a50c05ae73944 Mon Sep 17 00:00:00 2001 From: Fredrik Oseberg Date: Tue, 30 Jan 2024 20:41:54 +0100 Subject: [PATCH 9/9] fix: add projectFlagTrends --- .../hooks/api/getters/useExecutiveSummary/useExecutiveSummary.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/hooks/api/getters/useExecutiveSummary/useExecutiveSummary.ts b/frontend/src/hooks/api/getters/useExecutiveSummary/useExecutiveSummary.ts index 9a29d66071df..eda954d196d8 100644 --- a/frontend/src/hooks/api/getters/useExecutiveSummary/useExecutiveSummary.ts +++ b/frontend/src/hooks/api/getters/useExecutiveSummary/useExecutiveSummary.ts @@ -32,6 +32,7 @@ export const useExecutiveDashboard = ( flags: { total: 0 }, userTrends: [], flagTrends: [], + projectFlagTrends: [], }, refetchExecutiveDashboard, loading: !error && !data,