From 44afad2ce4a38fabc8fbc6bea68321c5a2baf00f Mon Sep 17 00:00:00 2001 From: Andre Pereira Date: Tue, 3 Jan 2023 10:43:11 +0000 Subject: [PATCH] Tempo: TraceQL autocomplete feature tracking (#60876) * Track usages of completion items in TraceQL query editor * Change back traceID reportInteraction() name to avoid breaking dashboards * Filter out TAG_VALUE labels from feature tracking to avoid exposing sensitive data --- .../plugins/datasource/tempo/datasource.ts | 2 +- .../tempo/traceql/TraceQLEditor.tsx | 23 +++++++++++++-- .../datasource/tempo/traceql/autocomplete.ts | 29 ++++++++++++++----- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/public/app/plugins/datasource/tempo/datasource.ts b/public/app/plugins/datasource/tempo/datasource.ts index 99dc23f8e3c..b4132cf9e45 100644 --- a/public/app/plugins/datasource/tempo/datasource.ts +++ b/public/app/plugins/datasource/tempo/datasource.ts @@ -181,7 +181,7 @@ export class TempoDatasource extends DataSourceWithBackend { - setupAutocompleteFn(editor, monaco); + setupAutocompleteFn(editor, monaco, setupRegisterInteractionCommand(editor)); setupActions(editor, monaco, onRunQuery); setupPlaceholder(editor, monaco, styles); setupAutoSize(editor); @@ -101,6 +102,17 @@ function setupActions(editor: monacoTypes.editor.IStandaloneCodeEditor, monaco: }); } +function setupRegisterInteractionCommand(editor: monacoTypes.editor.IStandaloneCodeEditor): string | null { + return editor.addCommand(0, function (_, label, type: CompletionType) { + const properties: Record = { datasourceType: 'tempo', type }; + // Filter out the label for TAG_VALUE completions to avoid potentially exposing sensitive data + if (type !== 'TAG_VALUE') { + properties.label = label; + } + reportInteraction('grafana_traces_traceql_completion', properties); + }); +} + function setupAutoSize(editor: monacoTypes.editor.IStandaloneCodeEditor) { const container = editor.getDomNode(); const updateHeight = () => { @@ -156,9 +168,14 @@ function useAutocomplete(datasource: TempoDatasource) { }, []); // This should be run in monaco onEditorDidMount - return (editor: monacoTypes.editor.IStandaloneCodeEditor, monaco: Monaco) => { + return ( + editor: monacoTypes.editor.IStandaloneCodeEditor, + monaco: Monaco, + registerInteractionCommandId: string | null + ) => { providerRef.current.editor = editor; providerRef.current.monaco = monaco; + providerRef.current.setRegisterInteractionCommandId(registerInteractionCommandId); const { dispose } = monaco.languages.registerCompletionItemProvider(langId, providerRef.current); autocompleteDisposeFun.current = dispose; diff --git a/public/app/plugins/datasource/tempo/traceql/autocomplete.ts b/public/app/plugins/datasource/tempo/traceql/autocomplete.ts index 3ba809ab443..889098bc52d 100644 --- a/public/app/plugins/datasource/tempo/traceql/autocomplete.ts +++ b/public/app/plugins/datasource/tempo/traceql/autocomplete.ts @@ -13,9 +13,11 @@ interface Props { */ export class CompletionProvider implements monacoTypes.languages.CompletionItemProvider { languageProvider: TempoLanguageProvider; + registerInteractionCommandId: string | null; constructor(props: Props) { this.languageProvider = props.languageProvider; + this.registerInteractionCommandId = null; } triggerCharacters = ['{', '.', '[', '(', '=', '~', ' ', '"']; @@ -57,14 +59,19 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP // so that monaco keeps the order we use const maxIndexDigits = items.length.toString().length; const suggestions: monacoTypes.languages.CompletionItem[] = items.map((item, index) => { - const suggestion = { + const suggestion: monacoTypes.languages.CompletionItem = { kind: getMonacoCompletionItemKind(item.type, this.monaco!), label: item.label, insertText: item.insertText, sortText: index.toString().padStart(maxIndexDigits, '0'), // to force the order we have range, + command: { + id: this.registerInteractionCommandId || 'noOp', + title: 'Report Interaction', + arguments: [item.label, item.type], + }, }; - fixSuggestion(suggestion, item.type, model, offset, this.monaco!); + fixSuggestion(suggestion, item.type, model, offset); return suggestion; }); return { suggestions }; @@ -78,6 +85,13 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP tags.forEach((t) => (this.tags[t] = new Set())); } + /** + * Set the ID for the registerInteraction command, to be used to keep track of how many completions are used by the users + */ + setRegisterInteractionCommandId(id: string | null) { + this.registerInteractionCommandId = id; + } + private overrideTagName(tagName: string): string { switch (tagName) { case 'status': @@ -88,7 +102,7 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP } private async getTagValues(tagName: string): Promise>> { - let tagValues: Array> = []; + let tagValues: Array>; if (this.cachedValues.hasOwnProperty(tagName)) { tagValues = this.cachedValues[tagName]; @@ -404,11 +418,10 @@ function getRangeAndOffset(monaco: Monaco, model: monacoTypes.editor.ITextModel, * here. */ function fixSuggestion( - suggestion: monacoTypes.languages.CompletionItem & { range: monacoTypes.IRange }, + suggestion: monacoTypes.languages.CompletionItem, itemType: CompletionType, model: monacoTypes.editor.ITextModel, - offset: number, - monaco: Monaco + offset: number ) { if (itemType === 'TAG_NAME') { const match = model @@ -427,10 +440,10 @@ function fixSuggestion( } // Adjust the range, so that we will replace the whole tag. - suggestion.range = monaco.Range.lift({ + suggestion.range = { ...suggestion.range, startColumn: offset - tag.length + 1, - }); + }; } } }