diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.test.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.test.tsx index 075f5240b78..b55f6f586e4 100644 --- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.test.tsx +++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.test.tsx @@ -183,6 +183,7 @@ const renderSearchField = ( }} tags={tags || []} hideTag={hideTag} + query={'{}'} /> ); }; diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.tsx index e705524dd94..0295ea7590e 100644 --- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.tsx +++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.tsx @@ -35,6 +35,7 @@ interface Props { hideTag?: boolean; hideValue?: boolean; allowDelete?: boolean; + query: string; } const SearchField = ({ filter, @@ -48,6 +49,7 @@ const SearchField = ({ hideTag, hideValue, allowDelete, + query, }: Props) => { const styles = useStyles2(getStyles); const languageProvider = useMemo(() => new TempoLanguageProvider(datasource), [datasource]); @@ -60,7 +62,7 @@ const SearchField = ({ const updateOptions = async () => { try { - return filter.tag ? await languageProvider.getOptionsV2(scopedTag) : []; + return filter.tag ? await languageProvider.getOptionsV2(scopedTag, query) : []; } catch (error) { // Display message if Tempo is connected but search 404's if (isFetchError(error) && error?.status === 404) { @@ -72,7 +74,12 @@ const SearchField = ({ return []; }; - const { loading: isLoadingValues, value: options } = useAsync(updateOptions, [scopedTag, languageProvider, setError]); + const { loading: isLoadingValues, value: options } = useAsync(updateOptions, [ + scopedTag, + languageProvider, + setError, + query, + ]); useEffect(() => { if (Array.isArray(filter.value) && filter.value.length > 1 && filter.operator !== '=~') { diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.test.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.test.tsx index 2d33ddc5b1a..7cb9ee97c75 100644 --- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.test.tsx +++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.test.tsx @@ -112,6 +112,7 @@ describe('TagsInput', () => { }} staticTags={[]} isTagsLoading={false} + query={''} /> ); }; diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.tsx index 75f6f5dd12e..6439fbbfb57 100644 --- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.tsx +++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.tsx @@ -34,6 +34,7 @@ interface Props { staticTags: Array; isTagsLoading: boolean; hideValues?: boolean; + query: string; } const TagsInput = ({ updateFilter, @@ -44,6 +45,7 @@ const TagsInput = ({ staticTags, isTagsLoading, hideValues, + query, }: Props) => { const styles = useStyles2(getStyles); const generateId = () => uuidv4().slice(0, 8); @@ -77,6 +79,7 @@ const TagsInput = ({ deleteFilter={deleteFilter} allowDelete={true} hideValue={hideValues} + query={query} /> {i === filters.length - 1 && ( diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.tsx index 831e43bbce5..b84468cf7a0 100644 --- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.tsx +++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.tsx @@ -117,6 +117,7 @@ const TraceQLSearch = ({ datasource, query, onChange }: Props) => { tags={[]} hideScope={true} hideTag={true} + query={traceQlQuery} /> ))} @@ -160,6 +161,7 @@ const TraceQLSearch = ({ datasource, query, onChange }: Props) => { deleteFilter={deleteFilter} staticTags={staticTags} isTagsLoading={isTagsLoading} + query={traceQlQuery} /> diff --git a/public/app/plugins/datasource/tempo/configuration/TraceQLSearchTags.tsx b/public/app/plugins/datasource/tempo/configuration/TraceQLSearchTags.tsx index 2bdc8f7c4a5..2c9ca525e31 100644 --- a/public/app/plugins/datasource/tempo/configuration/TraceQLSearchTags.tsx +++ b/public/app/plugins/datasource/tempo/configuration/TraceQLSearchTags.tsx @@ -89,6 +89,7 @@ export function TraceQLSearchTags({ options, onOptionsChange, datasource }: Prop staticTags={staticTags} isTagsLoading={loading} hideValues={true} + query={'{}'} /> ) : (
Invalid data source, please create a valid data source and try again
diff --git a/public/app/plugins/datasource/tempo/language_provider.ts b/public/app/plugins/datasource/tempo/language_provider.ts index 2b9dd8a038d..bb2ada8d323 100644 --- a/public/app/plugins/datasource/tempo/language_provider.ts +++ b/public/app/plugins/datasource/tempo/language_provider.ts @@ -117,8 +117,8 @@ export default class TempoLanguageProvider extends LanguageProvider { return options; } - async getOptionsV2(tag: string): Promise>> { - const response = await this.request(`/api/v2/search/tag/${tag}/values`); + async getOptionsV2(tag: string, query: string): Promise>> { + const response = await this.request(`/api/v2/search/tag/${tag}/values`, query ? { q: query } : {}); let options: Array> = []; if (response && response.tagValues) { options = response.tagValues.map((v: { type: string; value: string }) => ({ diff --git a/public/app/plugins/datasource/tempo/traceql/autocomplete.ts b/public/app/plugins/datasource/tempo/traceql/autocomplete.ts index 77a73a3b4bd..38f61694513 100644 --- a/public/app/plugins/datasource/tempo/traceql/autocomplete.ts +++ b/public/app/plugins/datasource/tempo/traceql/autocomplete.ts @@ -88,13 +88,13 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP this.registerInteractionCommandId = id; } - private async getTagValues(tagName: string): Promise>> { + private async getTagValues(tagName: string, query: string): Promise>> { let tagValues: Array>; if (this.cachedValues.hasOwnProperty(tagName)) { tagValues = this.cachedValues[tagName]; } else { - tagValues = await this.languageProvider.getOptionsV2(tagName); + tagValues = await this.languageProvider.getOptionsV2(tagName, query); this.cachedValues[tagName] = tagValues; } return tagValues; @@ -134,7 +134,7 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP case 'SPANSET_IN_VALUE': let tagValues; try { - tagValues = await this.getTagValues(situation.tagName); + tagValues = await this.getTagValues(situation.tagName, situation.query); } catch (error) { if (isFetchError(error)) { dispatch(notifyApp(createErrorNotification(error.data.error, new Error(error.data.message)))); diff --git a/public/app/plugins/datasource/tempo/traceql/situation.test.ts b/public/app/plugins/datasource/tempo/traceql/situation.test.ts index 4a0eb421b49..89ad2e4befc 100644 --- a/public/app/plugins/datasource/tempo/traceql/situation.test.ts +++ b/public/app/plugins/datasource/tempo/traceql/situation.test.ts @@ -1,4 +1,4 @@ -import { getSituation, Situation } from './situation'; +import { getSituation, SituationType } from './situation'; jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), @@ -7,7 +7,7 @@ jest.mock('@grafana/runtime', () => ({ interface SituationTest { query: string; cursorPos: number; - expected: Situation; + expected: SituationType; } describe('situation', () => { @@ -62,7 +62,7 @@ describe('situation', () => { tests.forEach((test) => { it(`${test.query} at ${test.cursorPos} is ${test.expected.type}`, async () => { const sit = getSituation(test.query, test.cursorPos); - expect(sit).toEqual(test.expected); + expect(sit).toEqual({ ...test.expected, query: test.query }); }); }); }); diff --git a/public/app/plugins/datasource/tempo/traceql/situation.ts b/public/app/plugins/datasource/tempo/traceql/situation.ts index 6e0c669012e..fe96276192b 100644 --- a/public/app/plugins/datasource/tempo/traceql/situation.ts +++ b/public/app/plugins/datasource/tempo/traceql/situation.ts @@ -1,16 +1,13 @@ -// we find the first error-node in the tree that is at the cursor-position. -// NOTE: this might be too slow, might need to optimize it -// (ideas: we do not need to go into every subtree, based on from/to) -// also, only go to places that are in the sub-tree of the node found -// by default by lezer. problem is, `next()` will go upward too, -// and we do not want to go higher than our node import { SyntaxNode, Tree } from '@lezer/common'; import { AttributeField, FieldExpression, FieldOp, parser, SpansetFilter } from '@grafana/lezer-traceql'; type Direction = 'parent' | 'firstChild' | 'lastChild' | 'nextSibling' | 'prevSibling'; type NodeType = number; -export type Situation = + +export type Situation = { query: string } & SituationType; + +export type SituationType = | { type: 'UNKNOWN'; } @@ -46,7 +43,7 @@ type Path = Array<[Direction, NodeType[]]>; type Resolver = { path: NodeType[]; - fun: (node: SyntaxNode, text: string, pos: number) => Situation | null; + fun: (node: SyntaxNode, text: string, pos: number) => SituationType | null; }; function getErrorNode(tree: Tree, cursorPos: number): SyntaxNode | null { @@ -101,6 +98,7 @@ export function getSituation(text: string, offset: number): Situation | null { // so we handle that case first if (text === '') { return { + query: text, type: 'EMPTY', }; } @@ -127,13 +125,14 @@ export function getSituation(text: string, offset: number): Situation | null { ids.push(cur.type.id); } + let situationType: SituationType | null = null; for (let resolver of RESOLVERS) { if (isPathMatch(resolver.path, ids)) { - return resolver.fun(currentNode, text, offset); + situationType = resolver.fun(currentNode, text, offset); } } - return null; + return { query: text, ...(situationType ?? { type: 'UNKNOWN' }) }; } const ERROR_NODE_ID = 0; @@ -157,7 +156,7 @@ const RESOLVERS: Resolver[] = [ }, ]; -function resolveSpanset(node: SyntaxNode, text: string, pos: number): Situation { +function resolveSpanset(node: SyntaxNode): SituationType { const lastFieldExpression = walk(node, [['lastChild', [FieldExpression]]]); if (lastFieldExpression) { return { @@ -170,7 +169,7 @@ function resolveSpanset(node: SyntaxNode, text: string, pos: number): Situation }; } -function resolveAttribute(node: SyntaxNode, text: string, pos: number): Situation { +function resolveAttribute(node: SyntaxNode, text: string): SituationType { const attributeFieldParent = walk(node, [['parent', [AttributeField]]]); const attributeFieldParentText = attributeFieldParent ? getNodeText(attributeFieldParent, text) : ''; @@ -194,7 +193,7 @@ function resolveAttribute(node: SyntaxNode, text: string, pos: number): Situatio }; } -function resolveExpression(node: SyntaxNode, text: string, pos: number): Situation { +function resolveExpression(node: SyntaxNode, text: string): SituationType { if (node.prevSibling?.type.id === FieldOp) { let attributeField = node.prevSibling.prevSibling; if (attributeField) { @@ -210,7 +209,7 @@ function resolveExpression(node: SyntaxNode, text: string, pos: number): Situati }; } -function resolveErrorInFilterRoot(node: SyntaxNode, text: string, pos: number): Situation { +function resolveErrorInFilterRoot(): SituationType { return { type: 'SPANSET_IN_NAME', };