Alerting: Fix incorrect time range display for queries with custom 'to' values (#110930)
* Add TimeRangeLabel component to display query time ranges * Remove query timerange windening in the rule viewer * Update translations
This commit is contained in:
@@ -2,7 +2,7 @@ import { css, cx } from '@emotion/css';
|
||||
import { keyBy, startCase, uniqueId } from 'lodash';
|
||||
import * as React from 'react';
|
||||
|
||||
import { DataSourceInstanceSettings, GrafanaTheme2, PanelData, rangeUtil, urlUtil } from '@grafana/data';
|
||||
import { DataSourceInstanceSettings, GrafanaTheme2, PanelData, urlUtil } from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { DataSourceRef } from '@grafana/schema';
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
import alertDef, { EvalFunction } from '../state/alertDef';
|
||||
|
||||
import { Spacer } from './components/Spacer';
|
||||
import { TimeRangeLabel } from './components/TimeRangeLabel';
|
||||
import { WithReturnButton } from './components/WithReturnButton';
|
||||
import { ExpressionResult } from './components/expressions/Expression';
|
||||
import { ThresholdDefinition, getThresholdsForQueries } from './components/rule-editor/util';
|
||||
@@ -123,12 +124,7 @@ export function QueryPreview({
|
||||
if (relativeTimeRange) {
|
||||
headerItems.push(
|
||||
<Text color="secondary" key="timerange">
|
||||
<Trans
|
||||
i18nKey="alerting.query-preview.relative-time-range"
|
||||
values={{ from: rangeUtil.secondsToHms(relativeTimeRange.from) }}
|
||||
>
|
||||
<code>{'{{from}}'}</code> to now
|
||||
</Trans>
|
||||
<TimeRangeLabel relativeTimeRange={relativeTimeRange} />
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { render, screen } from 'test/test-utils';
|
||||
|
||||
import { TimeRangeLabel } from './TimeRangeLabel';
|
||||
|
||||
describe('TimeRangeLabel', () => {
|
||||
it('renders "to now" when to is 0', () => {
|
||||
render(<TimeRangeLabel relativeTimeRange={{ from: 900, to: 0 }} />);
|
||||
|
||||
// 900 seconds -> 15m
|
||||
expect(screen.getByText(/to now/i)).toBeInTheDocument();
|
||||
expect(screen.getByText('15m')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders "to <to>" when to > 0', () => {
|
||||
render(<TimeRangeLabel relativeTimeRange={{ from: 900, to: 60 }} />);
|
||||
|
||||
// 900 seconds -> 15m, 60 seconds -> 1m
|
||||
const container = screen.getByText(/to/i).closest('span') || screen.getByText(/to/i).parentElement || document.body;
|
||||
expect(container).toHaveTextContent(/15m to 1m/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import { RelativeTimeRange, rangeUtil } from '@grafana/data';
|
||||
import { Trans } from '@grafana/i18n';
|
||||
|
||||
interface RuleTimeRangeLabelProps {
|
||||
relativeTimeRange: RelativeTimeRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a human-readable relative time range label like:
|
||||
* - "15m to now" when to === 0 or not set
|
||||
* - "15m to 1m" when to > 0
|
||||
*/
|
||||
export function TimeRangeLabel({ relativeTimeRange }: RuleTimeRangeLabelProps) {
|
||||
const fromLabel = rangeUtil.secondsToHms(relativeTimeRange.from);
|
||||
const toSeconds = relativeTimeRange.to ?? 0;
|
||||
const toIsNow = !toSeconds || toSeconds <= 0;
|
||||
const toLabel = toIsNow ? 'now' : rangeUtil.secondsToHms(toSeconds);
|
||||
|
||||
if (toIsNow) {
|
||||
return (
|
||||
<Trans i18nKey="alerting.rule-time-range-label.relative" values={{ from: fromLabel }}>
|
||||
<code>{'{{from}}'}</code> to now
|
||||
</Trans>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Trans i18nKey="alerting.rule-time-range-label.relative-with-to" values={{ from: fromLabel, to: toLabel }}>
|
||||
<code>{'{{from}}'}</code> to <code>{'{{to}}'}</code>
|
||||
</Trans>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2, RelativeTimeRange, dateTime, getDefaultRelativeTimeRange, rangeUtil } from '@grafana/data';
|
||||
import { GrafanaTheme2, RelativeTimeRange, getDefaultRelativeTimeRange } from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { Icon, InlineField, RelativeTimeRangePicker, Toggletip, clearButtonStyles, useStyles2 } from '@grafana/ui';
|
||||
import { AlertQuery } from 'app/types/unified-alerting-dto';
|
||||
|
||||
import { TimeRangeLabel } from '../TimeRangeLabel';
|
||||
|
||||
import { AlertQueryOptions, MaxDataPointsOption, MinIntervalOption } from './QueryWrapper';
|
||||
|
||||
export interface QueryOptionsProps {
|
||||
@@ -27,8 +29,6 @@ export const QueryOptions = ({
|
||||
|
||||
const [showOptions, setShowOptions] = useState(false);
|
||||
|
||||
const timeRange = query.relativeTimeRange ? rangeUtil.relativeToTimeRange(query.relativeTimeRange) : undefined;
|
||||
|
||||
const separator = <span>, </span>;
|
||||
|
||||
return (
|
||||
@@ -58,7 +58,9 @@ export const QueryOptions = ({
|
||||
</Toggletip>
|
||||
|
||||
<div className={styles.staticValues}>
|
||||
<span>{dateTime(timeRange?.from).locale('en').fromNow(true)}</span>
|
||||
<span>
|
||||
<TimeRangeLabel relativeTimeRange={query.relativeTimeRange ?? getDefaultRelativeTimeRange()} />
|
||||
</span>
|
||||
|
||||
{queryOptions.maxDataPoints && (
|
||||
<>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { produce } from 'immer';
|
||||
|
||||
import { DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { PromQuery } from '@grafana/prometheus';
|
||||
import { DataQuery } from '@grafana/schema';
|
||||
@@ -9,7 +7,6 @@ import { AlertQuery } from 'app/types/unified-alerting-dto';
|
||||
|
||||
import { isCloudRulesSource, isSupportedExternalRulesSourceType } from './datasource';
|
||||
import { rulerRuleType } from './rules';
|
||||
import { safeParsePrometheusDuration } from './time';
|
||||
|
||||
export function alertRuleToQueries(combinedRule: CombinedRule | undefined | null): AlertQuery[] {
|
||||
if (!combinedRule) {
|
||||
@@ -19,8 +16,7 @@ export function alertRuleToQueries(combinedRule: CombinedRule | undefined | null
|
||||
const { rulesSource } = namespace;
|
||||
|
||||
if (rulerRuleType.grafana.rule(rulerRule)) {
|
||||
const query = rulerRule.grafana_alert.data;
|
||||
return widenRelativeTimeRanges(query, rulerRule.for ?? '', combinedRule.group.interval);
|
||||
return rulerRule.grafana_alert.data;
|
||||
}
|
||||
|
||||
if (isCloudRulesSource(rulesSource)) {
|
||||
@@ -32,37 +28,6 @@ export function alertRuleToQueries(combinedRule: CombinedRule | undefined | null
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will figure out how large the time range for visualizing the alert rule detail view should be
|
||||
* We try to show as much data as is relevant for triaging / root cause analysis
|
||||
*
|
||||
* The function for it is;
|
||||
*
|
||||
* Math.max(3 * pending period, query range + (2 * pending period))
|
||||
*
|
||||
* We can safely ignore the evaluation interval because the pending period is guaranteed to be largen than or equal that
|
||||
*/
|
||||
export function widenRelativeTimeRanges(queries: AlertQuery[], pendingPeriod: string, groupInterval?: string) {
|
||||
// if pending period is zero that means inherit from group interval, if that is empty then assume 1m
|
||||
const pendingPeriodDurationMillis =
|
||||
safeParsePrometheusDuration(pendingPeriod) ?? safeParsePrometheusDuration(groupInterval ?? '1m');
|
||||
const pendingPeriodDuration = Math.floor(pendingPeriodDurationMillis / 1000);
|
||||
|
||||
return queries.map((query) =>
|
||||
produce(query, (draft) => {
|
||||
const fromQueryRange = draft.relativeTimeRange?.from ?? 0;
|
||||
|
||||
// use whichever has the largest time range
|
||||
const from = Math.max(pendingPeriodDuration * 3, fromQueryRange + pendingPeriodDuration * 2);
|
||||
|
||||
draft.relativeTimeRange = {
|
||||
from,
|
||||
to: 0,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function dataQueryToAlertQuery(dataQuery: DataQuery, dataSourceUid: string): AlertQuery {
|
||||
return {
|
||||
refId: dataQuery.refId,
|
||||
|
||||
@@ -2213,9 +2213,6 @@
|
||||
"max-data-points": "MD = {{maxDataPoints}}",
|
||||
"min-interval": "Min. Interval = {{minInterval}}"
|
||||
},
|
||||
"query-preview": {
|
||||
"relative-time-range": "<0>{{from}}</0> to now"
|
||||
},
|
||||
"queryAndExpressionsStep": {
|
||||
"disableAdvancedOptions": {
|
||||
"text": "The selected queries and expressions cannot be converted to default. If you deactivate advanced options, your query and condition will be reset to default settings."
|
||||
@@ -2561,6 +2558,10 @@
|
||||
"recording": "{{recordingStats}} recording",
|
||||
"recovering": "{{recoveringStats}} recovering"
|
||||
},
|
||||
"rule-time-range-label": {
|
||||
"relative": "<0>{{from}}</0> to now",
|
||||
"relative-with-to": "<0>{{from}}</0> to <2>{{to}}</2>"
|
||||
},
|
||||
"rule-type-picker": {
|
||||
"grafana-managed": "Select “Grafana managed” unless you have a Mimir, Loki or Cortex data source with the Ruler API enabled."
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user