Keep metric bars visually critical when alerts are disabled

Refs #1319
This commit is contained in:
rcourtman 2026-05-24 22:43:46 +01:00
parent 469a8e1bdc
commit c10b0dc353
8 changed files with 42 additions and 48 deletions

View file

@ -6,8 +6,7 @@ import { getMetricHistoryForRange, getMetricsVersion } from '@/stores/metricsHis
import { Sparkline } from '@/components/shared/Sparkline';
import type { AnomalyReport } from '@/types/aiIntelligence';
import {
getDefaultMetricDisplayThresholds,
getMetricSeverity,
getMetricVisualSeverity,
type MetricDisplayThresholds,
} from '@/utils/alertThresholds';
@ -36,13 +35,7 @@ export function EnhancedCPUBar(props: EnhancedCPUBarProps) {
// Bar color based on usage
const severity = createMemo(() => {
const thresholds = props.thresholds === undefined
? getDefaultMetricDisplayThresholds('cpu')
: props.thresholds;
return getMetricSeverity(
props.usage,
thresholds,
);
return getMetricVisualSeverity(props.usage, 'cpu', props.thresholds);
});
const barColor = createMemo(() => {

View file

@ -154,6 +154,20 @@ describe('MetricBar', () => {
expect(bar).toBeInTheDocument();
});
it('uses default display colors when notification thresholds are disabled', () => {
const result = render(() => (
<MetricBar
value={100}
label="100%"
type="memory"
thresholds={null}
/>
));
const bar = result.container.querySelector('.bg-red-500\\/60');
expect(bar).toBeInTheDocument();
});
it('toggles sparkline view mode', () => {
mockUseMetricsViewMode.mockReturnValue({
viewMode: () => 'sparklines',

View file

@ -3,8 +3,7 @@ import { Sparkline } from '@/components/shared/Sparkline';
import { useMetricsViewMode } from '@/stores/metricsViewMode';
import { getMetricHistoryForRange, getMetricsVersion } from '@/stores/metricsHistory';
import {
getDefaultMetricDisplayThresholds,
getMetricSeverity,
getMetricVisualSeverity,
type MetricDisplayThresholds,
} from '@/utils/alertThresholds';
@ -60,10 +59,7 @@ export function MetricBar(props: MetricBarProps) {
// Get color based on percentage and metric type (matching original)
const severity = createMemo(() => {
const metric = props.type || 'generic';
const thresholds = props.thresholds === undefined
? getDefaultMetricDisplayThresholds(metric)
: props.thresholds;
return getMetricSeverity(props.value, thresholds);
return getMetricVisualSeverity(props.value, metric, props.thresholds);
});
// Map color to CSS classes

View file

@ -4,8 +4,7 @@ import type { Disk } from '@/types/api';
import { formatBytes, formatPercent } from '@/utils/format';
import type { AnomalyReport } from '@/types/aiIntelligence';
import {
getDefaultMetricDisplayThresholds,
getMetricSeverity,
getMetricVisualSeverity,
type MetricDisplayThresholds,
} from '@/utils/alertThresholds';
@ -45,13 +44,7 @@ function getUsageColor(
percentage: number,
thresholds?: MetricDisplayThresholds | null,
): string {
const resolvedThresholds = thresholds === undefined
? getDefaultMetricDisplayThresholds('disk')
: thresholds;
const severity = getMetricSeverity(
percentage,
resolvedThresholds,
);
const severity = getMetricVisualSeverity(percentage, 'disk', thresholds);
if (severity === 'red') return 'rgba(239, 68, 68, 0.6)'; // red
if (severity === 'yellow') return 'rgba(234, 179, 8, 0.6)'; // yellow
return 'rgba(34, 197, 94, 0.6)'; // green
@ -138,10 +131,7 @@ export function StackedDiskBar(props: StackedDiskBarProps) {
const usedPercent = (disk.used / total) * 100;
const diskPercent = disk.total > 0 ? (disk.used / disk.total) * 100 : 0;
// Use warning/critical colors for high usage, otherwise use the color palette
const color = getMetricSeverity(
diskPercent,
props.thresholds === undefined ? getDefaultMetricDisplayThresholds('disk') : props.thresholds,
) !== 'green'
const color = getMetricVisualSeverity(diskPercent, 'disk', props.thresholds) !== 'green'
? getUsageColor(diskPercent, props.thresholds)
:
SEGMENT_COLORS[idx % SEGMENT_COLORS.length];
@ -207,7 +197,7 @@ export function StackedDiskBar(props: StackedDiskBarProps) {
const barColor = createMemo(() => {
const info = maxDiskInfo();
if (aggregateMode() && hasMultipleDisks() && info) {
return getUsageColor(info.percent);
return getUsageColor(info.percent, props.thresholds);
}
return getUsageColor(overallPercent(), props.thresholds);
});
@ -227,10 +217,7 @@ export function StackedDiskBar(props: StackedDiskBarProps) {
percent: measured ? formatPercent(percent) : '—',
color: useUsageColors
? getUsageColor(percent, props.thresholds)
: getMetricSeverity(
percent,
props.thresholds === undefined ? getDefaultMetricDisplayThresholds('disk') : props.thresholds,
) !== 'green'
: getMetricVisualSeverity(percent, 'disk', props.thresholds) !== 'green'
? getUsageColor(percent, props.thresholds)
: SEGMENT_COLORS[idx % SEGMENT_COLORS.length],
};

View file

@ -6,8 +6,7 @@ import { useMetricsViewMode } from '@/stores/metricsViewMode';
import { getMetricHistoryForRange, getMetricsVersion } from '@/stores/metricsHistory';
import type { AnomalyReport } from '@/types/aiIntelligence';
import {
getDefaultMetricDisplayThresholds,
getMetricSeverity,
getMetricVisualSeverity,
type MetricDisplayThresholds,
} from '@/utils/alertThresholds';
@ -44,13 +43,7 @@ const getMemoryColor = (
percent: number,
thresholds?: MetricDisplayThresholds | null,
): string => {
const resolvedThresholds = thresholds === undefined
? getDefaultMetricDisplayThresholds('memory')
: thresholds;
const severity = getMetricSeverity(
percent,
resolvedThresholds,
);
const severity = getMetricVisualSeverity(percent, 'memory', thresholds);
if (severity === 'red') return 'rgba(239, 68, 68, 0.7)'; // red
if (severity === 'yellow') return 'rgba(234, 179, 8, 0.7)'; // yellow/orange
return 'rgba(34, 197, 94, 0.6)'; // green

View file

@ -2,8 +2,7 @@ import { Component, Show, createMemo, JSX } from 'solid-js';
import { MetricBar } from '@/components/Dashboard/MetricBar';
import { formatPercent } from '@/utils/format';
import {
getDefaultMetricDisplayThresholds,
getMetricSeverity,
getMetricVisualSeverity,
type MetricDisplayThresholds,
} from '@/utils/alertThresholds';
@ -47,10 +46,7 @@ function getMetricColorClass(
type: 'cpu' | 'memory' | 'disk',
thresholds?: MetricDisplayThresholds | null,
): string {
const resolvedThresholds = thresholds === undefined
? getDefaultMetricDisplayThresholds(type)
: thresholds;
const severity = getMetricSeverity(value, resolvedThresholds);
const severity = getMetricVisualSeverity(value, type, thresholds);
if (severity === 'red') return 'text-red-600 dark:text-red-400 font-bold';
if (severity === 'yellow') return 'text-orange-600 dark:text-orange-400 font-medium';
return 'text-gray-600 dark:text-gray-400';

View file

@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest';
import {
getDefaultMetricDisplayThresholds,
getMetricSeverity,
getMetricVisualSeverity,
resolveMetricDisplayThresholds,
} from '../alertThresholds';
import type { AlertConfig } from '@/types/alerts';
@ -75,4 +76,9 @@ describe('alertThresholds', () => {
expect(getMetricSeverity(80, thresholds)).toBe('yellow');
expect(getMetricSeverity(85, thresholds)).toBe('red');
});
it('keeps visual severity when notification threshold is disabled', () => {
expect(getMetricSeverity(100, null)).toBe('green');
expect(getMetricVisualSeverity(100, 'memory', null)).toBe('red');
});
});

View file

@ -221,3 +221,12 @@ export const getMetricSeverity = (
}
return 'green';
};
export const getMetricVisualSeverity = (
value: number,
metric: DisplayMetricBarType,
thresholds: MetricDisplayThresholds | null | undefined,
): MetricSeverity => {
const displayThresholds = thresholds ?? getDefaultMetricDisplayThresholds(metric);
return getMetricSeverity(value, displayThresholds);
};