From 569fb3f112e0be58947d14213083a2be22beee57 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 21 Apr 2021 13:57:17 +0200 Subject: [PATCH] Alerting: adding query editor when creating threshold rule. (#33123) * fix viz * add datasource picker on query rows in mixed mode * add timerange, handle add/remove queryrunners * multiqueryrunner test * trying things out. * adding another test to verify running a induvidual query runner will update multirunner. * cleaned up tests a bit. * draft version working ok. * fixing so we base the refId from request targets. * reenable adding expression * layout fixes for alerting page * some cleanup * cleaning up code that we won't use * changed so we don't display the time range if params not passed. * remove unused things in querygroup * changed button to type button. * removed timerange from dataQuery and removed multiquery runner. * minor refactoring. * renamed callback function to make it more clear what it does. * renamed droppable area. * changed so we only display the query editor when selecting threshold. * removed the refresh picker. * revert * wip * extending with data query. * timerange fixes * it is now possible to add grafana queries. * removed unused type. * removed expect import. * added docs. * moved range converting methods to rangeUtil. * clean up some typings, remove file * making sure we don't blow up on component being unmounted. Co-authored-by: Marcus Andersson --- .../src/datetime/rangeutil.test.ts | 20 +- .../grafana-data/src/datetime/rangeutil.ts | 37 +++- packages/grafana-data/src/types/time.ts | 9 + .../src/components/Dropdown/ButtonSelect.tsx | 8 +- .../features/alerting/NextGenAlertingPage.tsx | 28 +-- .../components/AlertDefinitionOptions.tsx | 25 +-- .../components/AlertingQueryEditor.tsx | 178 ++++++++++++------ .../components/AlertingQueryPreview.tsx | 24 +-- .../alerting/components/AlertingQueryRows.tsx | 143 ++++++++++++++ .../alerting/components/PreviewQueryTab.tsx | 5 +- public/app/features/alerting/state/actions.ts | 72 +++---- .../app/features/alerting/state/reducers.ts | 44 +---- .../components/rule-editor/AlertRuleForm.tsx | 10 +- .../rule-editor/GrafanaQueryEditor.tsx | 27 --- .../components/rule-editor/QueryStep.tsx | 14 +- .../hooks/useCombinedRuleNamespaces.ts | 2 +- .../alerting/unified/mocks/grafana-queries.ts | 2 +- .../expressions/ExpressionDatasource.ts | 6 +- public/app/features/expressions/guards.ts | 7 + public/app/features/expressions/types.ts | 1 - .../query/components/QueryEditorRow.tsx | 15 +- .../query/components/QueryEditorRowTitle.tsx | 30 ++- .../query/components/QueryEditorRows.tsx | 6 - .../features/query/components/QueryGroup.tsx | 25 ++- public/app/features/sandbox/TestStuffPage.tsx | 72 ++++--- public/app/types/alerting.ts | 4 - public/app/types/unified-alerting-dto.ts | 13 +- 27 files changed, 512 insertions(+), 315 deletions(-) create mode 100644 public/app/features/alerting/components/AlertingQueryRows.tsx delete mode 100644 public/app/features/alerting/unified/components/rule-editor/GrafanaQueryEditor.tsx create mode 100644 public/app/features/expressions/guards.ts diff --git a/packages/grafana-data/src/datetime/rangeutil.test.ts b/packages/grafana-data/src/datetime/rangeutil.test.ts index 63931563880..da1606b6ac7 100644 --- a/packages/grafana-data/src/datetime/rangeutil.test.ts +++ b/packages/grafana-data/src/datetime/rangeutil.test.ts @@ -1,4 +1,4 @@ -import { rangeUtil } from './index'; +import { dateTime, rangeUtil } from './index'; describe('Range Utils', () => { describe('relative time', () => { @@ -40,4 +40,22 @@ describe('Range Utils', () => { expect(() => rangeUtil.describeInterval('xyz')).toThrow(); }); }); + + describe('relativeToTimeRange', () => { + it('should convert seconds to timeRange', () => { + const relativeTimeRange = { from: 600, to: 300 }; + const timeRange = rangeUtil.relativeToTimeRange(relativeTimeRange, dateTime('2021-04-20T15:55:00Z')); + + expect(timeRange.from.valueOf()).toEqual(dateTime('2021-04-20T15:45:00Z').valueOf()); + expect(timeRange.to.valueOf()).toEqual(dateTime('2021-04-20T15:50:00Z').valueOf()); + }); + + it('should convert from now', () => { + const relativeTimeRange = { from: 600, to: 0 }; + const timeRange = rangeUtil.relativeToTimeRange(relativeTimeRange, dateTime('2021-04-20T15:55:00Z')); + + expect(timeRange.from.valueOf()).toEqual(dateTime('2021-04-20T15:45:00Z').valueOf()); + expect(timeRange.to.valueOf()).toEqual(dateTime('2021-04-20T15:55:00Z').valueOf()); + }); + }); }); diff --git a/packages/grafana-data/src/datetime/rangeutil.ts b/packages/grafana-data/src/datetime/rangeutil.ts index bc2c01a8c0d..b1ce1432990 100644 --- a/packages/grafana-data/src/datetime/rangeutil.ts +++ b/packages/grafana-data/src/datetime/rangeutil.ts @@ -1,9 +1,9 @@ import { each, groupBy, has } from 'lodash'; -import { RawTimeRange, TimeRange, TimeZone, IntervalValues } from '../types/time'; +import { RawTimeRange, TimeRange, TimeZone, IntervalValues, RelativeTimeRange } from '../types/time'; import * as dateMath from './datemath'; -import { isDateTime, DateTime } from './moment_wrapper'; +import { isDateTime, DateTime, dateTime } from './moment_wrapper'; import { timeZoneAbbrevation, dateTimeFormat, dateTimeFormatTimeAgo } from './formatter'; import { dateTimeParse } from './parser'; @@ -432,3 +432,36 @@ export function roundInterval(interval: number) { return 31536000000; // 1y } } + +/** + * Converts a TimeRange to a RelativeTimeRange that can be used in + * e.g. alerting queries/rules. + * + * @internal + */ +export function timeRangeToRelative(timeRange: TimeRange): RelativeTimeRange { + const now = dateTime().unix(); + const from = (now - timeRange.from.unix()) / 1000; + const to = (now - timeRange.to.unix()) / 1000; + + return { + from, + to, + }; +} + +/** + * Converts a RelativeTimeRange to a TimeRange + * + * @internal + */ +export function relativeToTimeRange(relativeTimeRange: RelativeTimeRange, now: DateTime = dateTime()): TimeRange { + const from = dateTime(now).subtract(relativeTimeRange.from, 's'); + const to = relativeTimeRange.to === 0 ? dateTime(now) : dateTime(now).subtract(relativeTimeRange.to, 's'); + + return { + from, + to, + raw: { from, to }, + }; +} diff --git a/packages/grafana-data/src/types/time.ts b/packages/grafana-data/src/types/time.ts index b417e7dd201..4f7e93f0f89 100644 --- a/packages/grafana-data/src/types/time.ts +++ b/packages/grafana-data/src/types/time.ts @@ -11,6 +11,15 @@ export interface TimeRange { raw: RawTimeRange; } +/** + * Type to describe relative time to now in seconds. + * @internal + */ +export interface RelativeTimeRange { + from: number; + to: number; +} + export interface AbsoluteTimeRange { from: number; to: number; diff --git a/packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx b/packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx index 3c21fe4fbfe..978a01d4530 100644 --- a/packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx +++ b/packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx @@ -43,7 +43,7 @@ export const ButtonSelect = React.memo((props: Props) => { }; return ( - <> +
(props: Props) => {
)} - + ); }); @@ -79,6 +79,10 @@ ButtonSelect.displayName = 'ButtonSelect'; const getStyles = (theme: GrafanaTheme) => { return { + wrapper: css` + position: relative; + display: inline-flex; + `, menuWrapper: css` position: absolute; z-index: ${theme.zIndex.dropdown}; diff --git a/public/app/features/alerting/NextGenAlertingPage.tsx b/public/app/features/alerting/NextGenAlertingPage.tsx index 8bc5cd61e03..d1e13d68d26 100644 --- a/public/app/features/alerting/NextGenAlertingPage.tsx +++ b/public/app/features/alerting/NextGenAlertingPage.tsx @@ -16,19 +16,17 @@ import { evaluateAlertDefinition, evaluateNotSavedAlertDefinition, getAlertDefinition, - onRunQueries, updateAlertDefinition, updateAlertDefinitionOption, updateAlertDefinitionUiState, } from './state/actions'; import { StoreState } from 'app/types'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import { GrafanaQuery } from '../../types/unified-alerting-dto'; function mapStateToProps(state: StoreState, props: RouteProps) { return { uiState: state.alertDefinition.uiState, - getQueryOptions: state.alertDefinition.getQueryOptions, - queryRunner: state.alertDefinition.queryRunner, getInstances: state.alertDefinition.getInstances, alertDefinition: state.alertDefinition.alertDefinition, pageId: props.match.params.id as string, @@ -43,7 +41,6 @@ const mapDispatchToProps = { createAlertDefinition, getAlertDefinition, evaluateNotSavedAlertDefinition, - onRunQueries, cleanUpDefinitionState, }; @@ -125,18 +122,9 @@ class NextGenAlertingPageUnconnected extends PureComponent { } render() { - const { - alertDefinition, - uiState, - updateAlertDefinitionUiState, - getQueryOptions, - getInstances, - onRunQueries, - queryRunner, - } = this.props; + const { alertDefinition, uiState, updateAlertDefinitionUiState, getInstances } = this.props; const styles = getStyles(config.theme); - const queryOptions = getQueryOptions(); return (
@@ -146,15 +134,8 @@ class NextGenAlertingPageUnconnected extends PureComponent {
, - , + , + {}} />, ]} uiState={uiState} updateUiState={updateAlertDefinitionUiState} @@ -164,7 +145,6 @@ class NextGenAlertingPageUnconnected extends PureComponent { onChange={this.onChangeAlertOption} onIntervalChange={this.onChangeInterval} onConditionChange={this.onConditionChange} - queryOptions={queryOptions} /> } /> diff --git a/public/app/features/alerting/components/AlertDefinitionOptions.tsx b/public/app/features/alerting/components/AlertDefinitionOptions.tsx index dbc09d64c23..db22fb8d801 100644 --- a/public/app/features/alerting/components/AlertDefinitionOptions.tsx +++ b/public/app/features/alerting/components/AlertDefinitionOptions.tsx @@ -1,8 +1,8 @@ -import React, { FC, FormEvent, useMemo } from 'react'; +import React, { FC, FormEvent } from 'react'; import { css } from '@emotion/css'; import { GrafanaTheme, SelectableValue } from '@grafana/data'; import { Field, Input, Select, Tab, TabContent, TabsBar, TextArea, useStyles } from '@grafana/ui'; -import { AlertDefinition, QueryGroupOptions } from 'app/types'; +import { AlertDefinition } from 'app/types'; const intervalOptions: Array> = [ { value: 60, label: '1m' }, @@ -15,20 +15,10 @@ interface Props { onChange: (event: FormEvent) => void; onIntervalChange: (interval: SelectableValue) => void; onConditionChange: (refId: SelectableValue) => void; - queryOptions: QueryGroupOptions; } -export const AlertDefinitionOptions: FC = ({ - alertDefinition, - onChange, - onIntervalChange, - onConditionChange, - queryOptions, -}) => { +export const AlertDefinitionOptions: FC = ({ alertDefinition, onChange, onIntervalChange }) => { const styles = useStyles(getStyles); - const refIds = useMemo(() => queryOptions.queries.map((q) => ({ value: q.refId, label: q.refId })), [ - queryOptions.queries, - ]); return (
@@ -61,14 +51,7 @@ export const AlertDefinitionOptions: FC = ({
-
-