mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
@@ -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`);
|
||||
|
||||
Reference in New Issue
Block a user