mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Loki Autocomplete: Improve handling of trailing spaces in queries (#61184)
* Loki Autocomplete: improve handling of trailing spaces in the query * Monaco Field: rename resizing handler function * Fix typo Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>
This commit is contained in:
parent
c5f77b6a46
commit
ef9a71f483
@ -75,6 +75,7 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
container: css`
|
||||
border-radius: ${theme.shape.borderRadius()};
|
||||
border: 1px solid ${theme.components.input.borderColor};
|
||||
width: 100%;
|
||||
`,
|
||||
};
|
||||
};
|
||||
@ -177,19 +178,18 @@ const MonacoQueryField = ({ history, onBlur, onRunQuery, initialValue, datasourc
|
||||
// (it will grow taller when necessary)
|
||||
// FIXME: maybe move this functionality into CodeEditor, like:
|
||||
// <CodeEditor resizingMode="single-line"/>
|
||||
const updateElementHeight = () => {
|
||||
const handleResize = () => {
|
||||
const containerDiv = containerRef.current;
|
||||
if (containerDiv !== null) {
|
||||
const pixelHeight = editor.getContentHeight();
|
||||
containerDiv.style.height = `${pixelHeight + EDITOR_HEIGHT_OFFSET}px`;
|
||||
containerDiv.style.width = '100%';
|
||||
const pixelWidth = containerDiv.clientWidth;
|
||||
editor.layout({ width: pixelWidth, height: pixelHeight });
|
||||
}
|
||||
};
|
||||
|
||||
editor.onDidContentSizeChange(updateElementHeight);
|
||||
updateElementHeight();
|
||||
editor.onDidContentSizeChange(handleResize);
|
||||
handleResize();
|
||||
// handle: shift + enter
|
||||
// FIXME: maybe move this functionality into CodeEditor?
|
||||
editor.addCommand(
|
||||
|
@ -134,9 +134,14 @@ const afterSelectorCompletions = [
|
||||
},
|
||||
];
|
||||
|
||||
function buildAfterSelectorCompletions(detectedParser: string, otherParser: string, afterPipe: boolean) {
|
||||
function buildAfterSelectorCompletions(
|
||||
detectedParser: string,
|
||||
otherParser: string,
|
||||
afterPipe: boolean,
|
||||
hasSpace: boolean
|
||||
) {
|
||||
const explanation = '(detected)';
|
||||
const expectedCompletions = afterSelectorCompletions.map((completion) => {
|
||||
let expectedCompletions = afterSelectorCompletions.map((completion) => {
|
||||
if (completion.type === 'DETECTED_PARSER_PLACEHOLDER') {
|
||||
return {
|
||||
...completion,
|
||||
@ -158,17 +163,22 @@ function buildAfterSelectorCompletions(detectedParser: string, otherParser: stri
|
||||
|
||||
if (afterPipe) {
|
||||
// Remove pipe
|
||||
return (
|
||||
expectedCompletions
|
||||
.map((completion) => {
|
||||
completion.insertText = completion.insertText.replace('|', '');
|
||||
return completion;
|
||||
})
|
||||
// Remove != and !~
|
||||
.filter((completion) => !completion.insertText.startsWith('!'))
|
||||
);
|
||||
expectedCompletions = expectedCompletions
|
||||
.map((completion) => {
|
||||
completion.insertText = completion.insertText.replace('|', '').trimStart();
|
||||
return completion;
|
||||
})
|
||||
// Remove != and !~
|
||||
.filter((completion) => !completion.insertText.startsWith('!'))
|
||||
.filter((completion) => (hasSpace ? completion.type !== 'LINE_FILTER' : true));
|
||||
}
|
||||
|
||||
expectedCompletions.forEach((completion) => {
|
||||
if (completion.type !== 'LINE_FILTER') {
|
||||
completion.insertText = hasSpace ? completion.insertText.trimStart() : ` ${completion.insertText}`;
|
||||
}
|
||||
});
|
||||
|
||||
return expectedCompletions;
|
||||
}
|
||||
|
||||
@ -307,34 +317,39 @@ describe('getCompletions', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test.each([true, false])(
|
||||
'Returns completion options when the situation is AFTER_SELECTOR, detected JSON parser, and afterPipe %s',
|
||||
async (afterPipe: boolean) => {
|
||||
test.each([
|
||||
[true, true],
|
||||
[false, true],
|
||||
[true, false],
|
||||
[false, false],
|
||||
])(
|
||||
'Returns completion options when the situation is AFTER_SELECTOR, detected JSON parser, afterPipe %s, and hasSpace: %s',
|
||||
async (afterPipe: boolean, hasSpace: boolean) => {
|
||||
jest.spyOn(completionProvider, 'getParserAndLabelKeys').mockResolvedValue({
|
||||
extractedLabelKeys,
|
||||
hasJSON: true,
|
||||
hasLogfmt: false,
|
||||
});
|
||||
const situation: Situation = { type: 'AFTER_SELECTOR', logQuery: '', afterPipe };
|
||||
const situation: Situation = { type: 'AFTER_SELECTOR', logQuery: '', afterPipe, hasSpace };
|
||||
const completions = await getCompletions(situation, completionProvider);
|
||||
|
||||
const expected = buildAfterSelectorCompletions('json', 'logfmt', afterPipe);
|
||||
const expected = buildAfterSelectorCompletions('json', 'logfmt', afterPipe, hasSpace);
|
||||
expect(completions).toEqual(expected);
|
||||
}
|
||||
);
|
||||
|
||||
test.each([true, false])(
|
||||
'Returns completion options when the situation is AFTER_SELECTOR, detected Logfmt parser, and afterPipe %s',
|
||||
'Returns completion options when the situation is AFTER_SELECTOR, detected Logfmt parser, afterPipe %s, and hasSpace: %s',
|
||||
async (afterPipe: boolean) => {
|
||||
jest.spyOn(completionProvider, 'getParserAndLabelKeys').mockResolvedValue({
|
||||
extractedLabelKeys,
|
||||
hasJSON: false,
|
||||
hasLogfmt: true,
|
||||
});
|
||||
const situation: Situation = { type: 'AFTER_SELECTOR', logQuery: '', afterPipe };
|
||||
const situation: Situation = { type: 'AFTER_SELECTOR', logQuery: '', afterPipe, hasSpace: true };
|
||||
const completions = await getCompletions(situation, completionProvider);
|
||||
|
||||
const expected = buildAfterSelectorCompletions('logfmt', 'json', afterPipe);
|
||||
const expected = buildAfterSelectorCompletions('logfmt', 'json', afterPipe, true);
|
||||
expect(completions).toEqual(expected);
|
||||
}
|
||||
);
|
||||
|
@ -158,14 +158,13 @@ async function getInGroupingCompletions(logQuery: string, dataProvider: Completi
|
||||
const PARSERS = ['json', 'logfmt', 'pattern', 'regexp', 'unpack'];
|
||||
|
||||
async function getParserCompletions(
|
||||
afterPipe: boolean,
|
||||
prefix: string,
|
||||
hasJSON: boolean,
|
||||
hasLogfmt: boolean,
|
||||
extractedLabelKeys: string[]
|
||||
) {
|
||||
const allParsers = new Set(PARSERS);
|
||||
const completions: Completion[] = [];
|
||||
const prefix = afterPipe ? ' ' : '| ';
|
||||
const hasLevelInExtractedLabels = extractedLabelKeys.some((key) => key === 'level');
|
||||
|
||||
if (hasJSON) {
|
||||
@ -210,13 +209,13 @@ async function getParserCompletions(
|
||||
async function getAfterSelectorCompletions(
|
||||
logQuery: string,
|
||||
afterPipe: boolean,
|
||||
hasSpace: boolean,
|
||||
dataProvider: CompletionDataProvider
|
||||
): Promise<Completion[]> {
|
||||
const { extractedLabelKeys, hasJSON, hasLogfmt } = await dataProvider.getParserAndLabelKeys(logQuery);
|
||||
|
||||
const completions: Completion[] = await getParserCompletions(afterPipe, hasJSON, hasLogfmt, extractedLabelKeys);
|
||||
|
||||
const prefix = afterPipe ? ' ' : '| ';
|
||||
const prefix = `${hasSpace ? '' : ' '}${afterPipe ? '' : '| '}`;
|
||||
const completions: Completion[] = await getParserCompletions(prefix, hasJSON, hasLogfmt, extractedLabelKeys);
|
||||
|
||||
extractedLabelKeys.forEach((key) => {
|
||||
completions.push({
|
||||
@ -249,7 +248,9 @@ async function getAfterSelectorCompletions(
|
||||
documentation: explainOperator(LokiOperationId.LabelFormat),
|
||||
});
|
||||
|
||||
const lineFilters = getLineFilterCompletions(afterPipe);
|
||||
// With a space between the pipe and the cursor, we omit line filters
|
||||
// E.g. `{label="value"} | `
|
||||
const lineFilters = afterPipe && hasSpace ? [] : getLineFilterCompletions(afterPipe);
|
||||
|
||||
return [...lineFilters, ...completions];
|
||||
}
|
||||
@ -307,7 +308,7 @@ export async function getCompletions(
|
||||
dataProvider
|
||||
);
|
||||
case 'AFTER_SELECTOR':
|
||||
return getAfterSelectorCompletions(situation.logQuery, situation.afterPipe, dataProvider);
|
||||
return getAfterSelectorCompletions(situation.logQuery, situation.afterPipe, situation.hasSpace, dataProvider);
|
||||
case 'AFTER_UNWRAP':
|
||||
return getAfterUnwrapCompletions(situation.logQuery, dataProvider);
|
||||
case 'IN_AGGREGATION':
|
||||
|
@ -42,6 +42,7 @@ describe('situation', () => {
|
||||
assertSituation('{level="info"} ^', {
|
||||
type: 'AFTER_SELECTOR',
|
||||
afterPipe: false,
|
||||
hasSpace: true,
|
||||
logQuery: '{level="info"}',
|
||||
});
|
||||
|
||||
@ -54,18 +55,21 @@ describe('situation', () => {
|
||||
assertSituation('{level="info"} | json ^', {
|
||||
type: 'AFTER_SELECTOR',
|
||||
afterPipe: false,
|
||||
hasSpace: true,
|
||||
logQuery: '{level="info"} | json',
|
||||
});
|
||||
|
||||
assertSituation('{level="info"} | json | ^', {
|
||||
assertSituation('{level="info"} | json |^', {
|
||||
type: 'AFTER_SELECTOR',
|
||||
afterPipe: true,
|
||||
hasSpace: false,
|
||||
logQuery: '{level="info"} | json |',
|
||||
});
|
||||
|
||||
assertSituation('count_over_time({level="info"}^[10s])', {
|
||||
type: 'AFTER_SELECTOR',
|
||||
afterPipe: false,
|
||||
hasSpace: false,
|
||||
logQuery: '{level="info"}',
|
||||
});
|
||||
|
||||
@ -76,18 +80,21 @@ describe('situation', () => {
|
||||
assertSituation('count_over_time({level="info"}^)', {
|
||||
type: 'AFTER_SELECTOR',
|
||||
afterPipe: false,
|
||||
hasSpace: false,
|
||||
logQuery: '{level="info"}',
|
||||
});
|
||||
|
||||
assertSituation('{level="info"} |= "a" | logfmt ^', {
|
||||
type: 'AFTER_SELECTOR',
|
||||
afterPipe: false,
|
||||
hasSpace: true,
|
||||
logQuery: '{level="info"} |= "a" | logfmt',
|
||||
});
|
||||
|
||||
assertSituation('sum(count_over_time({place="luna"} | logfmt |^)) by (place)', {
|
||||
type: 'AFTER_SELECTOR',
|
||||
afterPipe: true,
|
||||
hasSpace: false,
|
||||
logQuery: '{place="luna"}| logfmt |',
|
||||
});
|
||||
});
|
||||
|
@ -119,6 +119,7 @@ export type Situation =
|
||||
| {
|
||||
type: 'AFTER_SELECTOR';
|
||||
afterPipe: boolean;
|
||||
hasSpace: boolean;
|
||||
logQuery: string;
|
||||
}
|
||||
| {
|
||||
@ -450,6 +451,7 @@ function resolveLogOrLogRange(node: SyntaxNode, text: string, pos: number, after
|
||||
return {
|
||||
type: 'AFTER_SELECTOR',
|
||||
afterPipe,
|
||||
hasSpace: text.endsWith(' '),
|
||||
logQuery: getLogQueryFromMetricsQuery(text).trim(),
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user