diff --git a/app/components/SystemMetric.tsx b/app/components/SystemMetric.tsx index bd138b0c81..1c4ab7ac66 100644 --- a/app/components/SystemMetric.tsx +++ b/app/components/SystemMetric.tsx @@ -188,7 +188,6 @@ export function SystemMetric({ interpolation="stepAfter" startTime={startTime} endTime={endTime} - maxValue={capacity} unit={unit !== 'count' ? unit : undefined} /> diff --git a/app/components/TimeSeriesChart.tsx b/app/components/TimeSeriesChart.tsx index 5843adbf84..026267cfc0 100644 --- a/app/components/TimeSeriesChart.tsx +++ b/app/components/TimeSeriesChart.tsx @@ -100,7 +100,7 @@ function renderTooltip(props: TooltipProps, unit?: string) { ) } -type Props = { +type TimeSeriesChartProps = { className?: string data: ChartDatum[] | undefined title: string @@ -109,10 +109,16 @@ type Props = { interpolation?: 'linear' | 'stepAfter' startTime: Date endTime: Date - maxValue?: number unit?: string } +const TICK_COUNT = 6 + +/** Round `value` up to nearest number divisible by `divisor` */ +function roundUpToDivBy(value: number, divisor: number) { + return Math.ceil(value / divisor) * divisor +} + export default function TimeSeriesChart({ className, data: rawData, @@ -122,20 +128,23 @@ export default function TimeSeriesChart({ interpolation = 'linear', startTime, endTime, - maxValue, unit, -}: Props) { +}: TimeSeriesChartProps) { + // We use the largest data point +20% for the graph scale. !rawData doesn't + // mean it's empty (it will never be empty because we fill in artificial 0s at + // beginning and end), it means the metrics requests haven't come back yet + const maxY = useMemo(() => { + if (!rawData) return null + const dataMax = Math.max(...rawData.map((datum) => datum.value)) + return roundUpToDivBy(dataMax * 1.2, TICK_COUNT) // avoid uneven ticks + }, [rawData]) + // If max value is set we normalize the graph so that // is the maximum, we also use our own function as recharts - // doesn't fill the whole domain (just upto the data max) - const yTicks = maxValue - ? { - domain: [0, maxValue], - ticks: getVerticalTicks(6, maxValue), - } - : { - tickSize: 6, - } + // doesn't fill the whole domain (just up to the data max) + const yTicks = maxY + ? { domain: [0, maxY], ticks: getVerticalTicks(TICK_COUNT, maxY) } + : undefined // falling back here instead of in the parent lets us avoid causing a // re-render on every render of the parent when the data is undefined diff --git a/libs/api-mocks/msw/util.ts b/libs/api-mocks/msw/util.ts index 77d61271bf..9278931c28 100644 --- a/libs/api-mocks/msw/util.ts +++ b/libs/api-mocks/msw/util.ts @@ -197,7 +197,7 @@ export function generateUtilization( const valueInterval = Math.floor(dataCount / timeInterval) // Pick a reasonable start value - const startVal = cap / 2 + const startVal = 500 const values = new Array(dataCount) values[0] = startVal @@ -213,15 +213,10 @@ export function generateUtilization( const threshold = i < 250 || (i > 500 && i < 750) ? 1 : 0.375 if (random < threshold) { - const amount = - metricName === 'cpus_provisioned' - ? 3 - : metricName === 'virtual_disk_space_provisioned' - ? TiB - : TiB / 20 + const amount = 50 offset = Math.floor(random * amount) - if (random < threshold / 3) { + if (random < threshold / 2.5) { offset = offset * -1 } } @@ -237,7 +232,14 @@ export function generateUtilization( } } - return values + // Find the current maximum value in the generated data + const currentMax = Math.max(...values) + + // Normalize the data to sit within the range of 0 to overall capacity + const randomFactor = Math.random() * (1 - 0.33) + 0.33 + const normalizedValues = values.map((value) => (value / currentMax) * cap * randomFactor) + + return normalizedValues } type MetricParams = {