Tempo: Integrate scoped tags API (#68106)

* Support scoped tags API

* Tests

* Updates

* Updated components and language provider to certralize tag retrieval

* Update tests and add new tests for language provider

* Minor update

* Update test
This commit is contained in:
Joey
2023-05-17 07:50:27 +01:00
committed by GitHub
parent 37791e7a01
commit caba156488
15 changed files with 511 additions and 173 deletions

View File

@@ -1,13 +1,14 @@
import { Value } from 'slate';
import { LanguageProvider, SelectableValue } from '@grafana/data';
import { CompletionItemGroup, TypeaheadInput, TypeaheadOutput } from '@grafana/ui';
import { getAllTags, getTagsByScope, getUnscopedTags } from './SearchTraceQLEditor/utils';
import { TraceqlSearchScope } from './dataquery.gen';
import { TempoDatasource } from './datasource';
import { Scope } from './types';
export default class TempoLanguageProvider extends LanguageProvider {
datasource: TempoDatasource;
tags?: string[];
tagsV1?: string[];
tagsV2?: Scope[];
constructor(datasource: TempoDatasource, initialValues?: any) {
super();
@@ -31,61 +32,78 @@ export default class TempoLanguageProvider extends LanguageProvider {
};
async fetchTags() {
const response = await this.request('/api/search/tags', []);
this.tags = response.tagNames;
let v1Resp, v2Resp;
try {
v2Resp = await this.request('/api/v2/search/tags', []);
} catch (error) {
v1Resp = await this.request('/api/search/tags', []);
}
if (v2Resp && v2Resp.scopes) {
this.setV2Tags(v2Resp.scopes);
} else if (v1Resp) {
this.setV1Tags(v1Resp.tagNames);
}
}
getTags = () => {
return this.tags;
setV1Tags = (tags: string[]) => {
this.tagsV1 = tags;
};
provideCompletionItems = async ({ text, value }: TypeaheadInput): Promise<TypeaheadOutput> => {
const emptyResult: TypeaheadOutput = { suggestions: [] };
if (!value) {
return emptyResult;
}
const query = value.endText.getText();
const isValue = query[query.indexOf(text) - 1] === '=';
if (isValue || text === '=') {
return this.getTagValueCompletionItems(value);
}
return this.getTagsCompletionItems();
setV2Tags = (tags: Scope[]) => {
this.tagsV2 = tags;
};
getTagsCompletionItems = (): TypeaheadOutput => {
const { tags } = this;
const suggestions: CompletionItemGroup[] = [];
if (tags?.length) {
suggestions.push({
label: `Tag`,
items: tags.map((tag) => ({ label: tag })),
});
getTags = (scope?: TraceqlSearchScope) => {
if (this.tagsV2 && scope) {
if (scope === TraceqlSearchScope.Unscoped) {
return getUnscopedTags(this.tagsV2);
}
return getTagsByScope(this.tagsV2, scope);
} else if (this.tagsV1) {
// This is needed because the /api/v2/search/tag/${tag}/values API expects "status" and the v1 API expects "status.code"
// so Tempo doesn't send anything and we inject it here for the autocomplete
if (!this.tagsV1.find((t) => t === 'status')) {
this.tagsV1.push('status');
}
return this.tagsV1;
}
return { suggestions };
return [];
};
async getTagValueCompletionItems(value: Value) {
const tags = value.endText.getText().split(' ');
let tagName = tags[tags.length - 1] ?? '';
tagName = tagName.split('=')[0];
const response = await this.request(`/api/v2/search/tag/${tagName}/values`, []);
const suggestions: CompletionItemGroup[] = [];
if (response && response.tagValues) {
suggestions.push({
label: `Tag Values`,
items: response.tagValues.map((tagValue: string) => ({ label: tagValue, insertText: `"${tagValue}"` })),
});
getTraceqlAutocompleteTags = (scope?: string) => {
if (this.tagsV2) {
if (!scope) {
// have not typed a scope yet || unscoped (.) typed
return getUnscopedTags(this.tagsV2);
} else if (scope === TraceqlSearchScope.Unscoped) {
return getUnscopedTags(this.tagsV2);
}
return getTagsByScope(this.tagsV2, scope);
} else if (this.tagsV1) {
// This is needed because the /api/v2/search/tag/${tag}/values API expects "status" and the v1 API expects "status.code"
// so Tempo doesn't send anything and we inject it here for the autocomplete
if (!this.tagsV1.find((t) => t === 'status')) {
this.tagsV1.push('status');
}
return this.tagsV1;
}
return { suggestions };
}
return [];
};
getAutocompleteTags = () => {
if (this.tagsV2) {
return getAllTags(this.tagsV2);
} else if (this.tagsV1) {
// This is needed because the /api/search/tag/${tag}/values API expects "status.code" and the v2 API expects "status"
// so Tempo doesn't send anything and we inject it here for the autocomplete
if (!this.tagsV1.find((t) => t === 'status.code')) {
this.tagsV1.push('status.code');
}
return this.tagsV1;
}
return [];
};
async getOptionsV1(tag: string): Promise<Array<SelectableValue<string>>> {
const response = await this.request(`/api/search/tag/${tag}/values`);