Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loki: Add the ability to prettify logql queries #64337

Merged
merged 54 commits into from
Jul 21, 2023
Merged

Conversation

gwdawson
Copy link
Member

@gwdawson gwdawson commented Mar 7, 2023

What is this feature?
adds a button giving the user an option to prettify their loki query.

Which issue(s) does this PR fix?:
Fixes #61041

how to test this pr:

in grafana/grafana

  1. checkout to this branch
  2. make sure you have enabled the feature flag lokiFormatQuery

@github-actions
Copy link
Contributor

github-actions bot commented Mar 7, 2023

Backend code coverage report for PR #64337
No changes

@github-actions
Copy link
Contributor

github-actions bot commented Mar 7, 2023

Frontend code coverage report for PR #64337

Plugin Main PR Difference
loki 85.29% 85.35% .06%

Copy link
Contributor

@matyax matyax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work so far! I will try to come back to this with a potential approach using Lezer to replace the template variables.

public/app/plugins/datasource/loki/datasource.ts Outdated Show resolved Hide resolved
@matyax
Copy link
Contributor

matyax commented Mar 9, 2023

Leaving this as a comment to continue later. I've been playing with using the paser to get the node that points to a variable, to be used after formatting to replace the interpolated string with the variable name. For example, I would like to keep a reference to the Range node in this tree:

imagen

So I can use it later to replace it with the variable name using the parsed prettified query:

imagen

To attempt to do that, I would store a path to the node (node parents until the root of the tree), for every detected variable.

Datasource:

  async formatQuery(query: string) {
    const transformedQuery = transformForFormatting(query, this.interpolateString.bind(this));
    
    let formatted = await this.metadataRequest('format_query', { query: transformedQuery.query  });

    return formatted;
  }

Formatter.ts

import { SyntaxNode } from '@lezer/common';

import { ScopedVars, TypedVariableModel } from '@grafana/data';
import { parser } from '@grafana/lezer-logql';
import { ErrorId } from 'app/plugins/datasource/prometheus/querybuilder/shared/parsingUtils';

import { placeHolderScopedVars } from './components/monaco-query-field/monaco-completion-provider/validation';

type Transformation = {
    original: string;
    replaced: string;
}

type TransformedQuery = {
    transformations: Transformation[];
    query: string;
}

interface ParseError {
    text: string;
    node: SyntaxNode;
}



function parseQuery(query: string) {
    const parseErrors = new Map<SyntaxNode | null, ParseError[]>();
    const tree = parser.parse(query);
    tree.iterate({
      enter: (nodeRef): false | void => {
        if (nodeRef.type.id === ErrorId) {
          const node = nodeRef.node;
          const siblings = parseErrors.get(node.parent) || []; 
          parseErrors.set(node.parent, [...siblings, {
            node,
            text: query.substring(node.from, node.to),
          }]);
        }
      },
    });
    return parseErrors;
  }

export function transformForFormatting(query: string, interpolateString: (string: string, scopedVars?: ScopedVars) => string): TransformedQuery {
    // We get the possible variables from the parse errors
    const errors = parseQuery(query);
    // Nothing to fix, the query is ready for formatting
    if (!errors.size) {
        return {
            transformations: [],
            query,
        };
    }

    errors.forEach((parseError, parent) => {
        if (parseError[0].text === '$') {
            const variable = query.substring(parseError[0].node.from, parseError[parseError.length-1].node.from);
            const chain = [parent?.type.name];
            let lastParent = parent;
            while (lastParent?.parent) {
                chain.push(lastParent.parent.type.name);
                lastParent = lastParent.parent;
            }
            console.log(variable, interpolateString(variable, placeHolderScopedVars), chain);
        }
    });

    return {
        transformations: [],
        query,
    };
}

export function revertTransformations(query: string, transformations: Transformation[]): string {
    return '';
}

Anyway, given the complexity that this could require, I wonder if we should either:

  • Ask the backend to be nice with variables somehow.
  • Format in the frontend and handle variables better than the endpoint.

In any case, this could be useful to you as an introduction to Lezer. To be continued.

@matyax
Copy link
Contributor

matyax commented Mar 10, 2023

Will follow up with something that might work. Stay tunned.

@matyax
Copy link
Contributor

matyax commented Mar 10, 2023

We might try something like this: #64591 Let me know what you think.

* Prettify query: use the parser to interpolate and recover the original query

* Fix typo

Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>

* Fix typo

Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>

* fix: reverse transformation not working

---------

Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>
Co-authored-by: Gareth Dawson <gwdawson.work@gmail.com>
@gwdawson gwdawson marked this pull request as ready for review March 23, 2023 11:51
@gwdawson gwdawson requested a review from a team as a code owner March 23, 2023 11:51
@gwdawson gwdawson requested a review from matyax March 23, 2023 11:51
@matyax
Copy link
Contributor

matyax commented Jun 16, 2023

@gwdawson Found the bug that is currently duplicating the output of count(sum(rate({compose_project="tns-custom"}[1s]))).

The root cause of the problem is the function iterateNode. What happens is that in formatVectorAggregationExpr you call iterateNode asking for the following nodes: [VectorOp, Number, MetricExpr, Grouping], and because it's recursive (and unfortunately it's bugged), it returns a lot more that you're expecting.

