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>
pull/61009/head^2
Matias Chomicki 2 years ago committed by GitHub
parent c5f77b6a46
commit ef9a71f483
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryField.tsx
  2. 53
      public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/completions.test.ts
  3. 15
      public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/completions.ts
  4. 9
      public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/situation.test.ts
  5. 2
      public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/situation.ts

@ -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…
Cancel
Save