From 990b3c07ab4e8db651616c84fbef67132dfeb9f1 Mon Sep 17 00:00:00 2001 From: Andre Pereira Date: Mon, 24 Apr 2023 14:11:28 +0100 Subject: [PATCH] Tempo: Add kind to TraceQL intrinsics (#65111) * Add kind to TraceQL intrinsics * Fix after merging main * Fix intrinsics array reference after merge * Dispatch error message when TraceQL autocomplete fails to retrieve tag values --- .../SearchTraceQLEditor/TraceQLSearch.tsx | 5 ++-- .../tempo/SearchTraceQLEditor/utils.ts | 4 +-- .../tempo/configuration/TraceQLSearchTags.tsx | 4 +-- .../tempo/traceql/autocomplete.test.ts | 9 +++--- .../datasource/tempo/traceql/autocomplete.ts | 29 ++++++++++++++----- .../datasource/tempo/traceql/traceql.ts | 4 +-- 6 files changed, 34 insertions(+), 21 deletions(-) diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.tsx index dfe9e398dbe..9148c90fcd2 100644 --- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.tsx +++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.tsx @@ -13,8 +13,7 @@ import { RawQuery } from '../../prometheus/querybuilder/shared/RawQuery'; import { TraceqlFilter } from '../dataquery.gen'; import { TempoDatasource } from '../datasource'; import { TempoQueryBuilderOptions } from '../traceql/TempoQueryBuilderOptions'; -import { CompletionProvider } from '../traceql/autocomplete'; -import { traceqlGrammar } from '../traceql/traceql'; +import { intrinsics, traceqlGrammar } from '../traceql/traceql'; import { TempoQuery } from '../types'; import DurationInput from './DurationInput'; @@ -102,7 +101,7 @@ const TraceQLSearch = ({ datasource, query, onChange }: Props) => { // filter out tags that already exist in the static fields const staticTags = datasource.search?.filters?.map((f) => f.tag) || []; staticTags.push('duration'); - const filteredTags = [...CompletionProvider.intrinsics, ...tags].filter((t) => !staticTags.includes(t)); + const filteredTags = [...intrinsics, ...tags].filter((t) => !staticTags.includes(t)); // Dynamic filters are all filters that don't match the ID of a filter in the datasource configuration // The duration tag is a special case since its selector is hard-coded diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/utils.ts b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/utils.ts index a49bbd94519..088018a68f3 100644 --- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/utils.ts +++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/utils.ts @@ -3,7 +3,7 @@ import { startCase } from 'lodash'; import { SelectableValue } from '@grafana/data'; import { TraceqlFilter, TraceqlSearchScope } from '../dataquery.gen'; -import { CompletionProvider } from '../traceql/autocomplete'; +import { intrinsics } from '../traceql/traceql'; export const generateQueryFromFilters = (filters: TraceqlFilter[]) => { return `{${filters @@ -23,7 +23,7 @@ const valueHelper = (f: TraceqlFilter) => { }; const scopeHelper = (f: TraceqlFilter) => { // Intrinsic fields don't have a scope - if (CompletionProvider.intrinsics.find((t) => t === f.tag)) { + if (intrinsics.find((t) => t === f.tag)) { return ''; } return ( diff --git a/public/app/plugins/datasource/tempo/configuration/TraceQLSearchTags.tsx b/public/app/plugins/datasource/tempo/configuration/TraceQLSearchTags.tsx index b90e61a2783..30ffaa7198f 100644 --- a/public/app/plugins/datasource/tempo/configuration/TraceQLSearchTags.tsx +++ b/public/app/plugins/datasource/tempo/configuration/TraceQLSearchTags.tsx @@ -8,7 +8,7 @@ import TagsInput from '../SearchTraceQLEditor/TagsInput'; import { replaceAt } from '../SearchTraceQLEditor/utils'; import { TraceqlFilter, TraceqlSearchScope } from '../dataquery.gen'; import { TempoDatasource } from '../datasource'; -import { CompletionProvider } from '../traceql/autocomplete'; +import { intrinsics } from '../traceql/traceql'; import { TempoJsonData } from '../types'; interface Props extends DataSourcePluginOptionsEditorProps { @@ -94,7 +94,7 @@ export function TraceQLSearchTags({ options, onOptionsChange, datasource }: Prop filters={options.jsonData.search?.filters || []} datasource={datasource} setError={() => {}} - tags={[...CompletionProvider.intrinsics, ...(tags || [])]} + tags={[...intrinsics, ...(tags || [])]} isTagsLoading={loading} hideValues={true} /> diff --git a/public/app/plugins/datasource/tempo/traceql/autocomplete.test.ts b/public/app/plugins/datasource/tempo/traceql/autocomplete.test.ts index 1533b21cb76..9524505d16b 100644 --- a/public/app/plugins/datasource/tempo/traceql/autocomplete.test.ts +++ b/public/app/plugins/datasource/tempo/traceql/autocomplete.test.ts @@ -6,6 +6,7 @@ import TempoLanguageProvider from '../language_provider'; import { TempoJsonData } from '../types'; import { CompletionProvider } from './autocomplete'; +import { intrinsics, scopes } from './traceql'; jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), @@ -19,8 +20,8 @@ describe('CompletionProvider', () => { {} as monacoTypes.Position ); expect((result! as monacoTypes.languages.CompletionList).suggestions).toEqual([ - ...CompletionProvider.scopes.map((s) => expect.objectContaining({ label: s, insertText: s })), - ...CompletionProvider.intrinsics.map((s) => expect.objectContaining({ label: s, insertText: s })), + ...scopes.map((s) => expect.objectContaining({ label: s, insertText: s })), + ...intrinsics.map((s) => expect.objectContaining({ label: s, insertText: s })), expect.objectContaining({ label: 'bar', insertText: '.bar' }), expect.objectContaining({ label: 'foo', insertText: '.foo' }), ]); @@ -116,8 +117,8 @@ describe('CompletionProvider', () => { {} as monacoTypes.Position ); expect((result! as monacoTypes.languages.CompletionList).suggestions).toEqual([ - ...CompletionProvider.scopes.map((s) => expect.objectContaining({ label: s, insertText: `{ ${s}` })), - ...CompletionProvider.intrinsics.map((s) => expect.objectContaining({ label: s, insertText: `{ ${s}` })), + ...scopes.map((s) => expect.objectContaining({ label: s, insertText: `{ ${s}` })), + ...intrinsics.map((s) => expect.objectContaining({ label: s, insertText: `{ ${s}` })), expect.objectContaining({ label: 'bar', insertText: '{ .bar' }), expect.objectContaining({ label: 'foo', insertText: '{ .foo' }), ]); diff --git a/public/app/plugins/datasource/tempo/traceql/autocomplete.ts b/public/app/plugins/datasource/tempo/traceql/autocomplete.ts index dc0eab1c857..7aafd911edb 100644 --- a/public/app/plugins/datasource/tempo/traceql/autocomplete.ts +++ b/public/app/plugins/datasource/tempo/traceql/autocomplete.ts @@ -1,8 +1,14 @@ import { SelectableValue } from '@grafana/data'; +import { isFetchError } from '@grafana/runtime'; import type { Monaco, monacoTypes } from '@grafana/ui'; +import { createErrorNotification } from '../../../../core/copy/appNotification'; +import { notifyApp } from '../../../../core/reducers/appNotification'; +import { dispatch } from '../../../../store/store'; import TempoLanguageProvider from '../language_provider'; +import { intrinsics, scopes } from './traceql'; + interface Props { languageProvider: TempoLanguageProvider; } @@ -21,9 +27,6 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP } triggerCharacters = ['{', '.', '[', '(', '=', '~', ' ', '"']; - - static readonly intrinsics: string[] = ['duration', 'name', 'status']; - static readonly scopes: string[] = ['resource', 'span']; static readonly operators: string[] = ['=', '-', '+', '<', '>', '>=', '<=', '=~']; static readonly logicalOps: string[] = ['&&', '||']; @@ -139,7 +142,17 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP type: 'OPERATOR', })); case 'SPANSET_IN_VALUE': - const tagValues = await this.getTagValues(situation.tagName); + let tagValues; + try { + tagValues = await this.getTagValues(situation.tagName); + } catch (error) { + if (isFetchError(error)) { + dispatch(notifyApp(createErrorNotification(error.data.error, new Error(error.data.message)))); + } else if (error instanceof Error) { + dispatch(notifyApp(createErrorNotification('Error', error))); + } + } + const items: Completion[] = []; const getInsertionText = (val: SelectableValue): string => { @@ -149,7 +162,7 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP return val.type === 'string' ? `"${val.label}"` : val.label!; }; - tagValues.forEach((val) => { + tagValues?.forEach((val) => { if (val?.label) { items.push({ label: val.label, @@ -181,7 +194,7 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP } private getIntrinsicsCompletions(prepend?: string): Completion[] { - return CompletionProvider.intrinsics.map((key) => ({ + return intrinsics.map((key) => ({ label: key, insertText: (prepend || '') + key, type: 'KEYWORD', @@ -189,7 +202,7 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP } private getScopesCompletions(prepend?: string): Completion[] { - return CompletionProvider.scopes.map((key) => ({ + return scopes.map((key) => ({ label: key, insertText: (prepend || '') + key, type: 'SCOPE', @@ -242,7 +255,7 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP if (!op) { // There's no operator so we check if the name is one of the known scopes // { resource.| - if (CompletionProvider.scopes.filter((w) => w === nameMatched?.groups?.word) && nameMatched?.groups?.post_dot) { + if (scopes.filter((w) => w === nameMatched?.groups?.word) && nameMatched?.groups?.post_dot) { return { type: 'SPANSET_IN_NAME_SCOPE', }; diff --git a/public/app/plugins/datasource/tempo/traceql/traceql.ts b/public/app/plugins/datasource/tempo/traceql/traceql.ts index 1ace5ecb793..74a89a56abd 100644 --- a/public/app/plugins/datasource/tempo/traceql/traceql.ts +++ b/public/app/plugins/datasource/tempo/traceql/traceql.ts @@ -26,9 +26,9 @@ export const operators = ['=', '!=', '>', '<', '>=', '<=', '=~']; export const stringOperators = ['=', '!=', '=~']; export const numberOperators = ['=', '!=', '>', '<', '>=', '<=']; -const intrinsics = ['duration', 'name', 'status', 'parent']; +export const intrinsics = ['duration', 'kind', 'name', 'status']; -const scopes: string[] = ['resource', 'span']; +export const scopes: string[] = ['resource', 'span']; const keywords = intrinsics.concat(scopes);