Skip to content

Commit 9908420

Browse files
oKennethJenkins
authored andcommitted
graphing: fix missing time range crash
upon removal/edit of the global settings' "Custom graph time ranges" a crash could occur for graph widget refering to a now missing time range. this is fixed both in the backend - fall back to _TimerangeAge - and the frontend - fall back to a custom "The last ..." time range based on the given duration seconds. CMK-37487 Change-Id: Idc18098be17ba5dc32d5c4a794c625b7f720a25c
1 parent 78d9745 commit 9908420

2 files changed

Lines changed: 57 additions & 9 deletions

File tree

cmk/gui/openapi/framework/model/common_fields.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,16 @@ def to_internal(self) -> TimerangeValue:
346346
age += (self.days or 0) * 86400
347347
return "age", age
348348

349+
@classmethod
350+
def from_seconds(cls, duration: int) -> "_TimerangeAge":
351+
return cls(
352+
timerange_type="age",
353+
days=duration // 86400,
354+
hours=(duration % 86400) // 3600,
355+
minutes=(duration % 3600) // 60,
356+
seconds=duration % 60,
357+
)
358+
349359

350360
@api_model
351361
class _TimerangeDate(_BaseTimerangeValue):
@@ -469,16 +479,16 @@ def timerange_from_internal(
469479
if lookup == value:
470480
return _TimerangePredefined(timerange_type="predefined", value=key)
471481
case int() as duration:
472-
return _TimerangeGraph(timerange_type="graph", duration=duration)
482+
# A stored duration that is no longer one of the configured graph time ranges (an admin
483+
# edited or removed it under Setup > Global settings > Graph time ranges) would fail
484+
# _TimerangeGraph validation. Emit it as a plain relative age instead, so the widget
485+
# still renders "now - duration" rather than erroring on dashboard load or data fetch.
486+
if any(tr["duration"] == duration for tr in active_config.graph_timeranges):
487+
return _TimerangeGraph(timerange_type="graph", duration=duration)
488+
return _TimerangeAge.from_seconds(duration)
473489
# mypy doesn't understand that this can only be an int
474490
case ("age", age) if isinstance(age, int):
475-
return _TimerangeAge(
476-
timerange_type="age",
477-
days=age // 86400,
478-
hours=(age % 86400) // 3600,
479-
minutes=(age % 3600) // 60,
480-
seconds=age % 60,
481-
)
491+
return _TimerangeAge.from_seconds(age)
482492
case ("date", (start, end)):
483493
return _TimerangeDate(
484494
timerange_type="date",

packages/cmk-frontend-vue/src/dashboard/components/TimeRange/GraphTimeRange.vue

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,29 @@ const durationOptionName = (totalSeconds: number): string => `duration_${totalSe
7272
const isDurationPredefinedKey = (key: PreDefinedTimeRange): key is PredefinedDurationKey =>
7373
key in predefinedDurationSeconds
7474
75+
const secondsToAge = (totalSeconds: number): Age => ({
76+
days: Math.floor(totalSeconds / 86400),
77+
hours: Math.floor((totalSeconds % 86400) / 3600),
78+
minutes: Math.floor((totalSeconds % 3600) / 60),
79+
seconds: totalSeconds % 60
80+
})
81+
82+
// The trailing-duration length a range denotes - a "last N ..." range stored either as a duration
83+
// or as a trailing-duration predefined key - or null for calendar / custom (age, date) ranges.
84+
const trailingDurationSeconds = (timerange: GraphTimerange): number | null => {
85+
if (timerange.type === 'duration') {
86+
return timerange.duration
87+
}
88+
if (
89+
timerange.type === 'predefined' &&
90+
timerange.predefined !== null &&
91+
isDurationPredefinedKey(timerange.predefined)
92+
) {
93+
return predefinedDurationSeconds[timerange.predefined]
94+
}
95+
return null
96+
}
97+
7598
interface GraphTimerangeApiResult {
7699
title: string
77100
extensions: {
@@ -175,7 +198,22 @@ onMounted(async () => {
175198
apiDurationTimeranges.value = await loadApiDurationGraphTimeranges()
176199
177200
if (selectedTimerange.value) {
178-
selectedDropdownOption.value = getDropdownOptionFromTimerange(selectedTimerange.value)
201+
const option = getDropdownOptionFromTimerange(selectedTimerange.value)
202+
const optionAvailable =
203+
option !== null && dropdownOptions.value.some((suggestion) => suggestion.name === option)
204+
const fallbackSeconds = optionAvailable
205+
? null
206+
: trailingDurationSeconds(selectedTimerange.value)
207+
208+
if (fallbackSeconds !== null) {
209+
// The stored trailing-duration range was edited or removed under Setup > Global settings >
210+
// Graph time ranges, so it no longer matches a configured option. Fall back to an equivalent
211+
// custom "The last ..." range instead of rendering a dropdown option 'duration_<n>'.
212+
customDuration.value = secondsToAge(fallbackSeconds)
213+
selectedDropdownOption.value = customTimeOptionName
214+
} else {
215+
selectedDropdownOption.value = option
216+
}
179217
if (selectedTimerange.value.type === 'age' && selectedTimerange.value.age) {
180218
customDuration.value = { ...selectedTimerange.value.age }
181219
} else if (selectedTimerange.value.type === 'date' && selectedTimerange.value.date_range) {

0 commit comments

Comments
 (0)