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
This commit is contained in:
Andre Pereira 2023-04-24 14:11:28 +01:00 committed by GitHub
parent d4715a6f04
commit 990b3c07ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 34 additions and 21 deletions

View File

@ -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

View File

@ -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 (

View File

@ -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<TempoJsonData> {
@ -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}
/>

View File

@ -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' }),
]);

View File

@ -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>): 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',
};

View File

@ -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);