mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-07 01:16:28 +02:00
Dashboard API hook (#5990)
Data fetching for dashboard https://linear.app/unleash/issue/1-1969/dashboard-users-chart-api-hook
This commit is contained in:
parent
9ac1c88bd4
commit
00b3cbaa8b
@ -3,6 +3,7 @@ import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
|||||||
import { VFC } from 'react';
|
import { VFC } from 'react';
|
||||||
import { UsersChart } from './UsersChart/UsersChart';
|
import { UsersChart } from './UsersChart/UsersChart';
|
||||||
import { FlagsChart } from './FlagsChart/FlagsChart';
|
import { FlagsChart } from './FlagsChart/FlagsChart';
|
||||||
|
import { useExecutiveDashboard } from 'hooks/api/getters/useExecutiveSummary/useExecutiveSummary';
|
||||||
import { UserStats } from './UserStats/UserStats';
|
import { UserStats } from './UserStats/UserStats';
|
||||||
|
|
||||||
const StyledGrid = styled(Box)(({ theme }) => ({
|
const StyledGrid = styled(Box)(({ theme }) => ({
|
||||||
@ -13,6 +14,8 @@ const StyledGrid = styled(Box)(({ theme }) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
export const ExecutiveDashboard: VFC = () => {
|
export const ExecutiveDashboard: VFC = () => {
|
||||||
|
const { executiveDashboardData, loading, error } = useExecutiveDashboard();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box sx={(theme) => ({ paddingBottom: theme.spacing(4) })}>
|
<Box sx={(theme) => ({ paddingBottom: theme.spacing(4) })}>
|
||||||
@ -22,14 +25,17 @@ export const ExecutiveDashboard: VFC = () => {
|
|||||||
Dashboard
|
Dashboard
|
||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
// subtitle='Succesfully synchronized: 01 Sep 2023 - 07:05:07'
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
{/* Dashboard */}
|
|
||||||
<StyledGrid>
|
<StyledGrid>
|
||||||
<UserStats />
|
<UserStats />
|
||||||
<UsersChart />
|
<UsersChart
|
||||||
<FlagsChart />
|
userTrends={executiveDashboardData?.userTrends ?? []}
|
||||||
|
/>
|
||||||
|
<Paper>Stats</Paper>
|
||||||
|
<FlagsChart
|
||||||
|
flagsTrends={executiveDashboardData?.flagsTrends ?? []}
|
||||||
|
/>
|
||||||
</StyledGrid>
|
</StyledGrid>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { type VFC } from 'react';
|
import { useMemo, type VFC } from 'react';
|
||||||
import {
|
import {
|
||||||
Chart as ChartJS,
|
Chart as ChartJS,
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
@ -18,40 +18,31 @@ import {
|
|||||||
type ILocationSettings,
|
type ILocationSettings,
|
||||||
} from 'hooks/useLocationSettings';
|
} from 'hooks/useLocationSettings';
|
||||||
import { formatDateYMD } from 'utils/formatDate';
|
import { formatDateYMD } from 'utils/formatDate';
|
||||||
import { mockData as usersMockData } from '../UsersChart/UsersChartComponent';
|
import { ExecutiveSummarySchema } from 'openapi';
|
||||||
|
|
||||||
type Data = {
|
const createData = (
|
||||||
date: string | Date;
|
theme: Theme,
|
||||||
total?: number;
|
flagsTrends: ExecutiveSummarySchema['flagsTrends'] = [],
|
||||||
active?: number;
|
) => ({
|
||||||
archived?: number;
|
labels: flagsTrends.map((item) => item.date),
|
||||||
}[];
|
|
||||||
|
|
||||||
const mockData: Data = usersMockData.map((item) => ({
|
|
||||||
...item,
|
|
||||||
archived: item.inactive,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const createData = (theme: Theme) => ({
|
|
||||||
labels: mockData.map((item) => item.date),
|
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: 'Total flags',
|
label: 'Total flags',
|
||||||
data: mockData.map((item) => item.total),
|
data: flagsTrends.map((item) => item.total),
|
||||||
borderColor: theme.palette.primary.main,
|
borderColor: theme.palette.primary.main,
|
||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor: theme.palette.primary.main,
|
||||||
fill: true,
|
fill: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Archived flags',
|
label: 'Archived flags',
|
||||||
data: mockData.map((item) => item.archived),
|
data: flagsTrends.map((item) => item.archived),
|
||||||
borderColor: theme.palette.error.main,
|
borderColor: theme.palette.error.main,
|
||||||
backgroundColor: theme.palette.error.main,
|
backgroundColor: theme.palette.error.main,
|
||||||
fill: true,
|
fill: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Active flags',
|
label: 'Active flags',
|
||||||
data: mockData.map((item) => item.active),
|
data: flagsTrends.map((item) => item.active),
|
||||||
borderColor: theme.palette.success.main,
|
borderColor: theme.palette.success.main,
|
||||||
backgroundColor: theme.palette.success.main,
|
backgroundColor: theme.palette.success.main,
|
||||||
fill: true,
|
fill: true,
|
||||||
@ -124,10 +115,19 @@ const createOptions = (theme: Theme, locationSettings: ILocationSettings) =>
|
|||||||
},
|
},
|
||||||
}) as const;
|
}) as const;
|
||||||
|
|
||||||
const FlagsChartComponent: VFC = () => {
|
interface IFlagsChartComponentProps {
|
||||||
|
flagsTrends: ExecutiveSummarySchema['flagsTrends'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const FlagsChartComponent: VFC<IFlagsChartComponentProps> = ({
|
||||||
|
flagsTrends,
|
||||||
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { locationSettings } = useLocationSettings();
|
const { locationSettings } = useLocationSettings();
|
||||||
const data = createData(theme);
|
const data = useMemo(
|
||||||
|
() => createData(theme, flagsTrends),
|
||||||
|
[theme, flagsTrends],
|
||||||
|
);
|
||||||
const options = createOptions(theme, locationSettings);
|
const options = createOptions(theme, locationSettings);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { type VFC } from 'react';
|
import { useMemo, type VFC } from 'react';
|
||||||
import {
|
import {
|
||||||
Chart as ChartJS,
|
Chart as ChartJS,
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
@ -18,316 +18,7 @@ import {
|
|||||||
type ILocationSettings,
|
type ILocationSettings,
|
||||||
} from 'hooks/useLocationSettings';
|
} from 'hooks/useLocationSettings';
|
||||||
import { formatDateYMD } from 'utils/formatDate';
|
import { formatDateYMD } from 'utils/formatDate';
|
||||||
|
import { ExecutiveSummarySchema } from 'openapi';
|
||||||
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,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const createOptions = (theme: Theme, locationSettings: ILocationSettings) =>
|
const createOptions = (theme: Theme, locationSettings: ILocationSettings) =>
|
||||||
({
|
({
|
||||||
@ -380,10 +71,50 @@ const createOptions = (theme: Theme, locationSettings: ILocationSettings) =>
|
|||||||
},
|
},
|
||||||
}) as const;
|
}) 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<IUsersChartComponentProps> = ({
|
||||||
|
userTrends,
|
||||||
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { locationSettings } = useLocationSettings();
|
const { locationSettings } = useLocationSettings();
|
||||||
const data = createData(theme);
|
const data = useMemo(
|
||||||
|
() => createData(theme, userTrends),
|
||||||
|
[theme, userTrends],
|
||||||
|
);
|
||||||
|
|
||||||
const options = createOptions(theme, locationSettings);
|
const options = createOptions(theme, locationSettings);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -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<ExecutiveSummarySchema>(
|
||||||
|
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<ExecutiveSummarySchema> => {
|
||||||
|
return fetch(path)
|
||||||
|
.then(handleErrorResponses('Executive Dashboard Data'))
|
||||||
|
.then((res) => res.json());
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user