refactor: improve code readability and consistency across dashboard components

This commit is contained in:
Nawaz Dhandala
2026-03-30 16:17:16 +01:00
parent 4caed413a3
commit ffafada55b
14 changed files with 125 additions and 140 deletions

View File

@@ -182,9 +182,9 @@ const DashboardChartComponentElement: FunctionComponent<ComponentProps> = (
const numberOfCharts: number = queryConfigs.length || 1;
// Account for widget-level header and per-chart overhead (title + legend + padding)
const hasWidgetHeader: boolean = !!(
const hasWidgetHeader: boolean = Boolean(
props.component.arguments.chartTitle ||
props.component.arguments.chartDescription
props.component.arguments.chartDescription,
);
const widgetHeaderHeight: number = hasWidgetHeader ? 50 : 0;
// Each chart section: pt-5(20) + title(20) + legend(24) + pb-4(16) = ~80px overhead
@@ -215,22 +215,19 @@ const DashboardChartComponentElement: FunctionComponent<ComponentProps> = (
};
const chartMetricViewData: MetricViewData = {
queryConfigs: queryConfigs.map(
(config: MetricQueryConfigData) => {
return {
...config,
metricAliasData: {
metricVariable:
config.metricAliasData?.metricVariable || undefined,
title: config.metricAliasData?.title || undefined,
description: config.metricAliasData?.description || undefined,
legend: config.metricAliasData?.legend || undefined,
legendUnit: config.metricAliasData?.legendUnit || undefined,
},
chartType: config.chartType || getMetricChartType(),
};
},
),
queryConfigs: queryConfigs.map((config: MetricQueryConfigData) => {
return {
...config,
metricAliasData: {
metricVariable: config.metricAliasData?.metricVariable || undefined,
title: config.metricAliasData?.title || undefined,
description: config.metricAliasData?.description || undefined,
legend: config.metricAliasData?.legend || undefined,
legendUnit: config.metricAliasData?.legendUnit || undefined,
},
chartType: config.chartType || getMetricChartType(),
};
}),
startAndEndDate: RangeStartAndEndDateTimeUtil.getStartAndEndDate(
props.dashboardStartAndEndDate,
),

View File

@@ -25,9 +25,15 @@ const DashboardTemplateCard: FunctionComponent<ComponentProps> = (
}}
>
<div className="flex items-center justify-center w-10 h-10 rounded-lg bg-indigo-50 mb-3">
<Icon icon={props.icon} size={SizeProp.Large} className="text-indigo-500 h-5 w-5" />
<Icon
icon={props.icon}
size={SizeProp.Large}
className="text-indigo-500 h-5 w-5"
/>
</div>
<h3 className="text-sm font-semibold text-gray-900 mb-1">{props.title}</h3>
<h3 className="text-sm font-semibold text-gray-900 mb-1">
{props.title}
</h3>
<p className="text-xs text-gray-500 leading-relaxed">
{props.description}
</p>

View File

@@ -70,8 +70,7 @@ const MetricCharts: FunctionComponent<ComponentProps> = (
continue;
}
let xAxisAggregationType: XAxisAggregateType =
XAxisAggregateType.Average;
let xAxisAggregationType: XAxisAggregateType = XAxisAggregateType.Average;
if (
queryConfig.metricQueryData.filterData.aggegationType ===
@@ -171,9 +170,7 @@ const MetricCharts: FunctionComponent<ComponentProps> = (
// Resolve the unit for formatting
const metricType: MetricType | undefined = props.metricTypes.find(
(m: MetricType) => {
return (
m.name === queryConfig.metricQueryData.filterData.metricName
);
return m.name === queryConfig.metricQueryData.filterData.metricName;
},
);
const unit: string =

View File

@@ -63,9 +63,7 @@ const MetricGraphConfig: FunctionComponent<ComponentProps> = (
"Avg";
// Remove a single attribute filter
const handleRemoveAttribute: (key: string) => void = (
key: string,
): void => {
const handleRemoveAttribute: (key: string) => void = (key: string): void => {
if (!attributes) {
return;
}
@@ -187,51 +185,52 @@ const MetricGraphConfig: FunctionComponent<ComponentProps> = (
);
};
const getAttributeChips: () => ReactElement | null = (): ReactElement | null => {
if (!attributes || activeAttributeCount === 0) {
return null;
}
const getAttributeChips: () => ReactElement | null =
(): ReactElement | null => {
if (!attributes || activeAttributeCount === 0) {
return null;
}
return (
<div className="flex flex-wrap items-center gap-1.5 mt-3 pt-3 border-t border-gray-100">
<span className="text-xs text-gray-400 font-medium mr-1">
Filtered by:
</span>
{Object.entries(attributes).map(
([key, value]: [string, string | number | boolean]) => {
return (
<span
key={key}
className="inline-flex items-center gap-1 rounded-md border border-indigo-200 bg-indigo-50 py-0.5 pl-2 pr-1 text-xs text-indigo-700"
>
<span className="font-medium text-indigo-500">{key}:</span>
<span>{String(value)}</span>
<button
type="button"
className="ml-0.5 inline-flex h-4 w-4 items-center justify-center rounded text-indigo-400 transition-colors hover:bg-indigo-100 hover:text-indigo-600"
onClick={() => {
handleRemoveAttribute(key);
}}
title={`Remove ${key}: ${String(value)}`}
return (
<div className="flex flex-wrap items-center gap-1.5 mt-3 pt-3 border-t border-gray-100">
<span className="text-xs text-gray-400 font-medium mr-1">
Filtered by:
</span>
{Object.entries(attributes).map(
([key, value]: [string, string | number | boolean]) => {
return (
<span
key={key}
className="inline-flex items-center gap-1 rounded-md border border-indigo-200 bg-indigo-50 py-0.5 pl-2 pr-1 text-xs text-indigo-700"
>
<Icon icon={IconProp.Close} className="h-2.5 w-2.5" />
</button>
</span>
);
},
)}
{activeAttributeCount > 1 && (
<button
type="button"
className="rounded px-1.5 py-0.5 text-[11px] font-medium text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600"
onClick={handleClearAllAttributes}
>
Clear all
</button>
)}
</div>
);
};
<span className="font-medium text-indigo-500">{key}:</span>
<span>{String(value)}</span>
<button
type="button"
className="ml-0.5 inline-flex h-4 w-4 items-center justify-center rounded text-indigo-400 transition-colors hover:bg-indigo-100 hover:text-indigo-600"
onClick={() => {
handleRemoveAttribute(key);
}}
title={`Remove ${key}: ${String(value)}`}
>
<Icon icon={IconProp.Close} className="h-2.5 w-2.5" />
</button>
</span>
);
},
)}
{activeAttributeCount > 1 && (
<button
type="button"
className="rounded px-1.5 py-0.5 text-[11px] font-medium text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600"
onClick={handleClearAllAttributes}
>
Clear all
</button>
)}
</div>
);
};
const getContent: () => ReactElement = (): ReactElement => {
return (
@@ -352,9 +351,7 @@ const MetricGraphConfig: FunctionComponent<ComponentProps> = (
Warning Threshold
</label>
<Input
value={
props.data?.warningThreshold?.toString() || ""
}
value={props.data?.warningThreshold?.toString() || ""}
type={InputType.NUMBER}
onChange={(value: string) => {
props.onBlur?.();
@@ -376,9 +373,7 @@ const MetricGraphConfig: FunctionComponent<ComponentProps> = (
Critical Threshold
</label>
<Input
value={
props.data?.criticalThreshold?.toString() || ""
}
value={props.data?.criticalThreshold?.toString() || ""}
type={InputType.NUMBER}
onChange={(value: string) => {
props.onBlur?.();

View File

@@ -108,9 +108,9 @@ const MetricView: FunctionComponent<ComponentProps> = (
const [isPageLoading, setIsPageLoading] = useState<boolean>(false);
const [pageError, setPageError] = useState<string>("");
const [telemetryAttributes, setTelemetryAttributes] = useState<
Array<string>
>([]);
const [telemetryAttributes, setTelemetryAttributes] = useState<Array<string>>(
[],
);
const [telemetryAttributesLoaded, setTelemetryAttributesLoaded] =
useState<boolean>(false);
const [telemetryAttributesLoading, setTelemetryAttributesLoading] =
@@ -456,9 +456,7 @@ const MetricView: FunctionComponent<ComponentProps> = (
{!isMetricResultsLoading && !metricResultsError && (
<div
className={
props.hideCardInCharts ? "" : "grid grid-cols-1 gap-4"
}
className={props.hideCardInCharts ? "" : "grid grid-cols-1 gap-4"}
>
<MetricCharts
hideCard={props.hideCardInCharts}

View File

@@ -60,7 +60,7 @@ const Dashboards: FunctionComponent<PageComponentProps> = (): ReactElement => {
},
]}
>
{showTemplateModal && (
{showTemplateModal ? (
<Modal
title="Create from Template"
description="Choose a template to quickly get started with a pre-configured dashboard."
@@ -87,6 +87,8 @@ const Dashboards: FunctionComponent<PageComponentProps> = (): ReactElement => {
)}
</div>
</Modal>
) : (
<></>
)}
<ModelTable<Dashboard>
@@ -138,8 +140,14 @@ const Dashboards: FunctionComponent<PageComponentProps> = (): ReactElement => {
placeholder: "Description",
},
]}
onBeforeCreate={async (item: Dashboard, _miscDataProps: JSONObject): Promise<Dashboard> => {
if (selectedTemplate && selectedTemplate !== DashboardTemplateType.Blank) {
onBeforeCreate={async (
item: Dashboard,
_miscDataProps: JSONObject,
): Promise<Dashboard> => {
if (
selectedTemplate &&
selectedTemplate !== DashboardTemplateType.Blank
) {
const templateConfig: DashboardViewConfig | null =
getTemplateConfig(selectedTemplate);
if (templateConfig) {

View File

@@ -17,18 +17,19 @@ const DashboardVariableSelector: FunctionComponent<ComponentProps> = (
<label className="text-xs font-medium text-gray-500">
{variable.name}:
</label>
{variable.options && variable.options.length > 0 ? (
{variable.customListValues ? (
<select
className="text-xs border border-gray-200 rounded px-2 py-1 bg-white text-gray-700"
value={variable.currentValue || variable.defaultValue || ""}
value={variable.selectedValue || variable.defaultValue || ""}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
props.onVariableValueChange(variable.id!, e.target.value);
props.onVariableValueChange(variable.id, e.target.value);
}}
>
{variable.options.map((option: string) => {
{variable.customListValues.split(",").map((option: string) => {
const trimmedOption: string = option.trim();
return (
<option key={option} value={option}>
{option}
<option key={trimmedOption} value={trimmedOption}>
{trimmedOption}
</option>
);
})}
@@ -37,9 +38,9 @@ const DashboardVariableSelector: FunctionComponent<ComponentProps> = (
<input
type="text"
className="text-xs border border-gray-200 rounded px-2 py-1 bg-white text-gray-700 w-24"
value={variable.currentValue || variable.defaultValue || ""}
value={variable.selectedValue || variable.defaultValue || ""}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
props.onVariableValueChange(variable.id!, e.target.value);
props.onVariableValueChange(variable.id, e.target.value);
}}
/>
)}

View File

@@ -7,7 +7,6 @@ import React, {
useState,
} from "react";
import DashboardCanvas from "../../Components/DashboardCanvas";
import DashboardMode from "Common/Types/Dashboard/DashboardMode";
import DashboardViewConfig, {
AutoRefreshInterval,
getAutoRefreshIntervalInMs,
@@ -370,19 +369,21 @@ const DashboardViewPage: FunctionComponent<ComponentProps> = (
onDashboardViewConfigChange={(_config: DashboardViewConfig) => {
// Read-only in public view
}}
dashboardMode={DashboardMode.View}
isEditMode={false}
selectedComponentId={null}
onComponentSelected={(_id: ObjectID | null) => {
onComponentSelected={(_id: ObjectID) => {
// No selection in public view
}}
dashboardTotalWidth={dashboardTotalWidth}
startAndEndDate={startAndEndDate}
onStartAndEndDateChange={(newRange: RangeStartAndEndDateTime) => {
setTimeRangeStack([...timeRangeStack, startAndEndDate]);
setStartAndEndDate(newRange);
onComponentUnselected={() => {
// No unselection in public view
}}
currentTotalDashboardWidthInPx={dashboardTotalWidth}
dashboardStartAndEndDate={startAndEndDate}
metrics={{
metricTypes: [],
telemetryAttributes: [],
}}
refreshTick={refreshTick}
dashboardVariables={dashboardVariables}
/>
</div>

View File

@@ -63,7 +63,7 @@ interface MetricConfig {
legendUnit?: string;
}
function buildMetricQueryConfig(config: MetricConfig): object {
function buildMetricQueryConfig(config: MetricConfig): Record<string, unknown> {
return {
metricAliasData: {
metricVariable: "a",
@@ -82,7 +82,7 @@ function buildMetricQueryConfig(config: MetricConfig): object {
};
}
function buildMetricQueryData(config: MetricConfig): object {
function buildMetricQueryData(config: MetricConfig): Record<string, unknown> {
return {
metricQueryData: {
filterData: {
@@ -497,10 +497,7 @@ function createMonitorDashboardConfig(): DashboardViewConfig {
return {
_type: ObjectType.DashboardViewConfig,
components,
heightInDashboardUnits: Math.max(
DashboardSize.heightInDashboardUnits,
13,
),
heightInDashboardUnits: Math.max(DashboardSize.heightInDashboardUnits, 13),
};
}
@@ -739,10 +736,7 @@ function createIncidentDashboardConfig(): DashboardViewConfig {
return {
_type: ObjectType.DashboardViewConfig,
components,
heightInDashboardUnits: Math.max(
DashboardSize.heightInDashboardUnits,
20,
),
heightInDashboardUnits: Math.max(DashboardSize.heightInDashboardUnits, 20),
};
}
@@ -950,10 +944,7 @@ function createKubernetesDashboardConfig(): DashboardViewConfig {
return {
_type: ObjectType.DashboardViewConfig,
components,
heightInDashboardUnits: Math.max(
DashboardSize.heightInDashboardUnits,
16,
),
heightInDashboardUnits: Math.max(DashboardSize.heightInDashboardUnits, 16),
};
}

View File

@@ -978,10 +978,7 @@ const AreaChart: React.ForwardRefExoticComponent<
);
})}
{props.referenceLines?.map(
(
refLine: ChartReferenceLineProps,
refIndex: number,
) => {
(refLine: ChartReferenceLineProps, refIndex: number) => {
return (
<ReferenceLine
key={`ref-${refIndex}`}

View File

@@ -1014,10 +1014,7 @@ const BarChart: React.ForwardRefExoticComponent<
);
})}
{props.referenceLines?.map(
(
refLine: ChartReferenceLineProps,
refIndex: number,
) => {
(refLine: ChartReferenceLineProps, refIndex: number) => {
return (
<ReferenceLine
key={`ref-${refIndex}`}

View File

@@ -996,10 +996,7 @@ const LineChart: React.ForwardRefExoticComponent<
})
: null}
{props.referenceLines?.map(
(
refLine: ChartReferenceLineProps,
refIndex: number,
) => {
(refLine: ChartReferenceLineProps, refIndex: number) => {
return (
<ReferenceLine
key={`ref-${refIndex}`}

View File

@@ -2803,11 +2803,7 @@ const Icon: FunctionComponent<ComponentProps> = ({
strokeLinejoin="round"
d="M5.636 7.636l1.414 1.414"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 5v2"
/>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 5v2" />
<path
strokeLinecap="round"
strokeLinejoin="round"

View File

@@ -1,5 +1,7 @@
// Human-friendly value formatting for metric units.
// Converts raw values like 1048576 bytes → "1 MB", 3661 seconds → "1.02 hr", etc.
/*
* Human-friendly value formatting for metric units.
* Converts raw values like 1048576 bytes → "1 MB", 3661 seconds → "1.02 hr", etc.
*/
export interface FormattedValue {
value: string; // e.g. "1.5"
@@ -135,10 +137,12 @@ function formatNumber(value: number): string {
}
export default class ValueFormatter {
// Format a value with a unit into a human-friendly string.
// e.g. formatValue(1048576, "bytes") → "1 MB"
// e.g. formatValue(3661, "seconds") → "1.02 hr"
// e.g. formatValue(42, "%") → "42 %" (passthrough for unknown units)
/*
* Format a value with a unit into a human-friendly string.
* e.g. formatValue(1048576, "bytes") → "1 MB"
* e.g. formatValue(3661, "seconds") → "1.02 hr"
* e.g. formatValue(42, "%") → "42 %" (passthrough for unknown units)
*/
public static formatValue(value: number, unit: string): string {
if (!unit || unit.trim() === "") {
return formatNumber(value);