1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-24 01:18:01 +02:00

feat: licensed users chart (#8844)

Currently showing 2 lines, because backend is not sorting the data.


![image](https://github.com/user-attachments/assets/905001fb-2020-45b2-a1f4-ba497b594e61)
This commit is contained in:
Jaanus Sellin 2024-11-25 10:03:21 +02:00 committed by GitHub
parent f985cb1deb
commit 9a269e3597
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 104 additions and 18 deletions

View File

@ -0,0 +1,54 @@
import type { FC } from 'react';
import 'chartjs-adapter-date-fns';
import type { LicensedUsersSchema } from 'openapi';
import { LineChart } from 'component/insights/components/LineChart/LineChart';
import { useTheme } from '@mui/material';
interface ILicensedUsersChartProps {
licensedUsers: LicensedUsersSchema['licensedUsers']['history'];
}
export const LicensedUsersChart: FC<ILicensedUsersChartProps> = ({
licensedUsers,
}) => {
const theme = useTheme();
const data = {
datasets: [
{
label: 'Licensed users',
data: licensedUsers,
borderColor: theme.palette.primary.main,
backgroundColor: theme.palette.primary.main,
fill: false,
},
],
};
return (
<LineChart
data={data}
overrideOptions={{
parsing: {
yAxisKey: 'count',
xAxisKey: 'date',
},
plugins: {
legend: {
display: false,
},
},
scales: {
x: {
time: {
unit: 'month',
tooltipFormat: 'MMM yyyy',
displayFormats: {
month: 'MMM',
},
},
},
},
}}
/>
);
};

View File

@ -1,6 +1,8 @@
import { Alert, Button, styled, Typography } from '@mui/material'; import { Alert, Button, styled, Typography } from '@mui/material';
import { DynamicSidebarModal } from 'component/common/SidebarModal/SidebarModal'; import { DynamicSidebarModal } from 'component/common/SidebarModal/SidebarModal';
import type React from 'react'; import type React from 'react';
import { LicensedUsersChart } from './LicensedUsersChart';
import { useLicensedUsers } from 'hooks/useLicensedUsers';
const ModalContentContainer = styled('section')(({ theme }) => ({ const ModalContentContainer = styled('section')(({ theme }) => ({
minHeight: '100vh', minHeight: '100vh',
maxWidth: 700, maxWidth: 700,
@ -74,6 +76,7 @@ export const LicensedUsersSidebar = ({
open, open,
close, close,
}: LicensedUsersSidebarProps) => { }: LicensedUsersSidebarProps) => {
const { data } = useLicensedUsers();
return ( return (
<DynamicSidebarModal <DynamicSidebarModal
open={open} open={open}
@ -94,7 +97,10 @@ export const LicensedUsersSidebar = ({
<RowHeader>Last 30 days</RowHeader> <RowHeader>Last 30 days</RowHeader>
<InfoRow> <InfoRow>
<LicenceBox> <LicenceBox>
<Typography fontWeight='bold'>11/25</Typography> <Typography fontWeight='bold'>
{data.licensedUsers.current}/
{data.seatCount}
</Typography>
<Typography variant='body2'> <Typography variant='body2'>
Used seats last 30 days Used seats last 30 days
</Typography> </Typography>
@ -108,7 +114,9 @@ export const LicensedUsersSidebar = ({
</Row> </Row>
<Row> <Row>
<RowHeader>Last year</RowHeader> <RowHeader>Last year</RowHeader>
<div>this will be great grid</div> <LicensedUsersChart
licensedUsers={data.licensedUsers.history}
/>
</Row> </Row>
</WidgetContainer> </WidgetContainer>
<CloseRow> <CloseRow>

View File

@ -24,6 +24,7 @@ import {
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { styled } from '@mui/material'; import { styled } from '@mui/material';
import { createOptions } from './createChartOptions'; import { createOptions } from './createChartOptions';
import merge from 'deepmerge';
const StyledContainer = styled('div')(({ theme }) => ({ const StyledContainer = styled('div')(({ theme }) => ({
position: 'relative', position: 'relative',
@ -81,6 +82,10 @@ const customHighlightPlugin = {
}, },
}; };
function mergeAll<T>(objects: Partial<T>[]): T {
return merge.all<T>(objects.filter((i) => i));
}
const LineChartComponent: FC<{ const LineChartComponent: FC<{
data: ChartData<'line', unknown>; data: ChartData<'line', unknown>;
aspectRatio?: number; aspectRatio?: number;
@ -100,16 +105,18 @@ const LineChartComponent: FC<{
const { locationSettings } = useLocationSettings(); const { locationSettings } = useLocationSettings();
const [tooltip, setTooltip] = useState<null | TooltipState>(null); const [tooltip, setTooltip] = useState<null | TooltipState>(null);
const options = useMemo( const options = useMemo(
() => ({ () =>
...createOptions( mergeAll([
theme, createOptions(
locationSettings, theme,
setTooltip, locationSettings,
Boolean(cover), setTooltip,
), Boolean(cover),
...overrideOptions, ),
}), overrideOptions ?? {},
]),
[theme, locationSettings, overrideOptions, cover], [theme, locationSettings, overrideOptions, cover],
); );

View File

@ -3,13 +3,14 @@ import type { ILocationSettings } from 'hooks/useLocationSettings';
import type { TooltipState } from './ChartTooltip/ChartTooltip'; import type { TooltipState } from './ChartTooltip/ChartTooltip';
import { createTooltip } from './createTooltip'; import { createTooltip } from './createTooltip';
import { legendOptions } from './legendOptions'; import { legendOptions } from './legendOptions';
import type { ChartOptions } from 'chart.js';
export const createOptions = ( export const createOptions = (
theme: Theme, theme: Theme,
locationSettings: ILocationSettings, locationSettings: ILocationSettings,
setTooltip: React.Dispatch<React.SetStateAction<TooltipState | null>>, setTooltip: React.Dispatch<React.SetStateAction<TooltipState | null>>,
isPlaceholder?: boolean, isPlaceholder?: boolean,
) => ): ChartOptions<'line'> =>
({ ({
responsive: true, responsive: true,
...(isPlaceholder ...(isPlaceholder
@ -27,10 +28,6 @@ export const createOptions = (
tooltip: { tooltip: {
enabled: false, enabled: false,
position: 'nearest', position: 'nearest',
interaction: {
axis: 'xy',
mode: 'nearest',
},
external: createTooltip(setTooltip), external: createTooltip(setTooltip),
}, },
}, },
@ -46,8 +43,6 @@ export const createOptions = (
hitRadius: 15, hitRadius: 15,
}, },
}, },
// cubicInterpolationMode: 'monotone',
tension: 0.1,
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
scales: { scales: {
y: { y: {

View File

@ -0,0 +1,22 @@
import type { LicensedUsersSchema } from '../openapi';
import { useApiGetter, fetcher } from './api/getters/useApiGetter/useApiGetter';
import { formatApiPath } from '../utils/formatPath';
const path = `api/admin/licensed-users`;
const placeholderData: LicensedUsersSchema = {
seatCount: 0,
licensedUsers: {
current: 0,
history: [],
},
};
export const useLicensedUsers = () => {
const { data, refetch, loading, error } = useApiGetter<LicensedUsersSchema>(
formatApiPath(path),
() => fetcher(formatApiPath(path), 'Licensed users'),
);
return { data: data || placeholderData, refetch, loading, error };
};