mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
refactor: improve code readability and consistency across dashboard components
This commit is contained in:
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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?.();
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -978,10 +978,7 @@ const AreaChart: React.ForwardRefExoticComponent<
|
||||
);
|
||||
})}
|
||||
{props.referenceLines?.map(
|
||||
(
|
||||
refLine: ChartReferenceLineProps,
|
||||
refIndex: number,
|
||||
) => {
|
||||
(refLine: ChartReferenceLineProps, refIndex: number) => {
|
||||
return (
|
||||
<ReferenceLine
|
||||
key={`ref-${refIndex}`}
|
||||
|
||||
@@ -1014,10 +1014,7 @@ const BarChart: React.ForwardRefExoticComponent<
|
||||
);
|
||||
})}
|
||||
{props.referenceLines?.map(
|
||||
(
|
||||
refLine: ChartReferenceLineProps,
|
||||
refIndex: number,
|
||||
) => {
|
||||
(refLine: ChartReferenceLineProps, refIndex: number) => {
|
||||
return (
|
||||
<ReferenceLine
|
||||
key={`ref-${refIndex}`}
|
||||
|
||||
@@ -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}`}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user