mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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
This commit is contained in:
parent
993ab2587e
commit
44afad2ce4
@ -181,7 +181,7 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson
|
||||
// Check whether this is a trace ID or traceQL query by checking if it only contains hex characters
|
||||
if (queryValue.trim().match(hexOnlyRegex)) {
|
||||
// There's only hex characters so let's assume that this is a trace ID
|
||||
reportInteraction('grafana_traces_traceql_traceID_queried', {
|
||||
reportInteraction('grafana_traces_traceID_queried', {
|
||||
datasourceType: 'tempo',
|
||||
app: options.app ?? '',
|
||||
query: queryValue ?? '',
|
||||
|
@ -3,6 +3,7 @@ import type { languages } from 'monaco-editor';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { CodeEditor, Monaco, monacoTypes, useTheme2 } from '@grafana/ui';
|
||||
|
||||
import { createErrorNotification } from '../../../../core/copy/appNotification';
|
||||
@ -10,7 +11,7 @@ import { notifyApp } from '../../../../core/reducers/appNotification';
|
||||
import { dispatch } from '../../../../store/store';
|
||||
import { TempoDatasource } from '../datasource';
|
||||
|
||||
import { CompletionProvider } from './autocomplete';
|
||||
import { CompletionProvider, CompletionType } from './autocomplete';
|
||||
import { languageDefinition } from './traceql';
|
||||
|
||||
interface Props {
|
||||
@ -51,7 +52,7 @@ export function TraceQLEditor(props: Props) {
|
||||
}}
|
||||
onBeforeEditorMount={ensureTraceQL}
|
||||
onEditorDidMount={(editor, monaco) => {
|
||||
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<string, unknown> = { 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;
|
||||
|
@ -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<string>()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<Array<SelectableValue<string>>> {
|
||||
let tagValues: Array<SelectableValue<string>> = [];
|
||||
let tagValues: Array<SelectableValue<string>>;
|
||||
|
||||
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,
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user