Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HOSTSD-276 Storage Trend Size Type #121

Merged
merged 8 commits into from
Mar 21, 2024
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import { DateRangePicker } from '@/components';
import { IServerItemListModel } from '@/hooks';
import { useDashboard } from '@/hooks/dashboard';
import { useStorageTrendsStore } from '@/store';
import {
Expand Down Expand Up @@ -31,8 +32,13 @@ interface LineChartProps {
loading?: boolean;
/** Date range selected for the filter. */
dateRange?: string[];
/** An array of server items */
serverItems?: IServerItemListModel[];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to pass the selected server items down so that it can be determine which storage size they are using.

/** 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;
}

Expand All @@ -46,6 +52,7 @@ export const StorageTrendsChart: React.FC<LineChartProps> = ({
loading,
minColumns,
dateRange: initDateRange,
serverItems = [],
showExport,
exportDisabled,
onExport,
Expand All @@ -56,6 +63,8 @@ export const StorageTrendsChart: React.FC<LineChartProps> = ({
const { isReady: serverHistoryItemsIsReady, findServerHistoryItems } = useServerHistoryItems();
const { tenantId, organizationId, operatingSystemItemId, serverItemKey } = useDashboard();

const [forceFetch, setForceFetch] = React.useState(0);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clicking the Update button doesn't do anything if the date doesn't change. This allows it to force an update.


React.useEffect(() => {
const values = [
initDateRange?.length && initDateRange[0]
Expand All @@ -68,22 +77,29 @@ export const StorageTrendsChart: React.FC<LineChartProps> = ({
// 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
Expand All @@ -97,11 +113,12 @@ export const StorageTrendsChart: React.FC<LineChartProps> = ({
dateRange[0],
// eslint-disable-next-line react-hooks/exhaustive-deps
dateRange[1],
forceFetch,
]);

return (
<LineChart
data={getStorageTrendsData(minColumns, dateRange)}
data={getStorageTrendsData(minColumns, dateRange, serverItems)}
label="Storage Trends"
loading={loading || !serverHistoryItemsIsReady}
large={large}
Expand All @@ -115,8 +132,9 @@ export const StorageTrendsChart: React.FC<LineChartProps> = ({
<div className={styles.date}>
<DateRangePicker
values={dateRange}
onChange={async (values, e) => {
setDateRange(values);
onChange={(values, e) => {
if (values[0] !== dateRange[0] || values[1] !== dateRange[1]) setDateRange(values);
else setForceFetch(Date.now());
}}
showButton
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the magic

'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
Expand Down Expand Up @@ -49,15 +62,15 @@ export const useStorageTrendsData = (): ((
.map((i) => i.capacity)
.reduce((result, value) => (result ?? 0) + (value ?? 0), 0) ?? 0,
'B',
'TB',
storageSizeType,
{ type: 'number' },
);
group.availableSpace = convertToStorageSize<number>(
values
.map((i) => i.availableSpace)
.reduce((result, value) => (result ?? 0) + (value ?? 0), 0) ?? 0,
'B',
'TB',
storageSizeType,
{ type: 'number' },
);
group.usedSpace = group.capacity - group.availableSpace;
Expand All @@ -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',
Expand Down
18 changes: 7 additions & 11 deletions src/dashboard/src/components/dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -214,7 +214,6 @@ export const Dashboard = () => {
});
toast.dismiss(toastLoading);
toast.success('Excel document has been downloaded successfully.');

} catch (ex) {
toast.dismiss(toastLoading);

Expand All @@ -237,15 +236,14 @@ 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({
tenantId: dashboardTenant?.id,
});
toast.dismiss(toastLoading);
toast.success('Excel document has been downloaded successfully.');

} catch (ex) {
toast.dismiss(toastLoading);

Expand All @@ -258,9 +256,10 @@ export const Dashboard = () => {
)}
<StorageTrendsChart
large={!!dashboardOrganization || !!dashboardOperatingSystemItem || !!dashboardServerItem}
serverItems={dashboardServerItem ? [dashboardServerItem] : dashboardServerItems}
showExport
onExport={async (startDate, endDate) => {
const toastLoading = toast.loading("Generating Excel document...");
const toastLoading = toast.loading('Generating Excel document...');

try {
await downloadHistory({
Expand All @@ -273,7 +272,6 @@ export const Dashboard = () => {
});
toast.dismiss(toastLoading);
toast.success('Excel document has been downloaded successfully.');

} catch (ex) {
toast.dismiss(toastLoading);

Expand Down Expand Up @@ -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({
Expand All @@ -374,7 +372,6 @@ export const Dashboard = () => {
});
toast.dismiss(toastLoading);
toast.success('Excel document has been downloaded successfully.');

} catch (ex) {
toast.dismiss(toastLoading);

Expand Down Expand Up @@ -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({
Expand All @@ -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);
Expand Down
38 changes: 31 additions & 7 deletions src/dashboard/src/utils/convertToStorageSize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* @param options Configuration options.
* @returns A string representing the storage size.
*/
export const convertToStorageSize = <T extends string | number>(
export const convertStorageSize = <T extends string | number>(
value: number,
input: 'TB' | 'GB' | 'MB' | 'KB' | 'B' | '' = '',
output: 'TB' | 'GB' | 'MB' | 'KB' | 'B' | '' = '',
Expand All @@ -17,7 +17,7 @@ export const convertToStorageSize = <T extends string | number>(
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') {
Expand Down Expand Up @@ -65,13 +65,37 @@ export const convertToStorageSize = <T extends string | number>(
}
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 = <T extends string | number>(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separated the function into two so that I have access to the logic without the string output.

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<T>(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;
};

Expand Down
Loading