diff --git a/packages/grafana-data/src/types/annotations.ts b/packages/grafana-data/src/types/annotations.ts index e701b6b3841..f5c3a72c51b 100644 --- a/packages/grafana-data/src/types/annotations.ts +++ b/packages/grafana-data/src/types/annotations.ts @@ -77,6 +77,12 @@ export interface AnnotationEventFieldMapping { } export type AnnotationEventMappings = Partial>; +type AnnotationQueryEditorProps = QueryEditorProps & { + // Needs to be optional otherwise component not using these cannot be used, even though they are passed on and can be + // just ignored if not used. + annotation?: AnnotationQuery; + onAnnotationChange?: (annotation: AnnotationQuery) => void; +}; /** * Since Grafana 7.2 @@ -86,7 +92,7 @@ export type AnnotationEventMappings = Partial> { /** * This hook lets you manipulate any existing stored values before running them though the processor. - * This is particularly helpful when dealing with migrating old formats. ie query as a string vs object + * This is particularly helpful when dealing with migrating old formats. ie query as a string vs object. */ prepareAnnotation?(json: any): TAnno; @@ -105,5 +111,5 @@ export interface AnnotationSupport>; + QueryEditor?: ComponentType>; } diff --git a/public/app/features/annotations/components/StandardAnnotationQueryEditor.tsx b/public/app/features/annotations/components/StandardAnnotationQueryEditor.tsx index 3cf223b504c..3d4b60e2fad 100644 --- a/public/app/features/annotations/components/StandardAnnotationQueryEditor.tsx +++ b/public/app/features/annotations/components/StandardAnnotationQueryEditor.tsx @@ -9,7 +9,7 @@ import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { PanelModel } from 'app/features/dashboard/state'; import { executeAnnotationQuery } from '../executeAnnotationQuery'; -import { standardAnnotationSupport } from '../standardAnnotationSupport'; +import { shouldUseLegacyRunner, shouldUseMappingUI, standardAnnotationSupport } from '../standardAnnotationSupport'; import { AnnotationQueryResponse } from '../types'; import { AnnotationFieldMapper } from './AnnotationResultMapper'; @@ -33,7 +33,7 @@ export default class StandardAnnotationQueryEditor extends PureComponent { const { datasource, annotation } = this.props; + if (shouldUseLegacyRunner(datasource)) { + // In the new UI the running of query is done so the data can be mapped. In the legacy annotations this does + // not exist as the annotationQuery already returns annotation events which cannot be mapped. This means that + // right now running a query for data source with legacy runner does not make much sense. + return; + } + const dashboard = getDashboardSrv().getCurrent(); if (!dashboard) { return; @@ -156,11 +163,15 @@ export default class StandardAnnotationQueryEditor extends PureComponent { + this.props.onChange(annotation); + }; + render() { const { datasource, annotation } = this.props; const { response } = this.state; - // Find the annotaiton runner + // Find the annotation runner let QueryEditor = datasource.annotations?.QueryEditor || datasource.components?.QueryEditor; if (!QueryEditor) { return
Annotations are not supported. This datasource needs to export a QueryEditor
; @@ -177,8 +188,10 @@ export default class StandardAnnotationQueryEditor extends PureComponent - {datasource.type !== 'datasource' && ( + {shouldUseMappingUI(datasource) && ( <> {this.renderStatus()} diff --git a/public/app/features/annotations/standardAnnotationSupport.ts b/public/app/features/annotations/standardAnnotationSupport.ts index 079a506a94b..710ded708ed 100644 --- a/public/app/features/annotations/standardAnnotationSupport.ts +++ b/public/app/features/annotations/standardAnnotationSupport.ts @@ -9,6 +9,7 @@ import { AnnotationQuery, AnnotationSupport, DataFrame, + DataSourceApi, Field, FieldType, getFieldDisplayName, @@ -26,6 +27,7 @@ export const standardAnnotationSupport: AnnotationSupport = { return { ...rest, target: { + refId: 'annotation_query', query, }, mappings: {}, @@ -35,14 +37,12 @@ export const standardAnnotationSupport: AnnotationSupport = { }, /** - * Convert the stored JSON model and environment to a standard data source query object. - * This query will be executed in the data source and the results converted into events. - * Returning an undefined result will quietly skip query execution + * Default will just return target from the annotation. */ prepareQuery: (anno: AnnotationQuery) => anno.target, /** - * When the standard frame > event processing is insufficient, this allows explicit control of the mappings + * Provides default processing from dataFrame to annotation events. */ processEvents: (anno: AnnotationQuery, data: DataFrame[]) => { return getAnnotationsFromData(data, anno.mappings); @@ -50,7 +50,7 @@ export const standardAnnotationSupport: AnnotationSupport = { }; /** - * Flatten all panel data into a single frame + * Flatten all frames into a single frame with mergeTransformer. */ export function singleFrameFromPanelData(): OperatorFunction { @@ -226,3 +226,21 @@ export function getAnnotationsFromData( }) ); } + +// These opt outs are here only for quicker and easier migration to react based annotations editors and because +// annotation support API needs some work to support less "standard" editors like prometheus and here it is not +// polluting public API. + +/** + * Opt out of using the default mapping functionality on frontend. + */ +export function shouldUseMappingUI(datasource: DataSourceApi): boolean { + return datasource.type !== 'prometheus'; +} + +/** + * Use legacy runner. Used only as an escape hatch for easier transition to React based annotation editor. + */ +export function shouldUseLegacyRunner(datasource: DataSourceApi): boolean { + return datasource.type === 'prometheus'; +} diff --git a/public/app/features/query/state/DashboardQueryRunner/AnnotationsQueryRunner.ts b/public/app/features/query/state/DashboardQueryRunner/AnnotationsQueryRunner.ts index 4eadff44e59..05b5bf7a3bc 100644 --- a/public/app/features/query/state/DashboardQueryRunner/AnnotationsQueryRunner.ts +++ b/public/app/features/query/state/DashboardQueryRunner/AnnotationsQueryRunner.ts @@ -15,7 +15,7 @@ export class AnnotationsQueryRunner implements AnnotationQueryRunner { return false; } - return !Boolean(datasource.annotationQuery && !datasource.annotations); + return Boolean(!datasource.annotationQuery || datasource.annotations); } run({ annotation, datasource, dashboard, range }: AnnotationQueryRunnerOptions): Observable { diff --git a/public/app/features/query/state/DashboardQueryRunner/LegacyAnnotationQueryRunner.ts b/public/app/features/query/state/DashboardQueryRunner/LegacyAnnotationQueryRunner.ts index 5d94911b9ab..f383897f5d9 100644 --- a/public/app/features/query/state/DashboardQueryRunner/LegacyAnnotationQueryRunner.ts +++ b/public/app/features/query/state/DashboardQueryRunner/LegacyAnnotationQueryRunner.ts @@ -2,6 +2,7 @@ import { from, Observable, of } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { AnnotationEvent, DataSourceApi } from '@grafana/data'; +import { shouldUseLegacyRunner } from 'app/features/annotations/standardAnnotationSupport'; import { AnnotationQueryRunner, AnnotationQueryRunnerOptions } from './types'; import { handleAnnotationQueryRunnerError } from './utils'; @@ -12,6 +13,10 @@ export class LegacyAnnotationQueryRunner implements AnnotationQueryRunner { return false; } + if (shouldUseLegacyRunner(datasource)) { + return true; + } + return Boolean(datasource.annotationQuery && !datasource.annotations); } diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx index 4f43cf5e0d3..fbb5f1d4f82 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx @@ -99,7 +99,15 @@ export const LokiQueryEditorSelector = React.memo((props) > Run query - + diff --git a/public/app/plugins/datasource/prometheus/components/AnnotationQueryEditor.tsx b/public/app/plugins/datasource/prometheus/components/AnnotationQueryEditor.tsx new file mode 100644 index 00000000000..cd7bfc987d4 --- /dev/null +++ b/public/app/plugins/datasource/prometheus/components/AnnotationQueryEditor.tsx @@ -0,0 +1,121 @@ +import React from 'react'; + +import { AnnotationQuery } from '@grafana/data'; +import { EditorRow, EditorField, EditorSwitch, Space } from '@grafana/experimental'; +import { Input } from '@grafana/ui'; + +import { PromQueryEditorSelector } from '../querybuilder/components/PromQueryEditorSelector'; +import { QueryEditorMode } from '../querybuilder/shared/types'; +import { PromQuery } from '../types'; + +import { PromQueryEditorProps } from './types'; + +type Props = PromQueryEditorProps & { + annotation?: AnnotationQuery; + onAnnotationChange?: (annotation: AnnotationQuery) => void; +}; + +export function AnnotationQueryEditor(props: Props) { + // This is because of problematic typing. See AnnotationQueryEditorProps in grafana-data/annotations.ts. + const annotation = props.annotation!; + const onAnnotationChange = props.onAnnotationChange!; + return ( + <> + + onAnnotationChange({ + ...annotation, + expr: query.expr, + step: query.interval, + }) + } + uiOptions={{ + modes: { + [QueryEditorMode.Explain]: false, + [QueryEditorMode.Code]: true, + [QueryEditorMode.Builder]: true, + }, + runQueryButton: false, + options: { + exemplars: false, + type: false, + format: false, + minStep: true, + legend: false, + resolution: false, + }, + }} + /> + + + + { + onAnnotationChange({ + ...annotation, + titleFormat: event.currentTarget.value, + }); + }} + /> + + + { + onAnnotationChange({ + ...annotation, + tagKeys: event.currentTarget.value, + }); + }} + /> + + + { + onAnnotationChange({ + ...annotation, + textFormat: event.currentTarget.value, + }); + }} + /> + + + { + onAnnotationChange({ + ...annotation, + useValueForTime: event.currentTarget.value, + }); + }} + /> + + + + ); +} diff --git a/public/app/plugins/datasource/prometheus/datasource.tsx b/public/app/plugins/datasource/prometheus/datasource.tsx index 420d203c817..209112270d2 100644 --- a/public/app/plugins/datasource/prometheus/datasource.tsx +++ b/public/app/plugins/datasource/prometheus/datasource.tsx @@ -40,6 +40,7 @@ import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_sr import { PromApplication, PromApiFeatures } from 'app/types/unified-alerting-dto'; import { addLabelToQuery } from './add_label_to_query'; +import { AnnotationQueryEditor } from './components/AnnotationQueryEditor'; import PrometheusLanguageProvider from './language_provider'; import { expandRecordingRules } from './language_utils'; import { renderLegendFormat } from './legend'; @@ -119,6 +120,14 @@ export class PrometheusDatasource this.customQueryParameters = new URLSearchParams(instanceSettings.jsonData.customQueryParameters); this.variables = new PrometheusVariableSupport(this, this.templateSrv, this.timeSrv); this.exemplarsAvailable = true; + + // This needs to be here and cannot be static because of how annotations typing affects casting of data source + // objects to DataSourceApi types. + // We don't use the default processing for prometheus. + // See standardAnnotationSupport.ts/[shouldUseMappingUI|shouldUseLegacyRunner] + this.annotations = { + QueryEditor: AnnotationQueryEditor, + }; } init = async () => { diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/LabelParamEditor.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/LabelParamEditor.tsx index ab95e2798e4..941bb1e11f3 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/LabelParamEditor.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/LabelParamEditor.tsx @@ -3,7 +3,6 @@ import React, { useState } from 'react'; import { DataSourceApi, SelectableValue, toOption } from '@grafana/data'; import { Select } from '@grafana/ui'; -import { PrometheusDatasource } from '../../datasource'; import { promQueryModeller } from '../PromQueryModeller'; import { getOperationParamId } from '../shared/operationUtils'; import { QueryBuilderLabelFilter, QueryBuilderOperationParamEditorProps } from '../shared/types'; @@ -49,8 +48,8 @@ async function loadGroupByLabels( ): Promise>> { let labels: QueryBuilderLabelFilter[] = query.labels; - // This function is used by both Prometheus and Loki and this the only difference - if (datasource instanceof PrometheusDatasource) { + // This function is used by both Prometheus and Loki and this the only difference. + if (datasource.type === 'prometheus') { labels = [{ label: '__name__', op: '=', value: query.metric }, ...query.labels]; } diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.test.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.test.tsx index a4df0c00284..9fc5fe05d38 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.test.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.test.tsx @@ -78,6 +78,14 @@ function setup(queryOverrides: Partial = {}) { }, onRunQuery: jest.fn(), onChange: jest.fn(), + uiOptions: { + exemplars: true, + type: true, + format: true, + minStep: true, + legend: true, + resolution: true, + }, }; const { container } = render(); diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx index 67ef8b17e7b..17b3b296eea 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx @@ -12,14 +12,24 @@ import { QueryOptionGroup } from '../shared/QueryOptionGroup'; import { getLegendModeLabel, PromQueryLegendEditor } from './PromQueryLegendEditor'; +export interface UIOptions { + exemplars: boolean; + type: boolean; + format: boolean; + minStep: boolean; + legend: boolean; + resolution: boolean; +} + export interface Props { query: PromQuery; app?: CoreApp; onChange: (update: PromQuery) => void; onRunQuery: () => void; + uiOptions: UIOptions; } -export const PromQueryBuilderOptions = React.memo(({ query, app, onChange, onRunQuery }) => { +export const PromQueryBuilderOptions = React.memo(({ query, app, onChange, onRunQuery, uiOptions }) => { const onChangeFormat = (value: SelectableValue) => { onChange({ ...query, format: value.value }); onRunQuery(); @@ -50,42 +60,53 @@ export const PromQueryBuilderOptions = React.memo(({ query, app, onChange return ( - - onChange({ ...query, legendFormat })} - onRunQuery={onRunQuery} - /> - - An additional lower limit for the step parameter of the Prometheus query and for the{' '} - $__interval and $__rate_interval variables. - - } - > - + {uiOptions.legend && ( + onChange({ ...query, legendFormat })} + onRunQuery={onRunQuery} /> - - - + + )} + {uiOptions.type && ( + + + + )} + {uiOptions.exemplars && shouldShowExemplarSwitch(query, app) && ( )} - {query.intervalFactor && query.intervalFactor > 1 && ( + {uiOptions.resolution && query.intervalFactor && query.intervalFactor > 1 && (