From 071b9e37280b39345943191a86b61097bfea0840 Mon Sep 17 00:00:00 2001 From: Jeremy Foster Date: Thu, 21 Mar 2024 07:52:05 -0700 Subject: [PATCH] HOSTSD-276 Storage trend size type --- .../storageTrends/StorageTrendsChart.tsx | 54 ++++++++++++------- .../hooks/useStorageTrendsData.ts | 25 ++++++--- .../src/components/dashboard/Dashboard.tsx | 18 +++---- .../src/utils/convertToStorageSize.ts | 38 ++++++++++--- 4 files changed, 93 insertions(+), 42 deletions(-) diff --git a/src/dashboard/src/components/charts/storageTrends/StorageTrendsChart.tsx b/src/dashboard/src/components/charts/storageTrends/StorageTrendsChart.tsx index f58aeefa..f2d3a94d 100644 --- a/src/dashboard/src/components/charts/storageTrends/StorageTrendsChart.tsx +++ b/src/dashboard/src/components/charts/storageTrends/StorageTrendsChart.tsx @@ -1,6 +1,7 @@ 'use client'; import { DateRangePicker } from '@/components'; +import { IServerItemListModel } from '@/hooks'; import { useDashboard } from '@/hooks/dashboard'; import { useStorageTrendsStore } from '@/store'; import { @@ -31,8 +32,13 @@ interface LineChartProps { loading?: boolean; /** Date range selected for the filter. */ dateRange?: string[]; + /** An array of server items */ + serverItems?: IServerItemListModel[]; + /** Whether to show the export button. */ showExport?: boolean; + /** Whether the export button is disabled. */ exportDisabled?: boolean; + /** Event fires when export button is clicked. */ onExport?: (startDate?: string, endDate?: string) => void; } @@ -46,6 +52,7 @@ export const StorageTrendsChart: React.FC = ({ loading, minColumns, dateRange: initDateRange, + serverItems = [], showExport, exportDisabled, onExport, @@ -56,6 +63,8 @@ export const StorageTrendsChart: React.FC = ({ const { isReady: serverHistoryItemsIsReady, findServerHistoryItems } = useServerHistoryItems(); const { tenantId, organizationId, operatingSystemItemId, serverItemKey } = useDashboard(); + const [forceFetch, setForceFetch] = React.useState(0); + React.useEffect(() => { const values = [ initDateRange?.length && initDateRange[0] @@ -68,22 +77,29 @@ export const StorageTrendsChart: React.FC = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [initDateRange?.[0], initDateRange?.[1], setDateRange]); + const fetch = React.useCallback( + (startDate?: string, endDate?: string) => { + if (startDate) { + // A single server was selected, fetch the history for this server. + return findServerHistoryItems({ + tenantId: tenantId, + organizationId: organizationId, + operatingSystemItemId: operatingSystemItemId, + serviceNowKey: serverItemKey, + startDate: startDate, + endDate: endDate ? endDate : undefined, + }).catch((ex) => { + const error = ex as Error; + toast.error(error.message); + console.error(error); + }); + } + }, + [findServerHistoryItems, operatingSystemItemId, organizationId, serverItemKey, tenantId], + ); + React.useEffect(() => { - if (dateRange[0]) { - // A single server was selected, fetch the history for this server. - findServerHistoryItems({ - tenantId: tenantId, - organizationId: organizationId, - operatingSystemItemId: operatingSystemItemId, - serviceNowKey: serverItemKey, - startDate: dateRange[0], - endDate: dateRange[1] ? dateRange[1] : undefined, - }).catch((ex) => { - const error = ex as Error; - toast.error(error.message); - console.error(error); - }); - } + fetch(dateRange[0], dateRange[1])?.catch(() => {}); // Values array will cause infinite loop, we're only interested in when values change. // findServerHistoryItems will cause infinite loop. // eslint-disable-next-line react-hooks/exhaustive-deps @@ -97,11 +113,12 @@ export const StorageTrendsChart: React.FC = ({ dateRange[0], // eslint-disable-next-line react-hooks/exhaustive-deps dateRange[1], + forceFetch, ]); return ( = ({
{ - setDateRange(values); + onChange={(values, e) => { + if (values[0] !== dateRange[0] || values[1] !== dateRange[1]) setDateRange(values); + else setForceFetch(Date.now()); }} showButton /> diff --git a/src/dashboard/src/components/charts/storageTrends/hooks/useStorageTrendsData.ts b/src/dashboard/src/components/charts/storageTrends/hooks/useStorageTrendsData.ts index bbceb6b5..909b136f 100644 --- a/src/dashboard/src/components/charts/storageTrends/hooks/useStorageTrendsData.ts +++ b/src/dashboard/src/components/charts/storageTrends/hooks/useStorageTrendsData.ts @@ -1,10 +1,11 @@ -import { IServerHistoryItemModel } from '@/hooks'; +import { IServerHistoryItemModel, IServerItemListModel } from '@/hooks'; import { useStorageTrendsStore } from '@/store'; import { convertToStorageSize } from '@/utils'; import { ChartData } from 'chart.js'; import moment from 'moment'; import React from 'react'; import { generateStorageHistoryForDateRange } from '../../utils'; +import { convertStorageSize } from './../../../../utils/convertToStorageSize'; /** * Generates line chart data based on the current filtered server history items. @@ -14,13 +15,25 @@ import { generateStorageHistoryForDateRange } from '../../utils'; export const useStorageTrendsData = (): (( minColumns?: number, dateRange?: string[], + serverItems?: IServerItemListModel[], ) => ChartData<'line', number[], string>) => { const serverHistoryItems = useStorageTrendsStore((state) => state.serverHistoryItems); return React.useCallback( - (minColumns: number = 1, dateRange: string[] = []) => { + ( + minColumns: number = 1, + dateRange: string[] = [], + serverItems: IServerItemListModel[] = [], + ) => { const groups = generateStorageHistoryForDateRange(minColumns, dateRange); + // Determine the output size type to use based on the current total storage of the selected servers. + const storageSizeType = convertStorageSize( + serverItems.map((i) => i.capacity ?? 0).reduce((a, b) => a + b, 0), + 'B', + 'TB', + ).type; + // server history is returned for each server, however some servers may lack history. // This process needs to group each month. const items = serverHistoryItems @@ -49,7 +62,7 @@ export const useStorageTrendsData = (): (( .map((i) => i.capacity) .reduce((result, value) => (result ?? 0) + (value ?? 0), 0) ?? 0, 'B', - 'TB', + storageSizeType, { type: 'number' }, ); group.availableSpace = convertToStorageSize( @@ -57,7 +70,7 @@ export const useStorageTrendsData = (): (( .map((i) => i.availableSpace) .reduce((result, value) => (result ?? 0) + (value ?? 0), 0) ?? 0, 'B', - 'TB', + storageSizeType, { type: 'number' }, ); group.usedSpace = group.capacity - group.availableSpace; @@ -67,14 +80,14 @@ export const useStorageTrendsData = (): (( labels: groups.map((i) => i.label), datasets: [ { - label: 'Total Used in TB', + label: `Total Used in ${storageSizeType}`, data: groups.map((i) => i.usedSpace), borderColor: '#313132', backgroundColor: '#313132', fill: false, }, { - label: 'Total Allocated in TB', + label: `Total Allocated in ${storageSizeType}`, data: groups.map((i) => i.capacity), borderColor: '#476E94', backgroundColor: '#476E94', diff --git a/src/dashboard/src/components/dashboard/Dashboard.tsx b/src/dashboard/src/components/dashboard/Dashboard.tsx index eebe21be..e19deb8a 100644 --- a/src/dashboard/src/components/dashboard/Dashboard.tsx +++ b/src/dashboard/src/components/dashboard/Dashboard.tsx @@ -205,7 +205,7 @@ export const Dashboard = () => { }} showExport onExport={async () => { - const toastLoading = toast.loading("Generating Excel document..."); + const toastLoading = toast.loading('Generating Excel document...'); try { await download({ @@ -214,7 +214,6 @@ export const Dashboard = () => { }); toast.dismiss(toastLoading); toast.success('Excel document has been downloaded successfully.'); - } catch (ex) { toast.dismiss(toastLoading); @@ -237,7 +236,7 @@ export const Dashboard = () => { loading={!isReadyOrganizations || !isReadyServerItems} showExport onExport={async () => { - const toastLoading = toast.loading("Generating Excel document..."); + const toastLoading = toast.loading('Generating Excel document...'); try { await download({ @@ -245,7 +244,6 @@ export const Dashboard = () => { }); toast.dismiss(toastLoading); toast.success('Excel document has been downloaded successfully.'); - } catch (ex) { toast.dismiss(toastLoading); @@ -258,9 +256,10 @@ export const Dashboard = () => { )} { - const toastLoading = toast.loading("Generating Excel document..."); + const toastLoading = toast.loading('Generating Excel document...'); try { await downloadHistory({ @@ -273,7 +272,6 @@ export const Dashboard = () => { }); toast.dismiss(toastLoading); toast.success('Excel document has been downloaded successfully.'); - } catch (ex) { toast.dismiss(toastLoading); @@ -365,7 +363,7 @@ export const Dashboard = () => { } }} onExport={async (search) => { - const toastLoading = toast.loading("Generating Excel document..."); + const toastLoading = toast.loading('Generating Excel document...'); try { await download({ @@ -374,7 +372,6 @@ export const Dashboard = () => { }); toast.dismiss(toastLoading); toast.success('Excel document has been downloaded successfully.'); - } catch (ex) { toast.dismiss(toastLoading); @@ -403,7 +400,7 @@ export const Dashboard = () => { }} showExport onExport={async (search) => { - const toastLoading = toast.loading("Generating Excel document..."); + const toastLoading = toast.loading('Generating Excel document...'); try { await download({ @@ -414,10 +411,9 @@ export const Dashboard = () => { }); toast.dismiss(toastLoading); toast.success('Excel document has been downloaded successfully.'); - } catch (ex) { toast.dismiss(toastLoading); - + const error = ex as Error; toast.error('Failed to download data. ' + error.message); console.error(error); diff --git a/src/dashboard/src/utils/convertToStorageSize.ts b/src/dashboard/src/utils/convertToStorageSize.ts index ae147d8f..cbf3c170 100644 --- a/src/dashboard/src/utils/convertToStorageSize.ts +++ b/src/dashboard/src/utils/convertToStorageSize.ts @@ -6,7 +6,7 @@ * @param options Configuration options. * @returns A string representing the storage size. */ -export const convertToStorageSize = ( +export const convertStorageSize = ( value: number, input: 'TB' | 'GB' | 'MB' | 'KB' | 'B' | '' = '', output: 'TB' | 'GB' | 'MB' | 'KB' | 'B' | '' = '', @@ -17,7 +17,7 @@ export const convertToStorageSize = ( locales?: Intl.LocalesArgument; } & Intl.NumberFormatOptions) | undefined, -): T => { +): { value: T; type: 'TB' | 'GB' | 'MB' | 'KB' | 'B' | '' } => { var result = value; if (input === output) result = value; else if (input === 'TB') { @@ -65,13 +65,37 @@ export const convertToStorageSize = ( } result = options?.formula?.(result) ?? result; + return { value: result as T, type: output }; +}; + +/** + * Converts a storage value to a specified size type. + * @param value The initial value to convert. + * @param input The input size type. + * @param output The output size type. + * @param options Configuration options. + * @returns A string representing the storage size. + */ +export const convertToStorageSize = ( + value: number, + input: 'TB' | 'GB' | 'MB' | 'KB' | 'B' | '' = '', + output: 'TB' | 'GB' | 'MB' | 'KB' | 'B' | '' = '', + options?: + | ({ + formula?: (value: number) => number; + type?: 'string' | 'number'; + locales?: Intl.LocalesArgument; + } & Intl.NumberFormatOptions) + | undefined, +): T => { + const result = convertStorageSize(value, input, output, options); + return ( options?.type === 'number' - ? result - : `${result.toLocaleString( - options?.locales ?? navigator.language, - options, - )} ${output}`.trimEnd() + ? result.value + : `${result.value.toLocaleString(options?.locales ?? navigator.language, options)} ${ + result.type + }`.trimEnd() ) as T; };