From 00b3cbaa8b6ef8dc07e2d69f567c78c9f1fbe686 Mon Sep 17 00:00:00 2001
From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com>
Date: Fri, 26 Jan 2024 09:03:12 +0100
Subject: [PATCH] Dashboard API hook (#5990)
Data fetching for dashboard
https://linear.app/unleash/issue/1-1969/dashboard-users-chart-api-hook
---
.../executiveDashboard/ExecutiveDashboard.tsx | 14 +-
.../FlagsChart/FlagsChartComponent.tsx | 42 +--
.../UsersChart/UsersChartComponent.tsx | 357 +++---------------
.../useExecutiveSummary.ts | 43 +++
4 files changed, 118 insertions(+), 338 deletions(-)
create mode 100644 frontend/src/hooks/api/getters/useExecutiveSummary/useExecutiveSummary.ts
diff --git a/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx b/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx
index 0b6a19f1d1..9baabd38aa 100644
--- a/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx
+++ b/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx
@@ -3,6 +3,7 @@ import { PageHeader } from 'component/common/PageHeader/PageHeader';
import { VFC } from 'react';
import { UsersChart } from './UsersChart/UsersChart';
import { FlagsChart } from './FlagsChart/FlagsChart';
+import { useExecutiveDashboard } from 'hooks/api/getters/useExecutiveSummary/useExecutiveSummary';
import { UserStats } from './UserStats/UserStats';
const StyledGrid = styled(Box)(({ theme }) => ({
@@ -13,6 +14,8 @@ const StyledGrid = styled(Box)(({ theme }) => ({
}));
export const ExecutiveDashboard: VFC = () => {
+ const { executiveDashboardData, loading, error } = useExecutiveDashboard();
+
return (
<>
({ paddingBottom: theme.spacing(4) })}>
@@ -22,14 +25,17 @@ export const ExecutiveDashboard: VFC = () => {
Dashboard
}
- // subtitle='Succesfully synchronized: 01 Sep 2023 - 07:05:07'
/>
- {/* Dashboard */}
-
-
+
+ Stats
+
>
);
diff --git a/frontend/src/component/executiveDashboard/FlagsChart/FlagsChartComponent.tsx b/frontend/src/component/executiveDashboard/FlagsChart/FlagsChartComponent.tsx
index a855b1e118..6dc058366a 100644
--- a/frontend/src/component/executiveDashboard/FlagsChart/FlagsChartComponent.tsx
+++ b/frontend/src/component/executiveDashboard/FlagsChart/FlagsChartComponent.tsx
@@ -1,4 +1,4 @@
-import { type VFC } from 'react';
+import { useMemo, type VFC } from 'react';
import {
Chart as ChartJS,
CategoryScale,
@@ -18,40 +18,31 @@ import {
type ILocationSettings,
} from 'hooks/useLocationSettings';
import { formatDateYMD } from 'utils/formatDate';
-import { mockData as usersMockData } from '../UsersChart/UsersChartComponent';
+import { ExecutiveSummarySchema } from 'openapi';
-type Data = {
- date: string | Date;
- total?: number;
- active?: number;
- archived?: number;
-}[];
-
-const mockData: Data = usersMockData.map((item) => ({
- ...item,
- archived: item.inactive,
-}));
-
-const createData = (theme: Theme) => ({
- labels: mockData.map((item) => item.date),
+const createData = (
+ theme: Theme,
+ flagsTrends: ExecutiveSummarySchema['flagsTrends'] = [],
+) => ({
+ labels: flagsTrends.map((item) => item.date),
datasets: [
{
label: 'Total flags',
- data: mockData.map((item) => item.total),
+ data: flagsTrends.map((item) => item.total),
borderColor: theme.palette.primary.main,
backgroundColor: theme.palette.primary.main,
fill: true,
},
{
label: 'Archived flags',
- data: mockData.map((item) => item.archived),
+ data: flagsTrends.map((item) => item.archived),
borderColor: theme.palette.error.main,
backgroundColor: theme.palette.error.main,
fill: true,
},
{
label: 'Active flags',
- data: mockData.map((item) => item.active),
+ data: flagsTrends.map((item) => item.active),
borderColor: theme.palette.success.main,
backgroundColor: theme.palette.success.main,
fill: true,
@@ -124,10 +115,19 @@ const createOptions = (theme: Theme, locationSettings: ILocationSettings) =>
},
}) as const;
-const FlagsChartComponent: VFC = () => {
+interface IFlagsChartComponentProps {
+ flagsTrends: ExecutiveSummarySchema['flagsTrends'];
+}
+
+const FlagsChartComponent: VFC = ({
+ flagsTrends,
+}) => {
const theme = useTheme();
const { locationSettings } = useLocationSettings();
- const data = createData(theme);
+ const data = useMemo(
+ () => createData(theme, flagsTrends),
+ [theme, flagsTrends],
+ );
const options = createOptions(theme, locationSettings);
return (
diff --git a/frontend/src/component/executiveDashboard/UsersChart/UsersChartComponent.tsx b/frontend/src/component/executiveDashboard/UsersChart/UsersChartComponent.tsx
index ad39e0f6cd..b2493621ad 100644
--- a/frontend/src/component/executiveDashboard/UsersChart/UsersChartComponent.tsx
+++ b/frontend/src/component/executiveDashboard/UsersChart/UsersChartComponent.tsx
@@ -1,4 +1,4 @@
-import { type VFC } from 'react';
+import { useMemo, type VFC } from 'react';
import {
Chart as ChartJS,
CategoryScale,
@@ -18,316 +18,7 @@ import {
type ILocationSettings,
} from 'hooks/useLocationSettings';
import { formatDateYMD } from 'utils/formatDate';
-
-type Data = {
- date: string | Date;
- total?: number;
- active?: number;
- inactive?: number;
-}[];
-
-export const mockData: Data = [
- {
- date: '2023-01-21',
- },
- {
- date: '2023-01-28',
- },
- {
- date: '2023-02-04',
- },
- {
- date: '2023-02-11',
- },
- {
- date: '2023-02-18',
- },
- {
- date: '2023-02-25',
- },
- {
- date: '2023-03-04',
- },
- {
- date: '2023-03-11',
- },
- {
- date: '2023-03-18',
- },
- {
- date: '2023-03-25',
- },
- {
- date: '2023-04-01',
- },
- {
- date: '2023-04-08',
- },
- {
- date: '2023-04-15',
- },
- {
- date: '2023-04-22',
- total: 43,
- active: 0,
- inactive: 0,
- },
- {
- date: '2023-04-29',
- total: 54,
- active: 54,
- inactive: 0,
- },
- {
- date: '2023-05-06',
- total: 63,
- active: 63,
- inactive: 0,
- },
- {
- date: '2023-05-13',
- total: 81,
- active: 81,
- inactive: 0,
- },
- {
- date: '2023-05-20',
- total: 80,
- active: 80,
- inactive: 0,
- },
- {
- date: '2023-05-27',
- total: 95,
- active: 95,
- inactive: 0,
- },
- {
- date: '2023-06-03',
- total: 108,
- active: 108,
- inactive: 0,
- },
- {
- date: '2023-06-10',
- total: 101,
- active: 101,
- inactive: 0,
- },
- {
- date: '2023-06-17',
- total: 104,
- active: 104,
- inactive: 0,
- },
- {
- date: '2023-06-24',
- total: 114,
- active: 114,
- inactive: 0,
- },
- {
- date: '2023-07-01',
- total: 108,
- active: 106,
- inactive: 2,
- },
- {
- date: '2023-07-08',
- total: 103,
- active: 102,
- inactive: 1,
- },
- {
- date: '2023-07-15',
- total: 106,
- active: 105,
- inactive: 1,
- },
- {
- date: '2023-07-22',
- total: 112,
- active: 106,
- inactive: 6,
- },
- {
- date: '2023-07-29',
- total: 113,
- active: 107,
- inactive: 6,
- },
- {
- date: '2023-08-05',
- total: 109,
- active: 98,
- inactive: 11,
- },
- {
- date: '2023-08-12',
- total: 110,
- active: 96,
- inactive: 14,
- },
- {
- date: '2023-08-19',
- total: 127,
- active: 111,
- inactive: 16,
- },
- {
- date: '2023-08-26',
- total: 140,
- active: 124,
- inactive: 16,
- },
- {
- date: '2023-09-02',
- total: 150,
- active: 130,
- inactive: 20,
- },
- {
- date: '2023-09-09',
- total: 168,
- active: 148,
- inactive: 20,
- },
- {
- date: '2023-09-16',
- total: 171,
- active: 154,
- inactive: 17,
- },
- {
- date: '2023-09-23',
- total: 190,
- active: 174,
- inactive: 16,
- },
- {
- date: '2023-09-30',
- total: 186,
- active: 169,
- inactive: 17,
- },
- {
- date: '2023-10-07',
- total: 188,
- active: 173,
- inactive: 15,
- },
- {
- date: '2023-10-14',
- total: 181,
- active: 166,
- inactive: 15,
- },
- {
- date: '2023-10-21',
- total: 192,
- active: 177,
- inactive: 15,
- },
- {
- date: '2023-10-28',
- total: 183,
- active: 164,
- inactive: 19,
- },
- {
- date: '2023-11-04',
- total: 200,
- active: 180,
- inactive: 20,
- },
- {
- date: '2023-11-11',
- total: 212,
- active: 189,
- inactive: 23,
- },
- {
- date: '2023-11-18',
- total: 204,
- active: 177,
- inactive: 27,
- },
- {
- date: '2023-11-25',
- total: 200,
- active: 173,
- inactive: 27,
- },
- {
- date: '2023-12-02',
- total: 200,
- active: 175,
- inactive: 25,
- },
- {
- date: '2023-12-09',
- total: 200,
- active: 176,
- inactive: 24,
- },
- {
- date: '2023-12-16',
- total: 215,
- active: 186,
- inactive: 29,
- },
- {
- date: '2023-12-23',
- total: 221,
- active: 195,
- inactive: 26,
- },
- {
- date: '2023-12-30',
- total: 214,
- active: 184,
- inactive: 30,
- },
- {
- date: '2024-01-06',
- total: 204,
- active: 173,
- inactive: 31,
- },
- {
- date: '2024-01-13',
- total: 215,
- active: 181,
- inactive: 34,
- },
-];
-
-const createData = (theme: Theme) => ({
- labels: mockData.map((item) => item.date),
- datasets: [
- {
- label: 'Total users',
- data: mockData.map((item) => item.total),
- borderColor: theme.palette.primary.main,
- backgroundColor: theme.palette.primary.main,
- fill: true,
- },
- {
- label: 'Inactive users',
- data: mockData.map((item) => item.inactive),
- borderColor: theme.palette.error.main,
- backgroundColor: theme.palette.error.main,
- fill: true,
- },
- {
- label: 'Active users',
- data: mockData.map((item) => item.active),
- borderColor: theme.palette.success.main,
- backgroundColor: theme.palette.success.main,
- fill: true,
- },
- ],
-});
+import { ExecutiveSummarySchema } from 'openapi';
const createOptions = (theme: Theme, locationSettings: ILocationSettings) =>
({
@@ -380,10 +71,50 @@ const createOptions = (theme: Theme, locationSettings: ILocationSettings) =>
},
}) as const;
-const UsersChartComponent: VFC = () => {
+interface IUsersChartComponentProps {
+ userTrends: ExecutiveSummarySchema['userTrends'];
+}
+
+const createData = (
+ theme: Theme,
+ userTrends: ExecutiveSummarySchema['userTrends'],
+) => ({
+ labels: userTrends.map((item) => item.date),
+ datasets: [
+ {
+ label: 'Total users',
+ data: userTrends.map((item) => item.total),
+ borderColor: theme.palette.primary.main,
+ backgroundColor: theme.palette.primary.main,
+ fill: true,
+ },
+ {
+ label: 'Inactive users',
+ data: userTrends.map((item) => item.inactive),
+ borderColor: theme.palette.error.main,
+ backgroundColor: theme.palette.error.main,
+ fill: true,
+ },
+ {
+ label: 'Active users',
+ data: userTrends.map((item) => item.active),
+ borderColor: theme.palette.success.main,
+ backgroundColor: theme.palette.success.main,
+ fill: true,
+ },
+ ],
+});
+
+const UsersChartComponent: VFC = ({
+ userTrends,
+}) => {
const theme = useTheme();
const { locationSettings } = useLocationSettings();
- const data = createData(theme);
+ const data = useMemo(
+ () => createData(theme, userTrends),
+ [theme, userTrends],
+ );
+
const options = createOptions(theme, locationSettings);
return (
diff --git a/frontend/src/hooks/api/getters/useExecutiveSummary/useExecutiveSummary.ts b/frontend/src/hooks/api/getters/useExecutiveSummary/useExecutiveSummary.ts
new file mode 100644
index 0000000000..74b9780d70
--- /dev/null
+++ b/frontend/src/hooks/api/getters/useExecutiveSummary/useExecutiveSummary.ts
@@ -0,0 +1,43 @@
+import useSWR, { mutate, SWRConfiguration } from 'swr';
+import { useCallback } from 'react';
+import { formatApiPath } from 'utils/formatPath';
+import handleErrorResponses from '../httpErrorResponseHandler';
+import { ExecutiveSummarySchema } from 'openapi';
+
+interface IUseExecutiveDashboardDataOutput {
+ executiveDashboardData: ExecutiveSummarySchema | undefined;
+ refetchExecutiveDashboard: () => void;
+ loading: boolean;
+ error?: Error;
+}
+
+export const useExecutiveDashboard = (
+ options?: SWRConfiguration,
+): IUseExecutiveDashboardDataOutput => {
+ const path = formatApiPath('api/admin/dashboard/executive');
+
+ const { data, error } = useSWR(
+ path,
+ fetchExecutiveDashboard,
+ options,
+ );
+
+ const refetchExecutiveDashboard = useCallback(() => {
+ mutate(path).catch(console.warn);
+ }, [path]);
+
+ return {
+ executiveDashboardData: data,
+ refetchExecutiveDashboard,
+ loading: !error && !data,
+ error,
+ };
+};
+
+const fetchExecutiveDashboard = (
+ path: string,
+): Promise => {
+ return fetch(path)
+ .then(handleErrorResponses('Executive Dashboard Data'))
+ .then((res) => res.json());
+};