Instead of getting an array of VectorOp, MetricExpr from:

imagen

You get 2 times those two:

imagen

All ☝️ are recursive calls over the same nodes. So, you correctly format the first two VectorOp, MetricExpr, and when you're done, you append the extra VectorOp, MetricExpr.

From that, the following change fixes the bug:

function iterateNode(node: SyntaxNode, lookingFor: number[]): SyntaxNode[] {
  const nodes: SyntaxNode[] = [];
  let child = node.firstChild;

  while (child) {
    if (lookingFor.includes(child.type.id)) {
      nodes.push(child);
      lookingFor.splice(lookingFor.indexOf(child.type.id), 1); // <---- this
    }

    nodes.push(...iterateNode(child, lookingFor));
    child = child.nextSibling;
  }

  return nodes;
}

imagen

The problem with that change is that it breaks other code that is dependent on iterateNode looking for repeated nodes.

One of the main issues with iterateNode is that it should be either recursive or use a loop, not both, and I'm not sure why you added a recursive call.

For example:

function iterateNode(node: SyntaxNode, lookingFor: number[]): SyntaxNode[] {
  const nodes: SyntaxNode[] = [];
  let child = node.firstChild;

  while (child) {
    if (lookingFor.includes(child.type.id)) {
      nodes.push(child);
    }

    // nodes.push(...iterateNode(child, lookingFor)); This should not be necessary
    child = child.nextSibling; // maybe this while should move to the children if there are no more siblings?
  }

  return nodes;
}

TL;DR

Adding lookingFor.splice(lookingFor.indexOf(child.type.id), 1); fixes the bug for that query, but the root cause of this bug (and maybe other bugs we don't know about yet) is that iterateNode is a buggy implementation that should be fixed.

Other things I noticed

I would suggest that you avoid recursive calls, specially calls to formatLokiQuery, because it's a potential source for bugs. E.g.

const metricExpr = query.substring(node.from, node.to);
response += indentMultiline(formatLokiQuery(metricExpr), 1);
response += '\n)';

There, I would change it to:

const metricExpr = query.substring(node.from, node.to); // slice the MetricExpr part
const metricNode = getNodeFromQuery(metricExpr, MetricExpr); // Get the new MetricExpr node
if (metricNode) {
  response += indentMultiline(formatMetricExpr(metricNode, metricExpr), 1); // Pass it to formatMetricExpr
}

PD: it works really well! Great work so far 🤝

@matyax matyax self-requested a review July 10, 2023 14:18
Copy link
Contributor

@matyax matyax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last thing and we're done here 🚢

Copy link
Contributor

@matyax matyax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice work 👏 👏 👏

@gwdawson gwdawson dismissed svennergr’s stale review July 21, 2023 12:00

this change is related to the api implementation we tested. dismissing to unblock merge

@gwdawson gwdawson changed the title Loki: Add button to prettify query Loki: Add the ability to prettify logql queries Jul 21, 2023
@gwdawson gwdawson merged commit 4e42f9b into main Jul 21, 2023
18 checks passed
@gwdawson gwdawson deleted the gareth/prettify-query branch July 21, 2023 12:03
linoman pushed a commit that referenced this pull request Jul 24, 2023
* pushed to get help of a genius

* fix: error response is not json

* feat: make request on click

* refactor: remove print statement

* refactor: remove unnecessary code

* feat: convert grafana variables to value for API request

* use the parser to interpolate and recover the original query (#64591)

* Prettify query: use the parser to interpolate and recover the original query

* Fix typo

Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>

* Fix typo

Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>

* fix: reverse transformation not working

---------

Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>
Co-authored-by: Gareth Dawson <gwdawson.work@gmail.com>

* fix: bugs created from merge

* refactor: move prettify code out of monaco editor

* fix: variables with the same value get converted back to the incorect variable

* refactor

* use consistent styling with bigquery

* fix: only allow text/plain and application/json

* fix: only make the request if the query is valid

* endpoint now returns application/json

* prettify from js

* WIP: not all cases are handles, code still needs cleaning up

* WIP

* large refactor, finished support for all pipeline expressions

* add tests for all format functions

* idk why these files changed

* add support for range aggregation expr & refactor

* add support for vector aggregation expressions

* add support for bin op expression

* add support for literal and vector expressions

* add tests and fix some bugs

* add support for distinct and decolorize

* feat: update variable replace and return

* fix: lezer throws an errow when using a range variable

* remove api implementation

* remove api implementation

* remove type assertions

* add feature flag

* update naming

* fix: bug incorrectly formatting unwrap with labelfilter

* support label replace expr

* remove duplicate code (after migration)

* add more tests

* validate query before formatting

* move tests to lezer repo

* add feature tracking

* populate feature tracking with some data

* upgrade lezer version to 0.1.7

* bump lezer to 0.1.8

* add tests

---------

Co-authored-by: Matias Chomicki <matyax@gmail.com>
Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>
@matyax matyax mentioned this pull request Jul 28, 2023
9 tasks
@ricky-undeadcoders ricky-undeadcoders modified the milestones: 10.1.x, 10.1.0 Aug 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Loki Query Editor: prettify query
7 participants