mirror of https://github.com/grafana/grafana.git
				
				
				
			Loki: Implement visual query builder from `@grafana/experimental` (#81140)
* Loki: Use visual query builder from grafana/experimental * Update to 1.7.7 * Update * In renderOperation console.error instead of throwing error * Remove redundant comment
This commit is contained in:
		
							parent
							
								
									4577e61ee7
								
							
						
					
					
						commit
						c6793d4f12
					
				| 
						 | 
					@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
 | 
				
			||||||
import { useAsync } from 'react-use';
 | 
					import { useAsync } from 'react-use';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { dateTime, GrafanaTheme2, LogRowModel, renderMarkdown, SelectableValue } from '@grafana/data';
 | 
					import { dateTime, GrafanaTheme2, LogRowModel, renderMarkdown, SelectableValue } from '@grafana/data';
 | 
				
			||||||
 | 
					import { RawQuery } from '@grafana/experimental';
 | 
				
			||||||
import { reportInteraction } from '@grafana/runtime';
 | 
					import { reportInteraction } from '@grafana/runtime';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  Alert,
 | 
					  Alert,
 | 
				
			||||||
| 
						 | 
					@ -20,7 +21,6 @@ import {
 | 
				
			||||||
  useStyles2,
 | 
					  useStyles2,
 | 
				
			||||||
} from '@grafana/ui';
 | 
					} from '@grafana/ui';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { RawQuery } from '../../prometheus/querybuilder/shared/RawQuery';
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  LogContextProvider,
 | 
					  LogContextProvider,
 | 
				
			||||||
  LOKI_LOG_CONTEXT_PRESERVED_LABELS,
 | 
					  LOKI_LOG_CONTEXT_PRESERVED_LABELS,
 | 
				
			||||||
| 
						 | 
					@ -312,7 +312,11 @@ export function LokiContextUi(props: LokiContextUiProps) {
 | 
				
			||||||
          <div className={styles.rawQueryContainer}>
 | 
					          <div className={styles.rawQueryContainer}>
 | 
				
			||||||
            {initialized ? (
 | 
					            {initialized ? (
 | 
				
			||||||
              <>
 | 
					              <>
 | 
				
			||||||
                <RawQuery lang={{ grammar: lokiGrammar, name: 'loki' }} query={queryExpr} className={styles.rawQuery} />
 | 
					                <RawQuery
 | 
				
			||||||
 | 
					                  language={{ grammar: lokiGrammar, name: 'loki' }}
 | 
				
			||||||
 | 
					                  query={queryExpr}
 | 
				
			||||||
 | 
					                  className={styles.rawQuery}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
                <Tooltip content="The initial log context query is created from all labels defining the stream for the selected log line. Use the editor below to customize the log context query.">
 | 
					                <Tooltip content="The initial log context query is created from all labels defining the stream for the selected log line. Use the editor below to customize the log context query.">
 | 
				
			||||||
                  <Icon name="info-circle" size="sm" className={styles.queryDescription} />
 | 
					                  <Icon name="info-circle" size="sm" className={styles.queryDescription} />
 | 
				
			||||||
                </Tooltip>
 | 
					                </Tooltip>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@ import { cloneDeep, defaultsDeep } from 'lodash';
 | 
				
			||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { CoreApp } from '@grafana/data';
 | 
					import { CoreApp } from '@grafana/data';
 | 
				
			||||||
import { QueryEditorMode } from 'app/plugins/datasource/prometheus/querybuilder/shared/types';
 | 
					import { QueryEditorMode } from '@grafana/experimental';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { createLokiDatasource } from '../__mocks__/datasource';
 | 
					import { createLokiDatasource } from '../__mocks__/datasource';
 | 
				
			||||||
import { EXPLAIN_LABEL_FILTER_CONTENT } from '../querybuilder/components/LokiQueryBuilderExplained';
 | 
					import { EXPLAIN_LABEL_FILTER_CONTENT } from '../querybuilder/components/LokiQueryBuilderExplained';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,12 +4,16 @@ import { usePrevious } from 'react-use';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { CoreApp, LoadingState } from '@grafana/data';
 | 
					import { CoreApp, LoadingState } from '@grafana/data';
 | 
				
			||||||
import { selectors } from '@grafana/e2e-selectors';
 | 
					import { selectors } from '@grafana/e2e-selectors';
 | 
				
			||||||
import { EditorHeader, EditorRows, FlexItem } from '@grafana/experimental';
 | 
					import {
 | 
				
			||||||
 | 
					  EditorHeader,
 | 
				
			||||||
 | 
					  EditorRows,
 | 
				
			||||||
 | 
					  FlexItem,
 | 
				
			||||||
 | 
					  QueryEditorModeToggle,
 | 
				
			||||||
 | 
					  QueryHeaderSwitch,
 | 
				
			||||||
 | 
					  QueryEditorMode,
 | 
				
			||||||
 | 
					} from '@grafana/experimental';
 | 
				
			||||||
import { config, reportInteraction } from '@grafana/runtime';
 | 
					import { config, reportInteraction } from '@grafana/runtime';
 | 
				
			||||||
import { Button, ConfirmModal, Space, Stack } from '@grafana/ui';
 | 
					import { Button, ConfirmModal, Space, Stack } from '@grafana/ui';
 | 
				
			||||||
import { QueryEditorModeToggle } from 'app/plugins/datasource/prometheus/querybuilder/shared/QueryEditorModeToggle';
 | 
					 | 
				
			||||||
import { QueryHeaderSwitch } from 'app/plugins/datasource/prometheus/querybuilder/shared/QueryHeaderSwitch';
 | 
					 | 
				
			||||||
import { QueryEditorMode } from 'app/plugins/datasource/prometheus/querybuilder/shared/types';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { LabelBrowserModal } from '../querybuilder/components/LabelBrowserModal';
 | 
					import { LabelBrowserModal } from '../querybuilder/components/LabelBrowserModal';
 | 
				
			||||||
import { LokiQueryBuilderContainer } from '../querybuilder/components/LokiQueryBuilderContainer';
 | 
					import { LokiQueryBuilderContainer } from '../querybuilder/components/LokiQueryBuilderContainer';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
import { NodeType, SyntaxNode } from '@lezer/common';
 | 
					import { NodeType, SyntaxNode } from '@lezer/common';
 | 
				
			||||||
import { sortBy } from 'lodash';
 | 
					import { sortBy } from 'lodash';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { QueryBuilderLabelFilter } from '@grafana/experimental';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  Identifier,
 | 
					  Identifier,
 | 
				
			||||||
  LabelFilter,
 | 
					  LabelFilter,
 | 
				
			||||||
| 
						 | 
					@ -22,8 +23,6 @@ import {
 | 
				
			||||||
  Expr,
 | 
					  Expr,
 | 
				
			||||||
} from '@grafana/lezer-logql';
 | 
					} from '@grafana/lezer-logql';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { QueryBuilderLabelFilter } from '../prometheus/querybuilder/shared/types';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { unescapeLabelValue } from './languageUtils';
 | 
					import { unescapeLabelValue } from './languageUtils';
 | 
				
			||||||
import { getNodePositionsFromQuery } from './queryUtils';
 | 
					import { getNodePositionsFromQuery } from './queryUtils';
 | 
				
			||||||
import { lokiQueryModeller as modeller } from './querybuilder/LokiQueryModeller';
 | 
					import { lokiQueryModeller as modeller } from './querybuilder/LokiQueryModeller';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -254,7 +254,7 @@ describe('LokiQueryModeller', () => {
 | 
				
			||||||
        operations: [],
 | 
					        operations: [],
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const def = modeller.getOperationDef('sum')!;
 | 
					      const def = modeller.getOperationDefinition('sum')!;
 | 
				
			||||||
      const result = def.addOperationHandler(def, query, modeller);
 | 
					      const result = def.addOperationHandler(def, query, modeller);
 | 
				
			||||||
      expect(result.operations[0].id).toBe('rate');
 | 
					      expect(result.operations[0].id).toBe('rate');
 | 
				
			||||||
      expect(result.operations[1].id).toBe('sum');
 | 
					      expect(result.operations[1].id).toBe('sum');
 | 
				
			||||||
| 
						 | 
					@ -266,7 +266,7 @@ describe('LokiQueryModeller', () => {
 | 
				
			||||||
        operations: [{ id: LokiOperationId.Json, params: [] }],
 | 
					        operations: [{ id: LokiOperationId.Json, params: [] }],
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const def = modeller.getOperationDef('sum')!;
 | 
					      const def = modeller.getOperationDefinition('sum')!;
 | 
				
			||||||
      const result = def.addOperationHandler(def, query, modeller);
 | 
					      const result = def.addOperationHandler(def, query, modeller);
 | 
				
			||||||
      expect(result.operations[0].id).toBe(LokiOperationId.Json);
 | 
					      expect(result.operations[0].id).toBe(LokiOperationId.Json);
 | 
				
			||||||
      expect(result.operations[1].id).toBe('rate');
 | 
					      expect(result.operations[1].id).toBe('rate');
 | 
				
			||||||
| 
						 | 
					@ -279,7 +279,7 @@ describe('LokiQueryModeller', () => {
 | 
				
			||||||
        operations: [{ id: 'rate', params: [] }],
 | 
					        operations: [{ id: 'rate', params: [] }],
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const def = modeller.getOperationDef(LokiOperationId.Json)!;
 | 
					      const def = modeller.getOperationDefinition(LokiOperationId.Json)!;
 | 
				
			||||||
      const result = def.addOperationHandler(def, query, modeller);
 | 
					      const result = def.addOperationHandler(def, query, modeller);
 | 
				
			||||||
      expect(result.operations[0].id).toBe(LokiOperationId.Json);
 | 
					      expect(result.operations[0].id).toBe(LokiOperationId.Json);
 | 
				
			||||||
      expect(result.operations[1].id).toBe('rate');
 | 
					      expect(result.operations[1].id).toBe('rate');
 | 
				
			||||||
| 
						 | 
					@ -291,7 +291,7 @@ describe('LokiQueryModeller', () => {
 | 
				
			||||||
        operations: [{ id: LokiOperationId.LineContains, params: ['error'] }],
 | 
					        operations: [{ id: LokiOperationId.LineContains, params: ['error'] }],
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const def = modeller.getOperationDef(LokiOperationId.Json)!;
 | 
					      const def = modeller.getOperationDefinition(LokiOperationId.Json)!;
 | 
				
			||||||
      const result = def.addOperationHandler(def, query, modeller);
 | 
					      const result = def.addOperationHandler(def, query, modeller);
 | 
				
			||||||
      expect(result.operations[0].id).toBe(LokiOperationId.LineContains);
 | 
					      expect(result.operations[0].id).toBe(LokiOperationId.LineContains);
 | 
				
			||||||
      expect(result.operations[1].id).toBe(LokiOperationId.Json);
 | 
					      expect(result.operations[1].id).toBe(LokiOperationId.Json);
 | 
				
			||||||
| 
						 | 
					@ -303,7 +303,7 @@ describe('LokiQueryModeller', () => {
 | 
				
			||||||
        operations: [{ id: LokiOperationId.Json, params: [] }],
 | 
					        operations: [{ id: LokiOperationId.Json, params: [] }],
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const def = modeller.getOperationDef(LokiOperationId.LineContains)!;
 | 
					      const def = modeller.getOperationDefinition(LokiOperationId.LineContains)!;
 | 
				
			||||||
      const result = def.addOperationHandler(def, query, modeller);
 | 
					      const result = def.addOperationHandler(def, query, modeller);
 | 
				
			||||||
      expect(result.operations[0].id).toBe(LokiOperationId.LineContains);
 | 
					      expect(result.operations[0].id).toBe(LokiOperationId.LineContains);
 | 
				
			||||||
      expect(result.operations[1].id).toBe(LokiOperationId.Json);
 | 
					      expect(result.operations[1].id).toBe(LokiOperationId.Json);
 | 
				
			||||||
| 
						 | 
					@ -315,7 +315,7 @@ describe('LokiQueryModeller', () => {
 | 
				
			||||||
        operations: [{ id: LokiOperationId.Rate, params: [] }],
 | 
					        operations: [{ id: LokiOperationId.Rate, params: [] }],
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const def = modeller.getOperationDef(LokiOperationId.Rate)!;
 | 
					      const def = modeller.getOperationDefinition(LokiOperationId.Rate)!;
 | 
				
			||||||
      const result = def.addOperationHandler(def, query, modeller);
 | 
					      const result = def.addOperationHandler(def, query, modeller);
 | 
				
			||||||
      expect(result.operations.length).toBe(1);
 | 
					      expect(result.operations.length).toBe(1);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
| 
						 | 
					@ -329,7 +329,7 @@ describe('LokiQueryModeller', () => {
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const def = modeller.getOperationDef(LokiOperationId.Unwrap)!;
 | 
					      const def = modeller.getOperationDefinition(LokiOperationId.Unwrap)!;
 | 
				
			||||||
      const result = def.addOperationHandler(def, query, modeller);
 | 
					      const result = def.addOperationHandler(def, query, modeller);
 | 
				
			||||||
      expect(result.operations[1].id).toBe(LokiOperationId.Unwrap);
 | 
					      expect(result.operations[1].id).toBe(LokiOperationId.Unwrap);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,17 @@
 | 
				
			||||||
import { LokiAndPromQueryModellerBase } from '../../prometheus/querybuilder/shared/LokiAndPromQueryModellerBase';
 | 
					import {
 | 
				
			||||||
import { QueryBuilderLabelFilter } from '../../prometheus/querybuilder/shared/types';
 | 
					  QueryModellerBase,
 | 
				
			||||||
 | 
					  QueryBuilderLabelFilter,
 | 
				
			||||||
 | 
					  VisualQuery,
 | 
				
			||||||
 | 
					  QueryBuilderOperation,
 | 
				
			||||||
 | 
					  VisualQueryBinary,
 | 
				
			||||||
 | 
					} from '@grafana/experimental';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { getOperationDefinitions } from './operations';
 | 
					import { operationDefinitions } from './operations';
 | 
				
			||||||
import { LokiOperationId, LokiQueryPattern, LokiQueryPatternType, LokiVisualQueryOperationCategory } from './types';
 | 
					import { LokiOperationId, LokiQueryPattern, LokiQueryPatternType, LokiVisualQueryOperationCategory } from './types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class LokiQueryModeller extends LokiAndPromQueryModellerBase {
 | 
					export class LokiQueryModeller extends QueryModellerBase {
 | 
				
			||||||
  constructor() {
 | 
					  constructor() {
 | 
				
			||||||
    super(getOperationDefinitions);
 | 
					    super(operationDefinitions, '<expr>');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.setOperationCategories([
 | 
					    this.setOperationCategories([
 | 
				
			||||||
      LokiVisualQueryOperationCategory.Aggregations,
 | 
					      LokiVisualQueryOperationCategory.Aggregations,
 | 
				
			||||||
| 
						 | 
					@ -18,12 +23,65 @@ export class LokiQueryModeller extends LokiAndPromQueryModellerBase {
 | 
				
			||||||
    ]);
 | 
					    ]);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  renderLabels(labels: QueryBuilderLabelFilter[]) {
 | 
					  renderOperations(queryString: string, operations: QueryBuilderOperation[]): string {
 | 
				
			||||||
 | 
					    for (const operation of operations) {
 | 
				
			||||||
 | 
					      const def = this.operationsRegistry.getIfExists(operation.id);
 | 
				
			||||||
 | 
					      if (!def) {
 | 
				
			||||||
 | 
					        console.error(`Could not find operation ${operation.id} in the registry`);
 | 
				
			||||||
 | 
					        continue;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      queryString = def.renderer(operation, def, queryString);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return queryString;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  renderBinaryQueries(queryString: string, binaryQueries?: Array<VisualQueryBinary<VisualQuery>>) {
 | 
				
			||||||
 | 
					    if (binaryQueries) {
 | 
				
			||||||
 | 
					      for (const binQuery of binaryQueries) {
 | 
				
			||||||
 | 
					        queryString = `${this.renderBinaryQuery(queryString, binQuery)}`;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return queryString;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private renderBinaryQuery(leftOperand: string, binaryQuery: VisualQueryBinary<VisualQuery>) {
 | 
				
			||||||
 | 
					    let result = leftOperand + ` ${binaryQuery.operator} `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (binaryQuery.vectorMatches) {
 | 
				
			||||||
 | 
					      result += `${binaryQuery.vectorMatchesType}(${binaryQuery.vectorMatches}) `;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result + this.renderQuery(binaryQuery.query, true);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  renderLabels(labels: QueryBuilderLabelFilter[]): string {
 | 
				
			||||||
    if (labels.length === 0) {
 | 
					    if (labels.length === 0) {
 | 
				
			||||||
      return '{}';
 | 
					      return '{}';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return super.renderLabels(labels);
 | 
					    let expr = '{';
 | 
				
			||||||
 | 
					    for (const filter of labels) {
 | 
				
			||||||
 | 
					      if (expr !== '{') {
 | 
				
			||||||
 | 
					        expr += ', ';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expr += `${filter.label}${filter.op}"${filter.value}"`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return expr + `}`;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  renderQuery(query: VisualQuery, nested?: boolean): string {
 | 
				
			||||||
 | 
					    let queryString = this.renderLabels(query.labels);
 | 
				
			||||||
 | 
					    queryString = this.renderOperations(queryString, query.operations);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!nested && this.hasBinaryOp(query) && Boolean(query.binaryQueries?.length)) {
 | 
				
			||||||
 | 
					      queryString = `(${queryString})`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    queryString = this.renderBinaryQueries(queryString, query.binaryQueries);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return queryString;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getQueryPatterns(): LokiQueryPattern[] {
 | 
					  getQueryPatterns(): LokiQueryPattern[] {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,8 @@
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  QueryBuilderOperation,
 | 
					  QueryBuilderOperation,
 | 
				
			||||||
  QueryBuilderOperationDef,
 | 
					  QueryBuilderOperationDefinition,
 | 
				
			||||||
  QueryBuilderOperationParamDef,
 | 
					  QueryBuilderOperationParamDef,
 | 
				
			||||||
} from '../../prometheus/querybuilder/shared/types';
 | 
					} from '@grafana/experimental';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { defaultAddOperationHandler } from './operationUtils';
 | 
					import { defaultAddOperationHandler } from './operationUtils';
 | 
				
			||||||
import { LokiOperationId, LokiVisualQueryOperationCategory } from './types';
 | 
					import { LokiOperationId, LokiVisualQueryOperationCategory } from './types';
 | 
				
			||||||
| 
						 | 
					@ -78,7 +78,7 @@ export const binaryScalarDefs = [
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Not sure about this one. It could also be a more generic 'Simple math operation' where user specifies
 | 
					// Not sure about this one. It could also be a more generic 'Simple math operation' where user specifies
 | 
				
			||||||
// both the operator and the operand in a single input
 | 
					// both the operator and the operand in a single input
 | 
				
			||||||
export const binaryScalarOperations: QueryBuilderOperationDef[] = binaryScalarDefs.map((opDef) => {
 | 
					export const binaryScalarOperations: QueryBuilderOperationDefinition[] = binaryScalarDefs.map((opDef) => {
 | 
				
			||||||
  const params: QueryBuilderOperationParamDef[] = [{ name: 'Value', type: 'number' }];
 | 
					  const params: QueryBuilderOperationParamDef[] = [{ name: 'Value', type: 'number' }];
 | 
				
			||||||
  const defaultParams: any[] = [2];
 | 
					  const defaultParams: any[] = [2];
 | 
				
			||||||
  if (opDef.comparison) {
 | 
					  if (opDef.comparison) {
 | 
				
			||||||
| 
						 | 
					@ -103,7 +103,11 @@ export const binaryScalarOperations: QueryBuilderOperationDef[] = binaryScalarDe
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getSimpleBinaryRenderer(operator: string) {
 | 
					function getSimpleBinaryRenderer(operator: string) {
 | 
				
			||||||
  return function binaryRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
 | 
					  return function binaryRenderer(
 | 
				
			||||||
 | 
					    model: QueryBuilderOperation,
 | 
				
			||||||
 | 
					    def: QueryBuilderOperationDefinition,
 | 
				
			||||||
 | 
					    innerExpr: string
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
    let param = model.params[0];
 | 
					    let param = model.params[0];
 | 
				
			||||||
    let bool = '';
 | 
					    let bool = '';
 | 
				
			||||||
    if (model.params.length === 2) {
 | 
					    if (model.params.length === 2) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,68 @@
 | 
				
			||||||
 | 
					import React, { useState } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { DataSourceApi, SelectableValue } from '@grafana/data';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  QueryBuilderLabelFilter,
 | 
				
			||||||
 | 
					  QueryBuilderOperationParamEditorProps,
 | 
				
			||||||
 | 
					  QueryBuilderOperationParamValue,
 | 
				
			||||||
 | 
					  VisualQuery,
 | 
				
			||||||
 | 
					  VisualQueryModeller,
 | 
				
			||||||
 | 
					} from '@grafana/experimental';
 | 
				
			||||||
 | 
					import { Select } from '@grafana/ui';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { getOperationParamId } from '../operationUtils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const LabelParamEditor = ({
 | 
				
			||||||
 | 
					  onChange,
 | 
				
			||||||
 | 
					  index,
 | 
				
			||||||
 | 
					  operationId,
 | 
				
			||||||
 | 
					  value,
 | 
				
			||||||
 | 
					  query,
 | 
				
			||||||
 | 
					  datasource,
 | 
				
			||||||
 | 
					  queryModeller,
 | 
				
			||||||
 | 
					}: QueryBuilderOperationParamEditorProps) => {
 | 
				
			||||||
 | 
					  const [state, setState] = useState<{
 | 
				
			||||||
 | 
					    options?: SelectableValue[];
 | 
				
			||||||
 | 
					    isLoading?: boolean;
 | 
				
			||||||
 | 
					  }>({});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Select<QueryBuilderOperationParamValue | undefined>
 | 
				
			||||||
 | 
					      inputId={getOperationParamId(operationId, index)}
 | 
				
			||||||
 | 
					      autoFocus={value === ''}
 | 
				
			||||||
 | 
					      openMenuOnFocus
 | 
				
			||||||
 | 
					      onOpenMenu={async () => {
 | 
				
			||||||
 | 
					        setState({ isLoading: true });
 | 
				
			||||||
 | 
					        const options = await loadGroupByLabels(query, datasource, queryModeller);
 | 
				
			||||||
 | 
					        setState({ options, isLoading: undefined });
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					      isLoading={state.isLoading}
 | 
				
			||||||
 | 
					      allowCustomValue
 | 
				
			||||||
 | 
					      noOptionsMessage="No labels found"
 | 
				
			||||||
 | 
					      loadingMessage="Loading labels"
 | 
				
			||||||
 | 
					      options={state.options}
 | 
				
			||||||
 | 
					      value={toOption(value)}
 | 
				
			||||||
 | 
					      onChange={(value) => onChange(index, value.value!)}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function loadGroupByLabels(
 | 
				
			||||||
 | 
					  query: VisualQuery,
 | 
				
			||||||
 | 
					  datasource: DataSourceApi,
 | 
				
			||||||
 | 
					  queryModeller: VisualQueryModeller
 | 
				
			||||||
 | 
					): Promise<SelectableValue[]> {
 | 
				
			||||||
 | 
					  let labels: QueryBuilderLabelFilter[] = query.labels;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const queryString = queryModeller.renderLabels(labels);
 | 
				
			||||||
 | 
					  const result = await datasource.languageProvider.fetchSeriesLabels(queryString);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return Object.keys(result).map((x) => ({
 | 
				
			||||||
 | 
					    label: x,
 | 
				
			||||||
 | 
					    value: x,
 | 
				
			||||||
 | 
					  }));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const toOption = (
 | 
				
			||||||
 | 
					  value: QueryBuilderOperationParamValue | undefined
 | 
				
			||||||
 | 
					): SelectableValue<QueryBuilderOperationParamValue | undefined> => ({ label: value?.toString(), value });
 | 
				
			||||||
| 
						 | 
					@ -5,13 +5,13 @@ import { getSelectParent } from 'test/helpers/selectOptionInTest';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { dateTime } from '@grafana/data';
 | 
					import { dateTime } from '@grafana/data';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { MISSING_LABEL_FILTER_ERROR_MESSAGE } from '../../../prometheus/querybuilder/shared/LabelFilters';
 | 
					 | 
				
			||||||
import { createLokiDatasource } from '../../__mocks__/datasource';
 | 
					import { createLokiDatasource } from '../../__mocks__/datasource';
 | 
				
			||||||
import { LokiOperationId, LokiVisualQuery } from '../types';
 | 
					import { LokiOperationId, LokiVisualQuery } from '../types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { LokiQueryBuilder } from './LokiQueryBuilder';
 | 
					import { LokiQueryBuilder } from './LokiQueryBuilder';
 | 
				
			||||||
import { EXPLAIN_LABEL_FILTER_CONTENT } from './LokiQueryBuilderExplained';
 | 
					import { EXPLAIN_LABEL_FILTER_CONTENT } from './LokiQueryBuilderExplained';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const MISSING_LABEL_FILTER_ERROR_MESSAGE = 'Select at least 1 label filter (label and value)';
 | 
				
			||||||
const defaultQuery: LokiVisualQuery = {
 | 
					const defaultQuery: LokiVisualQuery = {
 | 
				
			||||||
  labels: [{ op: '=', label: 'baz', value: 'bar' }],
 | 
					  labels: [{ op: '=', label: 'baz', value: 'bar' }],
 | 
				
			||||||
  operations: [],
 | 
					  operations: [],
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,25 +1,28 @@
 | 
				
			||||||
import React, { useEffect, useMemo, useState } from 'react';
 | 
					import React, { useEffect, useMemo, useState } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { DataSourceApi, getDefaultTimeRange, LoadingState, PanelData, SelectableValue, TimeRange } from '@grafana/data';
 | 
					import { DataSourceApi, getDefaultTimeRange, LoadingState, PanelData, SelectableValue, TimeRange } from '@grafana/data';
 | 
				
			||||||
import { EditorRow } from '@grafana/experimental';
 | 
					 | 
				
			||||||
import { config } from '@grafana/runtime';
 | 
					 | 
				
			||||||
import { LabelFilters } from 'app/plugins/datasource/prometheus/querybuilder/shared/LabelFilters';
 | 
					 | 
				
			||||||
import { OperationExplainedBox } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationExplainedBox';
 | 
					 | 
				
			||||||
import { OperationList } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationList';
 | 
					 | 
				
			||||||
import { OperationListExplained } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationListExplained';
 | 
					 | 
				
			||||||
import { OperationsEditorRow } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationsEditorRow';
 | 
					 | 
				
			||||||
import { QueryBuilderHints } from 'app/plugins/datasource/prometheus/querybuilder/shared/QueryBuilderHints';
 | 
					 | 
				
			||||||
import { RawQuery } from 'app/plugins/datasource/prometheus/querybuilder/shared/RawQuery';
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					  EditorRow,
 | 
				
			||||||
 | 
					  // LabelFilters, this is broken in @grafana/experimental so we need to use the one from prometheus
 | 
				
			||||||
 | 
					  OperationExplainedBox,
 | 
				
			||||||
 | 
					  OperationList,
 | 
				
			||||||
 | 
					  OperationListExplained,
 | 
				
			||||||
 | 
					  OperationsEditorRow,
 | 
				
			||||||
 | 
					  QueryBuilderHints,
 | 
				
			||||||
 | 
					  RawQuery,
 | 
				
			||||||
  QueryBuilderLabelFilter,
 | 
					  QueryBuilderLabelFilter,
 | 
				
			||||||
  QueryBuilderOperation,
 | 
					  QueryBuilderOperation,
 | 
				
			||||||
} from 'app/plugins/datasource/prometheus/querybuilder/shared/types';
 | 
					} from '@grafana/experimental';
 | 
				
			||||||
 | 
					import { config } from '@grafana/runtime';
 | 
				
			||||||
 | 
					import { LabelFilters } from 'app/plugins/datasource/prometheus/querybuilder/shared/LabelFilters';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { testIds } from '../../components/LokiQueryEditor';
 | 
					import { testIds } from '../../components/LokiQueryEditor';
 | 
				
			||||||
import { LokiDatasource } from '../../datasource';
 | 
					import { LokiDatasource } from '../../datasource';
 | 
				
			||||||
import { escapeLabelValueInSelector } from '../../languageUtils';
 | 
					import { escapeLabelValueInSelector } from '../../languageUtils';
 | 
				
			||||||
import logqlGrammar from '../../syntax';
 | 
					import logqlGrammar from '../../syntax';
 | 
				
			||||||
 | 
					import { LokiQuery } from '../../types';
 | 
				
			||||||
import { lokiQueryModeller } from '../LokiQueryModeller';
 | 
					import { lokiQueryModeller } from '../LokiQueryModeller';
 | 
				
			||||||
 | 
					import { isConflictingFilter } from '../operationUtils';
 | 
				
			||||||
import { buildVisualQueryFromString } from '../parsing';
 | 
					import { buildVisualQueryFromString } from '../parsing';
 | 
				
			||||||
import { LokiOperationId, LokiVisualQuery } from '../types';
 | 
					import { LokiOperationId, LokiVisualQuery } from '../types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -130,7 +133,7 @@ export const LokiQueryBuilder = React.memo<Props>(
 | 
				
			||||||
        {showExplain && (
 | 
					        {showExplain && (
 | 
				
			||||||
          <OperationExplainedBox
 | 
					          <OperationExplainedBox
 | 
				
			||||||
            stepNumber={1}
 | 
					            stepNumber={1}
 | 
				
			||||||
            title={<RawQuery query={`${lokiQueryModeller.renderLabels(query.labels)}`} lang={lang} />}
 | 
					            title={<RawQuery query={`${lokiQueryModeller.renderLabels(query.labels)}`} language={lang} />}
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            {EXPLAIN_LABEL_FILTER_CONTENT}
 | 
					            {EXPLAIN_LABEL_FILTER_CONTENT}
 | 
				
			||||||
          </OperationExplainedBox>
 | 
					          </OperationExplainedBox>
 | 
				
			||||||
| 
						 | 
					@ -143,14 +146,19 @@ export const LokiQueryBuilder = React.memo<Props>(
 | 
				
			||||||
            onRunQuery={onRunQuery}
 | 
					            onRunQuery={onRunQuery}
 | 
				
			||||||
            datasource={datasource as DataSourceApi}
 | 
					            datasource={datasource as DataSourceApi}
 | 
				
			||||||
            highlightedOp={highlightedOp}
 | 
					            highlightedOp={highlightedOp}
 | 
				
			||||||
 | 
					            isConflictingOperation={(operation: QueryBuilderOperation, otherOperations: QueryBuilderOperation[]) =>
 | 
				
			||||||
 | 
					              operation.id === LokiOperationId.LabelFilter && isConflictingFilter(operation, otherOperations)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
          <QueryBuilderHints<LokiVisualQuery>
 | 
					          <QueryBuilderHints<LokiVisualQuery, LokiQuery>
 | 
				
			||||||
            datasource={datasource}
 | 
					            datasource={datasource}
 | 
				
			||||||
            query={query}
 | 
					            query={query}
 | 
				
			||||||
            onChange={onChange}
 | 
					            onChange={onChange}
 | 
				
			||||||
            data={sampleData}
 | 
					            data={sampleData}
 | 
				
			||||||
            queryModeller={lokiQueryModeller}
 | 
					            queryModeller={lokiQueryModeller}
 | 
				
			||||||
            buildVisualQueryFromString={buildVisualQueryFromString}
 | 
					            buildVisualQueryFromString={buildVisualQueryFromString}
 | 
				
			||||||
 | 
					            buildDataQueryFromQueryString={(queryString) => ({ expr: queryString, refId: 'hints' })}
 | 
				
			||||||
 | 
					            buildQueryStringFromDataQuery={(query) => query.expr}
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        </OperationsEditorRow>
 | 
					        </OperationsEditorRow>
 | 
				
			||||||
        {showExplain && (
 | 
					        {showExplain && (
 | 
				
			||||||
| 
						 | 
					@ -158,7 +166,7 @@ export const LokiQueryBuilder = React.memo<Props>(
 | 
				
			||||||
            stepNumber={2}
 | 
					            stepNumber={2}
 | 
				
			||||||
            queryModeller={lokiQueryModeller}
 | 
					            queryModeller={lokiQueryModeller}
 | 
				
			||||||
            query={query}
 | 
					            query={query}
 | 
				
			||||||
            lang={lang}
 | 
					            language={lang}
 | 
				
			||||||
            onMouseEnter={(op) => {
 | 
					            onMouseEnter={(op) => {
 | 
				
			||||||
              setHighlightedOp(op);
 | 
					              setHighlightedOp(op);
 | 
				
			||||||
            }}
 | 
					            }}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,7 @@
 | 
				
			||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { OperationExplainedBox, OperationListExplained, RawQuery } from '@grafana/experimental';
 | 
				
			||||||
import { Stack } from '@grafana/ui';
 | 
					import { Stack } from '@grafana/ui';
 | 
				
			||||||
import { OperationExplainedBox } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationExplainedBox';
 | 
					 | 
				
			||||||
import { OperationListExplained } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationListExplained';
 | 
					 | 
				
			||||||
import { RawQuery } from 'app/plugins/datasource/prometheus/querybuilder/shared/RawQuery';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { lokiGrammar } from '../../syntax';
 | 
					import { lokiGrammar } from '../../syntax';
 | 
				
			||||||
import { lokiQueryModeller } from '../LokiQueryModeller';
 | 
					import { lokiQueryModeller } from '../LokiQueryModeller';
 | 
				
			||||||
| 
						 | 
					@ -24,7 +22,7 @@ export const LokiQueryBuilderExplained = React.memo<Props>(({ query }) => {
 | 
				
			||||||
    <Stack gap={0} direction="column">
 | 
					    <Stack gap={0} direction="column">
 | 
				
			||||||
      <OperationExplainedBox
 | 
					      <OperationExplainedBox
 | 
				
			||||||
        stepNumber={1}
 | 
					        stepNumber={1}
 | 
				
			||||||
        title={<RawQuery query={`${lokiQueryModeller.renderLabels(visQuery.labels)}`} lang={lang} />}
 | 
					        title={<RawQuery query={`${lokiQueryModeller.renderLabels(visQuery.labels)}`} language={lang} />}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        {EXPLAIN_LABEL_FILTER_CONTENT}
 | 
					        {EXPLAIN_LABEL_FILTER_CONTENT}
 | 
				
			||||||
      </OperationExplainedBox>
 | 
					      </OperationExplainedBox>
 | 
				
			||||||
| 
						 | 
					@ -32,7 +30,7 @@ export const LokiQueryBuilderExplained = React.memo<Props>(({ query }) => {
 | 
				
			||||||
        stepNumber={2}
 | 
					        stepNumber={2}
 | 
				
			||||||
        queryModeller={lokiQueryModeller}
 | 
					        queryModeller={lokiQueryModeller}
 | 
				
			||||||
        query={visQuery}
 | 
					        query={visQuery}
 | 
				
			||||||
        lang={lang}
 | 
					        language={lang}
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
    </Stack>
 | 
					    </Stack>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,10 +2,9 @@ import { trim } from 'lodash';
 | 
				
			||||||
import React, { useMemo, useState } from 'react';
 | 
					import React, { useMemo, useState } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { CoreApp, isValidDuration, isValidGrafanaDuration, SelectableValue } from '@grafana/data';
 | 
					import { CoreApp, isValidDuration, isValidGrafanaDuration, SelectableValue } from '@grafana/data';
 | 
				
			||||||
import { EditorField, EditorRow } from '@grafana/experimental';
 | 
					import { EditorField, EditorRow, QueryOptionGroup } from '@grafana/experimental';
 | 
				
			||||||
import { config, reportInteraction } from '@grafana/runtime';
 | 
					import { config, reportInteraction } from '@grafana/runtime';
 | 
				
			||||||
import { Alert, AutoSizeInput, RadioButtonGroup, Select } from '@grafana/ui';
 | 
					import { Alert, AutoSizeInput, RadioButtonGroup, Select } from '@grafana/ui';
 | 
				
			||||||
import { QueryOptionGroup } from 'app/plugins/datasource/prometheus/querybuilder/shared/QueryOptionGroup';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { preprocessMaxLines, queryTypeOptions, RESOLUTION_OPTIONS } from '../../components/LokiOptionFields';
 | 
					import { preprocessMaxLines, queryTypeOptions, RESOLUTION_OPTIONS } from '../../components/LokiOptionFields';
 | 
				
			||||||
import { getLokiQueryType, isLogsQuery } from '../../queryUtils';
 | 
					import { getLokiQueryType, isLogsQuery } from '../../queryUtils';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,8 +2,8 @@ import { css } from '@emotion/css';
 | 
				
			||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { GrafanaTheme2 } from '@grafana/data';
 | 
					import { GrafanaTheme2 } from '@grafana/data';
 | 
				
			||||||
 | 
					import { RawQuery } from '@grafana/experimental';
 | 
				
			||||||
import { Button, Card, useStyles2 } from '@grafana/ui';
 | 
					import { Button, Card, useStyles2 } from '@grafana/ui';
 | 
				
			||||||
import { RawQuery } from 'app/plugins/datasource/prometheus/querybuilder/shared/RawQuery';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import logqlGrammar from '../../syntax';
 | 
					import logqlGrammar from '../../syntax';
 | 
				
			||||||
import { lokiQueryModeller } from '../LokiQueryModeller';
 | 
					import { lokiQueryModeller } from '../LokiQueryModeller';
 | 
				
			||||||
| 
						 | 
					@ -31,7 +31,7 @@ export const QueryPattern = (props: Props) => {
 | 
				
			||||||
      <div className={styles.rawQueryContainer}>
 | 
					      <div className={styles.rawQueryContainer}>
 | 
				
			||||||
        <RawQuery
 | 
					        <RawQuery
 | 
				
			||||||
          query={lokiQueryModeller.renderQuery({ labels: [], operations: pattern.operations })}
 | 
					          query={lokiQueryModeller.renderQuery({ labels: [], operations: pattern.operations })}
 | 
				
			||||||
          lang={lang}
 | 
					          language={lang}
 | 
				
			||||||
          className={styles.rawQuery}
 | 
					          className={styles.rawQuery}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,7 @@
 | 
				
			||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { EditorRow, EditorFieldGroup } from '@grafana/experimental';
 | 
					import { EditorRow, EditorFieldGroup, RawQuery } from '@grafana/experimental';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { RawQuery } from '../../../prometheus/querybuilder/shared/RawQuery';
 | 
					 | 
				
			||||||
import { lokiGrammar } from '../../syntax';
 | 
					import { lokiGrammar } from '../../syntax';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface Props {
 | 
					export interface Props {
 | 
				
			||||||
| 
						 | 
					@ -13,7 +12,7 @@ export function QueryPreview({ query }: Props) {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <EditorRow>
 | 
					    <EditorRow>
 | 
				
			||||||
      <EditorFieldGroup>
 | 
					      <EditorFieldGroup>
 | 
				
			||||||
        <RawQuery query={query} lang={{ grammar: lokiGrammar, name: 'lokiql' }} />
 | 
					        <RawQuery query={query} language={{ grammar: lokiGrammar, name: 'lokiql' }} />
 | 
				
			||||||
      </EditorFieldGroup>
 | 
					      </EditorFieldGroup>
 | 
				
			||||||
    </EditorRow>
 | 
					    </EditorRow>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,14 +3,12 @@ import userEvent from '@testing-library/user-event';
 | 
				
			||||||
import React, { ComponentProps } from 'react';
 | 
					import React, { ComponentProps } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { DataFrame, DataSourceApi, FieldType, toDataFrame } from '@grafana/data';
 | 
					import { DataFrame, DataSourceApi, FieldType, toDataFrame } from '@grafana/data';
 | 
				
			||||||
 | 
					import { QueryBuilderOperation, QueryBuilderOperationParamDef } from '@grafana/experimental';
 | 
				
			||||||
import { config } from '@grafana/runtime';
 | 
					import { config } from '@grafana/runtime';
 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  QueryBuilderOperation,
 | 
					 | 
				
			||||||
  QueryBuilderOperationParamDef,
 | 
					 | 
				
			||||||
} from 'app/plugins/datasource/prometheus/querybuilder/shared/types';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { createLokiDatasource } from '../../__mocks__/datasource';
 | 
					import { createLokiDatasource } from '../../__mocks__/datasource';
 | 
				
			||||||
import { LokiDatasource } from '../../datasource';
 | 
					import { LokiDatasource } from '../../datasource';
 | 
				
			||||||
 | 
					import { LokiQueryModeller } from '../LokiQueryModeller';
 | 
				
			||||||
import { LokiOperationId } from '../types';
 | 
					import { LokiOperationId } from '../types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { UnwrapParamEditor } from './UnwrapParamEditor';
 | 
					import { UnwrapParamEditor } from './UnwrapParamEditor';
 | 
				
			||||||
| 
						 | 
					@ -96,6 +94,9 @@ const createProps = (
 | 
				
			||||||
    paramDef: {} as QueryBuilderOperationParamDef,
 | 
					    paramDef: {} as QueryBuilderOperationParamDef,
 | 
				
			||||||
    operation: {} as QueryBuilderOperation,
 | 
					    operation: {} as QueryBuilderOperation,
 | 
				
			||||||
    datasource: createLokiDatasource() as DataSourceApi,
 | 
					    datasource: createLokiDatasource() as DataSourceApi,
 | 
				
			||||||
 | 
					    queryModeller: {
 | 
				
			||||||
 | 
					      renderQuery: jest.fn().mockReturnValue('sum_over_time({foo="bar"} | logfmt | unwrap [5m])'),
 | 
				
			||||||
 | 
					    } as unknown as LokiQueryModeller,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  const props = { ...propsDefault, ...propsOverrides };
 | 
					  const props = { ...propsDefault, ...propsOverrides };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,15 +1,14 @@
 | 
				
			||||||
import React, { useState } from 'react';
 | 
					import React, { useState } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { SelectableValue, getDefaultTimeRange, toOption } from '@grafana/data';
 | 
					import { SelectableValue, getDefaultTimeRange, toOption } from '@grafana/data';
 | 
				
			||||||
 | 
					import { QueryBuilderOperationParamEditorProps, VisualQueryModeller } from '@grafana/experimental';
 | 
				
			||||||
import { config } from '@grafana/runtime';
 | 
					import { config } from '@grafana/runtime';
 | 
				
			||||||
import { Select } from '@grafana/ui';
 | 
					import { Select } from '@grafana/ui';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { QueryBuilderOperationParamEditorProps } from '../../../prometheus/querybuilder/shared/types';
 | 
					 | 
				
			||||||
import { placeHolderScopedVars } from '../../components/monaco-query-field/monaco-completion-provider/validation';
 | 
					import { placeHolderScopedVars } from '../../components/monaco-query-field/monaco-completion-provider/validation';
 | 
				
			||||||
import { LokiDatasource } from '../../datasource';
 | 
					import { LokiDatasource } from '../../datasource';
 | 
				
			||||||
import { getLogQueryFromMetricsQuery, isQueryWithError } from '../../queryUtils';
 | 
					import { getLogQueryFromMetricsQuery, isQueryWithError } from '../../queryUtils';
 | 
				
			||||||
import { extractUnwrapLabelKeysFromDataFrame } from '../../responseUtils';
 | 
					import { extractUnwrapLabelKeysFromDataFrame } from '../../responseUtils';
 | 
				
			||||||
import { lokiQueryModeller } from '../LokiQueryModeller';
 | 
					 | 
				
			||||||
import { getOperationParamId } from '../operationUtils';
 | 
					import { getOperationParamId } from '../operationUtils';
 | 
				
			||||||
import { LokiVisualQuery } from '../types';
 | 
					import { LokiVisualQuery } from '../types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,6 +20,7 @@ export function UnwrapParamEditor({
 | 
				
			||||||
  query,
 | 
					  query,
 | 
				
			||||||
  datasource,
 | 
					  datasource,
 | 
				
			||||||
  timeRange,
 | 
					  timeRange,
 | 
				
			||||||
 | 
					  queryModeller,
 | 
				
			||||||
}: QueryBuilderOperationParamEditorProps) {
 | 
					}: QueryBuilderOperationParamEditorProps) {
 | 
				
			||||||
  const [state, setState] = useState<{
 | 
					  const [state, setState] = useState<{
 | 
				
			||||||
    options?: Array<SelectableValue<string>>;
 | 
					    options?: Array<SelectableValue<string>>;
 | 
				
			||||||
| 
						 | 
					@ -34,7 +34,7 @@ export function UnwrapParamEditor({
 | 
				
			||||||
        // This check is always true, we do it to make typescript happy
 | 
					        // This check is always true, we do it to make typescript happy
 | 
				
			||||||
        if (datasource instanceof LokiDatasource && config.featureToggles.lokiQueryHints) {
 | 
					        if (datasource instanceof LokiDatasource && config.featureToggles.lokiQueryHints) {
 | 
				
			||||||
          setState({ isLoading: true });
 | 
					          setState({ isLoading: true });
 | 
				
			||||||
          const options = await loadUnwrapOptions(query, datasource, timeRange);
 | 
					          const options = await loadUnwrapOptions(query, datasource, queryModeller, timeRange);
 | 
				
			||||||
          setState({ options, isLoading: undefined });
 | 
					          setState({ options, isLoading: undefined });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }}
 | 
					      }}
 | 
				
			||||||
| 
						 | 
					@ -56,9 +56,10 @@ export function UnwrapParamEditor({
 | 
				
			||||||
async function loadUnwrapOptions(
 | 
					async function loadUnwrapOptions(
 | 
				
			||||||
  query: LokiVisualQuery,
 | 
					  query: LokiVisualQuery,
 | 
				
			||||||
  datasource: LokiDatasource,
 | 
					  datasource: LokiDatasource,
 | 
				
			||||||
 | 
					  queryModeller: VisualQueryModeller,
 | 
				
			||||||
  timeRange = getDefaultTimeRange()
 | 
					  timeRange = getDefaultTimeRange()
 | 
				
			||||||
): Promise<Array<SelectableValue<string>>> {
 | 
					): Promise<Array<SelectableValue<string>>> {
 | 
				
			||||||
  const queryExpr = lokiQueryModeller.renderQuery(query);
 | 
					  const queryExpr = queryModeller.renderQuery(query);
 | 
				
			||||||
  const logExpr = getLogQueryFromMetricsQuery(queryExpr);
 | 
					  const logExpr = getLogQueryFromMetricsQuery(queryExpr);
 | 
				
			||||||
  if (isQueryWithError(datasource.interpolateString(logExpr, placeHolderScopedVars))) {
 | 
					  if (isQueryWithError(datasource.interpolateString(logExpr, placeHolderScopedVars))) {
 | 
				
			||||||
    return [];
 | 
					    return [];
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
import { QueryBuilderOperation, QueryBuilderOperationDef } from '../../prometheus/querybuilder/shared/types';
 | 
					import { QueryBuilderOperation, QueryBuilderOperationDefinition } from '@grafana/experimental';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  createAggregationOperation,
 | 
					  createAggregationOperation,
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,7 @@ import {
 | 
				
			||||||
  labelFilterRenderer,
 | 
					  labelFilterRenderer,
 | 
				
			||||||
  pipelineRenderer,
 | 
					  pipelineRenderer,
 | 
				
			||||||
} from './operationUtils';
 | 
					} from './operationUtils';
 | 
				
			||||||
import { getOperationDefinitions } from './operations';
 | 
					import { operationDefinitions } from './operations';
 | 
				
			||||||
import { LokiOperationId, LokiVisualQueryOperationCategory } from './types';
 | 
					import { LokiOperationId, LokiVisualQueryOperationCategory } from './types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('createRangeOperation', () => {
 | 
					describe('createRangeOperation', () => {
 | 
				
			||||||
| 
						 | 
					@ -149,7 +149,7 @@ describe('getLineFilterRenderer', () => {
 | 
				
			||||||
    params: ['`error`'],
 | 
					    params: ['`error`'],
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const MOCK_DEF = undefined as unknown as QueryBuilderOperationDef;
 | 
					  const MOCK_DEF = undefined as unknown as QueryBuilderOperationDefinition;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const MOCK_INNER_EXPR = '{job="grafana"}';
 | 
					  const MOCK_INNER_EXPR = '{job="grafana"}';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -178,7 +178,7 @@ describe('getLineFilterRenderer', () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('labelFilterRenderer', () => {
 | 
					describe('labelFilterRenderer', () => {
 | 
				
			||||||
  const MOCK_MODEL = { id: '__label_filter', params: ['label', '', 'value'] };
 | 
					  const MOCK_MODEL = { id: '__label_filter', params: ['label', '', 'value'] };
 | 
				
			||||||
  const MOCK_DEF = undefined as unknown as QueryBuilderOperationDef;
 | 
					  const MOCK_DEF = undefined as unknown as QueryBuilderOperationDefinition;
 | 
				
			||||||
  const MOCK_INNER_EXPR = '{job="grafana"}';
 | 
					  const MOCK_INNER_EXPR = '{job="grafana"}';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it.each`
 | 
					  it.each`
 | 
				
			||||||
| 
						 | 
					@ -220,17 +220,12 @@ describe('isConflictingFilter', () => {
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('pipelineRenderer', () => {
 | 
					describe('pipelineRenderer', () => {
 | 
				
			||||||
  let definitions: QueryBuilderOperationDef[];
 | 
					 | 
				
			||||||
  beforeEach(() => {
 | 
					 | 
				
			||||||
    definitions = getOperationDefinitions();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('correctly renders unpack expressions', () => {
 | 
					  it('correctly renders unpack expressions', () => {
 | 
				
			||||||
    const model: QueryBuilderOperation = {
 | 
					    const model: QueryBuilderOperation = {
 | 
				
			||||||
      id: LokiOperationId.Unpack,
 | 
					      id: LokiOperationId.Unpack,
 | 
				
			||||||
      params: [],
 | 
					      params: [],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    const definition = definitions.find((def) => def.id === LokiOperationId.Unpack);
 | 
					    const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Unpack);
 | 
				
			||||||
    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | unpack');
 | 
					    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | unpack');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -239,7 +234,7 @@ describe('pipelineRenderer', () => {
 | 
				
			||||||
      id: LokiOperationId.Unpack,
 | 
					      id: LokiOperationId.Unpack,
 | 
				
			||||||
      params: [],
 | 
					      params: [],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    const definition = definitions.find((def) => def.id === LokiOperationId.Unpack);
 | 
					    const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Unpack);
 | 
				
			||||||
    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | unpack');
 | 
					    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | unpack');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -248,7 +243,7 @@ describe('pipelineRenderer', () => {
 | 
				
			||||||
      id: LokiOperationId.Logfmt,
 | 
					      id: LokiOperationId.Logfmt,
 | 
				
			||||||
      params: [],
 | 
					      params: [],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    const definition = definitions.find((def) => def.id === LokiOperationId.Logfmt);
 | 
					    const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Logfmt);
 | 
				
			||||||
    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | logfmt');
 | 
					    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | logfmt');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -257,7 +252,7 @@ describe('pipelineRenderer', () => {
 | 
				
			||||||
      id: LokiOperationId.Logfmt,
 | 
					      id: LokiOperationId.Logfmt,
 | 
				
			||||||
      params: [true, false, 'foo', ''],
 | 
					      params: [true, false, 'foo', ''],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    const definition = definitions.find((def) => def.id === LokiOperationId.Logfmt);
 | 
					    const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Logfmt);
 | 
				
			||||||
    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | logfmt --strict foo');
 | 
					    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | logfmt --strict foo');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -266,7 +261,7 @@ describe('pipelineRenderer', () => {
 | 
				
			||||||
      id: LokiOperationId.Logfmt,
 | 
					      id: LokiOperationId.Logfmt,
 | 
				
			||||||
      params: [true, false, 'foo', 'bar', 'baz'],
 | 
					      params: [true, false, 'foo', 'bar', 'baz'],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    const definition = definitions.find((def) => def.id === LokiOperationId.Logfmt);
 | 
					    const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Logfmt);
 | 
				
			||||||
    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | logfmt --strict foo, bar, baz');
 | 
					    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | logfmt --strict foo, bar, baz');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -275,7 +270,7 @@ describe('pipelineRenderer', () => {
 | 
				
			||||||
      id: LokiOperationId.Json,
 | 
					      id: LokiOperationId.Json,
 | 
				
			||||||
      params: [],
 | 
					      params: [],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    const definition = definitions.find((def) => def.id === LokiOperationId.Json);
 | 
					    const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Json);
 | 
				
			||||||
    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | json');
 | 
					    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | json');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -284,7 +279,7 @@ describe('pipelineRenderer', () => {
 | 
				
			||||||
      id: LokiOperationId.Json,
 | 
					      id: LokiOperationId.Json,
 | 
				
			||||||
      params: ['foo', ''],
 | 
					      params: ['foo', ''],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    const definition = definitions.find((def) => def.id === LokiOperationId.Json);
 | 
					    const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Json);
 | 
				
			||||||
    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | json foo');
 | 
					    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | json foo');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -293,7 +288,7 @@ describe('pipelineRenderer', () => {
 | 
				
			||||||
      id: LokiOperationId.Json,
 | 
					      id: LokiOperationId.Json,
 | 
				
			||||||
      params: ['foo', 'bar', 'baz'],
 | 
					      params: ['foo', 'bar', 'baz'],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    const definition = definitions.find((def) => def.id === LokiOperationId.Json);
 | 
					    const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Json);
 | 
				
			||||||
    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | json foo, bar, baz');
 | 
					    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | json foo, bar, baz');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -302,7 +297,7 @@ describe('pipelineRenderer', () => {
 | 
				
			||||||
      id: LokiOperationId.Keep,
 | 
					      id: LokiOperationId.Keep,
 | 
				
			||||||
      params: ['foo', ''],
 | 
					      params: ['foo', ''],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    const definition = definitions.find((def) => def.id === LokiOperationId.Keep);
 | 
					    const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Keep);
 | 
				
			||||||
    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | keep foo');
 | 
					    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | keep foo');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -311,7 +306,7 @@ describe('pipelineRenderer', () => {
 | 
				
			||||||
      id: LokiOperationId.Keep,
 | 
					      id: LokiOperationId.Keep,
 | 
				
			||||||
      params: ['foo', 'bar', 'baz'],
 | 
					      params: ['foo', 'bar', 'baz'],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    const definition = definitions.find((def) => def.id === LokiOperationId.Keep);
 | 
					    const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Keep);
 | 
				
			||||||
    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | keep foo, bar, baz');
 | 
					    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | keep foo, bar, baz');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -320,7 +315,7 @@ describe('pipelineRenderer', () => {
 | 
				
			||||||
      id: LokiOperationId.Drop,
 | 
					      id: LokiOperationId.Drop,
 | 
				
			||||||
      params: ['foo', ''],
 | 
					      params: ['foo', ''],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    const definition = definitions.find((def) => def.id === LokiOperationId.Drop);
 | 
					    const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Drop);
 | 
				
			||||||
    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | drop foo');
 | 
					    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | drop foo');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -329,7 +324,7 @@ describe('pipelineRenderer', () => {
 | 
				
			||||||
      id: LokiOperationId.Drop,
 | 
					      id: LokiOperationId.Drop,
 | 
				
			||||||
      params: ['foo', 'bar', 'baz'],
 | 
					      params: ['foo', 'bar', 'baz'],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    const definition = definitions.find((def) => def.id === LokiOperationId.Drop);
 | 
					    const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Drop);
 | 
				
			||||||
    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | drop foo, bar, baz');
 | 
					    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | drop foo, bar, baz');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,21 +1,25 @@
 | 
				
			||||||
import { capitalize } from 'lodash';
 | 
					import { capitalize } from 'lodash';
 | 
				
			||||||
import pluralize from 'pluralize';
 | 
					import pluralize from 'pluralize';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { LabelParamEditor } from '../../prometheus/querybuilder/components/LabelParamEditor';
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  QueryBuilderOperation,
 | 
					  QueryBuilderOperation,
 | 
				
			||||||
  QueryBuilderOperationDef,
 | 
					  QueryBuilderOperationDefinition,
 | 
				
			||||||
  QueryBuilderOperationParamDef,
 | 
					  QueryBuilderOperationParamDef,
 | 
				
			||||||
  QueryBuilderOperationParamValue,
 | 
					  QueryBuilderOperationParamValue,
 | 
				
			||||||
  QueryWithOperations,
 | 
					  VisualQuery,
 | 
				
			||||||
  VisualQueryModeller,
 | 
					  VisualQueryModeller,
 | 
				
			||||||
} from '../../prometheus/querybuilder/shared/types';
 | 
					} from '@grafana/experimental';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { escapeLabelValueInExactSelector } from '../languageUtils';
 | 
					import { escapeLabelValueInExactSelector } from '../languageUtils';
 | 
				
			||||||
import { FUNCTIONS } from '../syntax';
 | 
					import { FUNCTIONS } from '../syntax';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { LabelParamEditor } from './components/LabelParamEditor';
 | 
				
			||||||
import { LokiOperationId, LokiOperationOrder, LokiVisualQuery, LokiVisualQueryOperationCategory } from './types';
 | 
					import { LokiOperationId, LokiOperationOrder, LokiVisualQuery, LokiVisualQueryOperationCategory } from './types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function createRangeOperation(name: string, isRangeOperationWithGrouping?: boolean): QueryBuilderOperationDef {
 | 
					export function createRangeOperation(
 | 
				
			||||||
 | 
					  name: string,
 | 
				
			||||||
 | 
					  isRangeOperationWithGrouping?: boolean
 | 
				
			||||||
 | 
					): QueryBuilderOperationDefinition {
 | 
				
			||||||
  const params = [getRangeVectorParamDef()];
 | 
					  const params = [getRangeVectorParamDef()];
 | 
				
			||||||
  const defaultParams = ['$__auto'];
 | 
					  const defaultParams = ['$__auto'];
 | 
				
			||||||
  let paramChangedHandler = undefined;
 | 
					  let paramChangedHandler = undefined;
 | 
				
			||||||
| 
						 | 
					@ -62,11 +66,11 @@ export function createRangeOperation(name: string, isRangeOperationWithGrouping?
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function createRangeOperationWithGrouping(name: string): QueryBuilderOperationDef[] {
 | 
					export function createRangeOperationWithGrouping(name: string): QueryBuilderOperationDefinition[] {
 | 
				
			||||||
  const rangeOperation = createRangeOperation(name, true);
 | 
					  const rangeOperation = createRangeOperation(name, true);
 | 
				
			||||||
  // Copy range operation params without the last param
 | 
					  // Copy range operation params without the last param
 | 
				
			||||||
  const params = rangeOperation.params.slice(0, -1);
 | 
					  const params = rangeOperation.params.slice(0, -1);
 | 
				
			||||||
  const operations: QueryBuilderOperationDef[] = [
 | 
					  const operations: QueryBuilderOperationDefinition[] = [
 | 
				
			||||||
    rangeOperation,
 | 
					    rangeOperation,
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      id: `__${name}_by`,
 | 
					      id: `__${name}_by`,
 | 
				
			||||||
| 
						 | 
					@ -118,7 +122,11 @@ export function createRangeOperationWithGrouping(name: string): QueryBuilderOper
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function getRangeAggregationWithGroupingRenderer(aggregation: string, grouping: 'by' | 'without') {
 | 
					export function getRangeAggregationWithGroupingRenderer(aggregation: string, grouping: 'by' | 'without') {
 | 
				
			||||||
  return function aggregationRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
 | 
					  return function aggregationRenderer(
 | 
				
			||||||
 | 
					    model: QueryBuilderOperation,
 | 
				
			||||||
 | 
					    def: QueryBuilderOperationDefinition,
 | 
				
			||||||
 | 
					    innerExpr: string
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
    const restParamIndex = def.params.findIndex((param) => param.restParam);
 | 
					    const restParamIndex = def.params.findIndex((param) => param.restParam);
 | 
				
			||||||
    const params = model.params.slice(0, restParamIndex);
 | 
					    const params = model.params.slice(0, restParamIndex);
 | 
				
			||||||
    const restParams = model.params.slice(restParamIndex);
 | 
					    const restParams = model.params.slice(restParamIndex);
 | 
				
			||||||
| 
						 | 
					@ -133,7 +141,7 @@ export function getRangeAggregationWithGroupingRenderer(aggregation: string, gro
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function operationWithRangeVectorRenderer(
 | 
					function operationWithRangeVectorRenderer(
 | 
				
			||||||
  model: QueryBuilderOperation,
 | 
					  model: QueryBuilderOperation,
 | 
				
			||||||
  def: QueryBuilderOperationDef,
 | 
					  def: QueryBuilderOperationDefinition,
 | 
				
			||||||
  innerExpr: string
 | 
					  innerExpr: string
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
  const params = model.params ?? [];
 | 
					  const params = model.params ?? [];
 | 
				
			||||||
| 
						 | 
					@ -147,7 +155,11 @@ function operationWithRangeVectorRenderer(
 | 
				
			||||||
  return `${model.id}(${innerExpr} [${params[0] ?? '$__auto'}])`;
 | 
					  return `${model.id}(${innerExpr} [${params[0] ?? '$__auto'}])`;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function labelFilterRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
 | 
					export function labelFilterRenderer(
 | 
				
			||||||
 | 
					  model: QueryBuilderOperation,
 | 
				
			||||||
 | 
					  def: QueryBuilderOperationDefinition,
 | 
				
			||||||
 | 
					  innerExpr: string
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
  const integerOperators = ['<', '<=', '>', '>='];
 | 
					  const integerOperators = ['<', '<=', '>', '>='];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (integerOperators.includes(String(model.params[1]))) {
 | 
					  if (integerOperators.includes(String(model.params[1]))) {
 | 
				
			||||||
| 
						 | 
					@ -161,6 +173,9 @@ export function isConflictingFilter(
 | 
				
			||||||
  operation: QueryBuilderOperation,
 | 
					  operation: QueryBuilderOperation,
 | 
				
			||||||
  queryOperations: QueryBuilderOperation[]
 | 
					  queryOperations: QueryBuilderOperation[]
 | 
				
			||||||
): boolean {
 | 
					): boolean {
 | 
				
			||||||
 | 
					  if (!operation) {
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  const operationIsNegative = operation.params[1].toString().startsWith('!');
 | 
					  const operationIsNegative = operation.params[1].toString().startsWith('!');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const candidates = queryOperations.filter(
 | 
					  const candidates = queryOperations.filter(
 | 
				
			||||||
| 
						 | 
					@ -183,7 +198,11 @@ export function isConflictingFilter(
 | 
				
			||||||
  return conflict;
 | 
					  return conflict;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function pipelineRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
 | 
					export function pipelineRenderer(
 | 
				
			||||||
 | 
					  model: QueryBuilderOperation,
 | 
				
			||||||
 | 
					  def: QueryBuilderOperationDefinition,
 | 
				
			||||||
 | 
					  innerExpr: string
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
  switch (model.id) {
 | 
					  switch (model.id) {
 | 
				
			||||||
    case LokiOperationId.Logfmt:
 | 
					    case LokiOperationId.Logfmt:
 | 
				
			||||||
      const [strict = false, keepEmpty = false, ...labels] = model.params;
 | 
					      const [strict = false, keepEmpty = false, ...labels] = model.params;
 | 
				
			||||||
| 
						 | 
					@ -201,17 +220,17 @@ export function pipelineRenderer(model: QueryBuilderOperation, def: QueryBuilder
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function isRangeVectorFunction(def: QueryBuilderOperationDef) {
 | 
					function isRangeVectorFunction(def: QueryBuilderOperationDefinition) {
 | 
				
			||||||
  return def.category === LokiVisualQueryOperationCategory.RangeFunctions;
 | 
					  return def.category === LokiVisualQueryOperationCategory.RangeFunctions;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getIndexOfOrLast(
 | 
					function getIndexOfOrLast(
 | 
				
			||||||
  operations: QueryBuilderOperation[],
 | 
					  operations: QueryBuilderOperation[],
 | 
				
			||||||
  queryModeller: VisualQueryModeller,
 | 
					  queryModeller: VisualQueryModeller,
 | 
				
			||||||
  condition: (def: QueryBuilderOperationDef) => boolean
 | 
					  condition: (def: QueryBuilderOperationDefinition) => boolean
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
  const index = operations.findIndex((x) => {
 | 
					  const index = operations.findIndex((x) => {
 | 
				
			||||||
    const opDef = queryModeller.getOperationDef(x.id);
 | 
					    const opDef = queryModeller.getOperationDefinition(x.id);
 | 
				
			||||||
    if (!opDef) {
 | 
					    if (!opDef) {
 | 
				
			||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -222,7 +241,7 @@ function getIndexOfOrLast(
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function addLokiOperation(
 | 
					export function addLokiOperation(
 | 
				
			||||||
  def: QueryBuilderOperationDef,
 | 
					  def: QueryBuilderOperationDefinition,
 | 
				
			||||||
  query: LokiVisualQuery,
 | 
					  query: LokiVisualQuery,
 | 
				
			||||||
  modeller: VisualQueryModeller
 | 
					  modeller: VisualQueryModeller
 | 
				
			||||||
): LokiVisualQuery {
 | 
					): LokiVisualQuery {
 | 
				
			||||||
| 
						 | 
					@ -234,7 +253,7 @@ export function addLokiOperation(
 | 
				
			||||||
  const operations = [...query.operations];
 | 
					  const operations = [...query.operations];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const existingRangeVectorFunction = operations.find((x) => {
 | 
					  const existingRangeVectorFunction = operations.find((x) => {
 | 
				
			||||||
    const opDef = modeller.getOperationDef(x.id);
 | 
					    const opDef = modeller.getOperationDefinition(x.id);
 | 
				
			||||||
    if (!opDef) {
 | 
					    if (!opDef) {
 | 
				
			||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -280,7 +299,7 @@ export function addLokiOperation(
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function addNestedQueryHandler(def: QueryBuilderOperationDef, query: LokiVisualQuery): LokiVisualQuery {
 | 
					export function addNestedQueryHandler(def: QueryBuilderOperationDefinition, query: LokiVisualQuery): LokiVisualQuery {
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    ...query,
 | 
					    ...query,
 | 
				
			||||||
    binaryQueries: [
 | 
					    binaryQueries: [
 | 
				
			||||||
| 
						 | 
					@ -294,7 +313,11 @@ export function addNestedQueryHandler(def: QueryBuilderOperationDef, query: Loki
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function getLineFilterRenderer(operation: string, caseInsensitive?: boolean) {
 | 
					export function getLineFilterRenderer(operation: string, caseInsensitive?: boolean) {
 | 
				
			||||||
  return function lineFilterRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
 | 
					  return function lineFilterRenderer(
 | 
				
			||||||
 | 
					    model: QueryBuilderOperation,
 | 
				
			||||||
 | 
					    def: QueryBuilderOperationDefinition,
 | 
				
			||||||
 | 
					    innerExpr: string
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
    const hasBackticks = model.params.some((param) => typeof param === 'string' && param.includes('`'));
 | 
					    const hasBackticks = model.params.some((param) => typeof param === 'string' && param.includes('`'));
 | 
				
			||||||
    const delimiter = hasBackticks ? '"' : '`';
 | 
					    const delimiter = hasBackticks ? '"' : '`';
 | 
				
			||||||
    let params;
 | 
					    let params;
 | 
				
			||||||
| 
						 | 
					@ -324,7 +347,7 @@ export function getOperationParamId(operationId: string, paramIndex: number) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function getOnLabelAddedHandler(changeToOperationId: string) {
 | 
					export function getOnLabelAddedHandler(changeToOperationId: string) {
 | 
				
			||||||
  return function onParamChanged(index: number, op: QueryBuilderOperation, def: QueryBuilderOperationDef) {
 | 
					  return function onParamChanged(index: number, op: QueryBuilderOperation, def: QueryBuilderOperationDefinition) {
 | 
				
			||||||
    // Check if we actually have the label param. As it's optional the aggregation can have one less, which is the
 | 
					    // Check if we actually have the label param. As it's optional the aggregation can have one less, which is the
 | 
				
			||||||
    // case of just simple aggregation without label. When user adds the label it now has the same number of params
 | 
					    // case of just simple aggregation without label. When user adds the label it now has the same number of params
 | 
				
			||||||
    // as its definition, and now we can change it to its `_by` variant.
 | 
					    // as its definition, and now we can change it to its `_by` variant.
 | 
				
			||||||
| 
						 | 
					@ -361,7 +384,7 @@ export function getAggregationExplainer(aggregationName: string, mode: 'by' | 'w
 | 
				
			||||||
 * This function will transform operations without labels to their plan aggregation operation
 | 
					 * This function will transform operations without labels to their plan aggregation operation
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function getLastLabelRemovedHandler(changeToOperationId: string) {
 | 
					export function getLastLabelRemovedHandler(changeToOperationId: string) {
 | 
				
			||||||
  return function onParamChanged(index: number, op: QueryBuilderOperation, def: QueryBuilderOperationDef) {
 | 
					  return function onParamChanged(index: number, op: QueryBuilderOperation, def: QueryBuilderOperationDefinition) {
 | 
				
			||||||
    // If definition has more params then is defined there are no optional rest params anymore.
 | 
					    // If definition has more params then is defined there are no optional rest params anymore.
 | 
				
			||||||
    // We then transform this operation into a different one
 | 
					    // We then transform this operation into a different one
 | 
				
			||||||
    if (op.params.length < def.params.length) {
 | 
					    if (op.params.length < def.params.length) {
 | 
				
			||||||
| 
						 | 
					@ -379,7 +402,7 @@ export function getLokiOperationDisplayName(funcName: string) {
 | 
				
			||||||
  return capitalize(funcName.replace(/_/g, ' '));
 | 
					  return capitalize(funcName.replace(/_/g, ' '));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function defaultAddOperationHandler<T extends QueryWithOperations>(def: QueryBuilderOperationDef, query: T) {
 | 
					export function defaultAddOperationHandler<T extends VisualQuery>(def: QueryBuilderOperationDefinition, query: T) {
 | 
				
			||||||
  const newOperation: QueryBuilderOperation = {
 | 
					  const newOperation: QueryBuilderOperation = {
 | 
				
			||||||
    id: def.id,
 | 
					    id: def.id,
 | 
				
			||||||
    params: def.defaultParams,
 | 
					    params: def.defaultParams,
 | 
				
			||||||
| 
						 | 
					@ -393,9 +416,9 @@ export function defaultAddOperationHandler<T extends QueryWithOperations>(def: Q
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function createAggregationOperation(
 | 
					export function createAggregationOperation(
 | 
				
			||||||
  name: string,
 | 
					  name: string,
 | 
				
			||||||
  overrides: Partial<QueryBuilderOperationDef> = {}
 | 
					  overrides: Partial<QueryBuilderOperationDefinition> = {}
 | 
				
			||||||
): QueryBuilderOperationDef[] {
 | 
					): QueryBuilderOperationDefinition[] {
 | 
				
			||||||
  const operations: QueryBuilderOperationDef[] = [
 | 
					  const operations: QueryBuilderOperationDefinition[] = [
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      id: name,
 | 
					      id: name,
 | 
				
			||||||
      name: getLokiOperationDisplayName(name),
 | 
					      name: getLokiOperationDisplayName(name),
 | 
				
			||||||
| 
						 | 
					@ -466,12 +489,20 @@ export function createAggregationOperation(
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getAggregationWithoutRenderer(aggregation: string) {
 | 
					function getAggregationWithoutRenderer(aggregation: string) {
 | 
				
			||||||
  return function aggregationRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
 | 
					  return function aggregationRenderer(
 | 
				
			||||||
 | 
					    model: QueryBuilderOperation,
 | 
				
			||||||
 | 
					    def: QueryBuilderOperationDefinition,
 | 
				
			||||||
 | 
					    innerExpr: string
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
    return `${aggregation} without(${model.params.join(', ')}) (${innerExpr})`;
 | 
					    return `${aggregation} without(${model.params.join(', ')}) (${innerExpr})`;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function functionRendererLeft(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
 | 
					export function functionRendererLeft(
 | 
				
			||||||
 | 
					  model: QueryBuilderOperation,
 | 
				
			||||||
 | 
					  def: QueryBuilderOperationDefinition,
 | 
				
			||||||
 | 
					  innerExpr: string
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
  const params = renderParams(model, def, innerExpr);
 | 
					  const params = renderParams(model, def, innerExpr);
 | 
				
			||||||
  const str = model.id + '(';
 | 
					  const str = model.id + '(';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -482,7 +513,7 @@ export function functionRendererLeft(model: QueryBuilderOperation, def: QueryBui
 | 
				
			||||||
  return str + params.join(', ') + ')';
 | 
					  return str + params.join(', ') + ')';
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function renderParams(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
 | 
					function renderParams(model: QueryBuilderOperation, def: QueryBuilderOperationDefinition, innerExpr: string) {
 | 
				
			||||||
  return (model.params ?? []).map((value, index) => {
 | 
					  return (model.params ?? []).map((value, index) => {
 | 
				
			||||||
    const paramDef = def.params[index];
 | 
					    const paramDef = def.params[index];
 | 
				
			||||||
    if (paramDef.type === 'string') {
 | 
					    if (paramDef.type === 'string') {
 | 
				
			||||||
| 
						 | 
					@ -494,7 +525,11 @@ function renderParams(model: QueryBuilderOperation, def: QueryBuilderOperationDe
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getAggregationByRenderer(aggregation: string) {
 | 
					function getAggregationByRenderer(aggregation: string) {
 | 
				
			||||||
  return function aggregationRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
 | 
					  return function aggregationRenderer(
 | 
				
			||||||
 | 
					    model: QueryBuilderOperation,
 | 
				
			||||||
 | 
					    def: QueryBuilderOperationDefinition,
 | 
				
			||||||
 | 
					    innerExpr: string
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
    return `${aggregation} by(${model.params.join(', ')}) (${innerExpr})`;
 | 
					    return `${aggregation} by(${model.params.join(', ')}) (${innerExpr})`;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -502,8 +537,8 @@ function getAggregationByRenderer(aggregation: string) {
 | 
				
			||||||
export function createAggregationOperationWithParam(
 | 
					export function createAggregationOperationWithParam(
 | 
				
			||||||
  name: string,
 | 
					  name: string,
 | 
				
			||||||
  paramsDef: { params: QueryBuilderOperationParamDef[]; defaultParams: QueryBuilderOperationParamValue[] },
 | 
					  paramsDef: { params: QueryBuilderOperationParamDef[]; defaultParams: QueryBuilderOperationParamValue[] },
 | 
				
			||||||
  overrides: Partial<QueryBuilderOperationDef> = {}
 | 
					  overrides: Partial<QueryBuilderOperationDefinition> = {}
 | 
				
			||||||
): QueryBuilderOperationDef[] {
 | 
					): QueryBuilderOperationDefinition[] {
 | 
				
			||||||
  const operations = createAggregationOperation(name, overrides);
 | 
					  const operations = createAggregationOperation(name, overrides);
 | 
				
			||||||
  operations[0].params.unshift(...paramsDef.params);
 | 
					  operations[0].params.unshift(...paramsDef.params);
 | 
				
			||||||
  operations[1].params.unshift(...paramsDef.params);
 | 
					  operations[1].params.unshift(...paramsDef.params);
 | 
				
			||||||
| 
						 | 
					@ -517,7 +552,11 @@ export function createAggregationOperationWithParam(
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getAggregationByRendererWithParameter(aggregation: string) {
 | 
					function getAggregationByRendererWithParameter(aggregation: string) {
 | 
				
			||||||
  return function aggregationRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
 | 
					  return function aggregationRenderer(
 | 
				
			||||||
 | 
					    model: QueryBuilderOperation,
 | 
				
			||||||
 | 
					    def: QueryBuilderOperationDefinition,
 | 
				
			||||||
 | 
					    innerExpr: string
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
    const restParamIndex = def.params.findIndex((param) => param.restParam);
 | 
					    const restParamIndex = def.params.findIndex((param) => param.restParam);
 | 
				
			||||||
    const params = model.params.slice(0, restParamIndex);
 | 
					    const params = model.params.slice(0, restParamIndex);
 | 
				
			||||||
    const restParams = model.params.slice(restParamIndex);
 | 
					    const restParams = model.params.slice(restParamIndex);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
import { explainOperator, getOperationDefinitions } from './operations';
 | 
					import { explainOperator, operationDefinitions } from './operations';
 | 
				
			||||||
import { LokiOperationId } from './types';
 | 
					import { LokiOperationId } from './types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const undocumentedOperationsIds: string[] = [
 | 
					const undocumentedOperationsIds: string[] = [
 | 
				
			||||||
| 
						 | 
					@ -21,8 +21,7 @@ describe('explainOperator', () => {
 | 
				
			||||||
  let operations = [];
 | 
					  let operations = [];
 | 
				
			||||||
  let undocumentedOperations = [];
 | 
					  let undocumentedOperations = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const definitions = getOperationDefinitions();
 | 
					  for (const definition of operationDefinitions) {
 | 
				
			||||||
  for (const definition of definitions) {
 | 
					 | 
				
			||||||
    if (!undocumentedOperationsIds.includes(definition.id)) {
 | 
					    if (!undocumentedOperationsIds.includes(definition.id)) {
 | 
				
			||||||
      operations.push(definition.id);
 | 
					      operations.push(definition.id);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
| 
						 | 
					@ -31,7 +30,7 @@ describe('explainOperator', () => {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  test('Resolves operation definitions', () => {
 | 
					  test('Resolves operation definitions', () => {
 | 
				
			||||||
    expect(definitions.length).toBeGreaterThan(0);
 | 
					    expect(operationDefinitions.length).toBeGreaterThan(0);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  test.each(operations)('Returns docs for the %s operation', (operation) => {
 | 
					  test.each(operations)('Returns docs for the %s operation', (operation) => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
import { QueryBuilderOperationDef, QueryBuilderOperationParamValue } from '../../prometheus/querybuilder/shared/types';
 | 
					import { QueryBuilderOperationDefinition, QueryBuilderOperationParamValue } from '@grafana/experimental';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { binaryScalarOperations } from './binaryScalarOperations';
 | 
					import { binaryScalarOperations } from './binaryScalarOperations';
 | 
				
			||||||
import { UnwrapParamEditor } from './components/UnwrapParamEditor';
 | 
					import { UnwrapParamEditor } from './components/UnwrapParamEditor';
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,7 @@ import {
 | 
				
			||||||
} from './operationUtils';
 | 
					} from './operationUtils';
 | 
				
			||||||
import { LokiOperationId, LokiOperationOrder, lokiOperators, LokiVisualQueryOperationCategory } from './types';
 | 
					import { LokiOperationId, LokiOperationOrder, lokiOperators, LokiVisualQueryOperationCategory } from './types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function getOperationDefinitions(): QueryBuilderOperationDef[] {
 | 
					function getOperationDefinitions(): QueryBuilderOperationDefinition[] {
 | 
				
			||||||
  const aggregations = [
 | 
					  const aggregations = [
 | 
				
			||||||
    LokiOperationId.Sum,
 | 
					    LokiOperationId.Sum,
 | 
				
			||||||
    LokiOperationId.Min,
 | 
					    LokiOperationId.Min,
 | 
				
			||||||
| 
						 | 
					@ -66,7 +66,7 @@ export function getOperationDefinitions(): QueryBuilderOperationDef[] {
 | 
				
			||||||
    ...createRangeOperationWithGrouping(LokiOperationId.QuantileOverTime),
 | 
					    ...createRangeOperationWithGrouping(LokiOperationId.QuantileOverTime),
 | 
				
			||||||
  ];
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const list: QueryBuilderOperationDef[] = [
 | 
					  const list: QueryBuilderOperationDefinition[] = [
 | 
				
			||||||
    ...aggregations,
 | 
					    ...aggregations,
 | 
				
			||||||
    ...aggregationsWithParam,
 | 
					    ...aggregationsWithParam,
 | 
				
			||||||
    ...rangeOperations,
 | 
					    ...rangeOperations,
 | 
				
			||||||
| 
						 | 
					@ -581,14 +581,14 @@ Example: \`\`error_level=\`level\` \`\`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Keeping a local copy as an optimization measure.
 | 
					// Keeping a local copy as an optimization measure.
 | 
				
			||||||
const definitions = getOperationDefinitions();
 | 
					export const operationDefinitions = getOperationDefinitions();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Given an operator, return the corresponding explain.
 | 
					 * Given an operator, return the corresponding explain.
 | 
				
			||||||
 * For usage within the Query Editor.
 | 
					 * For usage within the Query Editor.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function explainOperator(id: LokiOperationId | string): string {
 | 
					export function explainOperator(id: LokiOperationId | string): string {
 | 
				
			||||||
  const definition = definitions.find((operation) => operation.id === id);
 | 
					  const definition = operationDefinitions.find((operation) => operation.id === id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const explain = definition?.explainHandler?.({ id: '', params: ['<value>'] }) || '';
 | 
					  const explain = definition?.explainHandler?.({ id: '', params: ['<value>'] }) || '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -596,11 +596,14 @@ export function explainOperator(id: LokiOperationId | string): string {
 | 
				
			||||||
  return explain.replace(/\[(.*)\]\(.*\)/g, '$1');
 | 
					  return explain.replace(/\[(.*)\]\(.*\)/g, '$1');
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function getDefinitionById(id: string): QueryBuilderOperationDef | undefined {
 | 
					export function getDefinitionById(id: string): QueryBuilderOperationDefinition | undefined {
 | 
				
			||||||
  return definitions.find((x) => x.id === id);
 | 
					  return operationDefinitions.find((x) => x.id === id);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function checkParamsAreValid(def: QueryBuilderOperationDef, params: QueryBuilderOperationParamValue[]): boolean {
 | 
					export function checkParamsAreValid(
 | 
				
			||||||
 | 
					  def: QueryBuilderOperationDefinition,
 | 
				
			||||||
 | 
					  params: QueryBuilderOperationParamValue[]
 | 
				
			||||||
 | 
					): boolean {
 | 
				
			||||||
  // For now we only check if the operation has all the required params.
 | 
					  // For now we only check if the operation has all the required params.
 | 
				
			||||||
  if (params.length < def.params.filter((param) => !param.optional).length) {
 | 
					  if (params.length < def.params.filter((param) => !param.optional).length) {
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
import { SyntaxNode } from '@lezer/common';
 | 
					import { SyntaxNode } from '@lezer/common';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { QueryBuilderLabelFilter, QueryBuilderOperation, QueryBuilderOperationParamValue } from '@grafana/experimental';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  And,
 | 
					  And,
 | 
				
			||||||
  BinOpExpr,
 | 
					  BinOpExpr,
 | 
				
			||||||
| 
						 | 
					@ -54,12 +55,6 @@ import {
 | 
				
			||||||
  OrFilter,
 | 
					  OrFilter,
 | 
				
			||||||
} from '@grafana/lezer-logql';
 | 
					} from '@grafana/lezer-logql';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  QueryBuilderLabelFilter,
 | 
					 | 
				
			||||||
  QueryBuilderOperation,
 | 
					 | 
				
			||||||
  QueryBuilderOperationParamValue,
 | 
					 | 
				
			||||||
} from '../../prometheus/querybuilder/shared/types';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { binaryScalarDefs } from './binaryScalarOperations';
 | 
					import { binaryScalarDefs } from './binaryScalarOperations';
 | 
				
			||||||
import { checkParamsAreValid, getDefinitionById } from './operations';
 | 
					import { checkParamsAreValid, getDefinitionById } from './operations';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
import { SyntaxNode, TreeCursor } from '@lezer/common';
 | 
					import { SyntaxNode, TreeCursor } from '@lezer/common';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { QueryBuilderOperation, QueryBuilderOperationParamValue } from '../../prometheus/querybuilder/shared/types';
 | 
					import { QueryBuilderOperation, QueryBuilderOperationParamValue } from '@grafana/experimental';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Although 0 isn't explicitly provided in the @grafana/lezer-logql library as the error node ID, it does appear to be the ID of error nodes within lezer.
 | 
					// Although 0 isn't explicitly provided in the @grafana/lezer-logql library as the error node ID, it does appear to be the ID of error nodes within lezer.
 | 
				
			||||||
export const ErrorId = 0;
 | 
					export const ErrorId = 0;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
import { QueryEditorMode } from '../../prometheus/querybuilder/shared/types';
 | 
					import { QueryEditorMode } from '@grafana/experimental';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { changeEditorMode, getQueryWithDefaults } from './state';
 | 
					import { changeEditorMode, getQueryWithDefaults } from './state';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
import { QueryEditorMode } from '../../prometheus/querybuilder/shared/types';
 | 
					import { QueryEditorMode } from '@grafana/experimental';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { LokiQuery, LokiQueryType } from '../types';
 | 
					import { LokiQuery, LokiQueryType } from '../types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const queryEditorModeDefaultLocalStorageKey = 'LokiQueryEditorModeDefault';
 | 
					const queryEditorModeDefaultLocalStorageKey = 'LokiQueryEditorModeDefault';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,9 @@
 | 
				
			||||||
import { VisualQueryBinary } from '../../prometheus/querybuilder/shared/LokiAndPromQueryModellerBase';
 | 
					import {
 | 
				
			||||||
import { QueryBuilderLabelFilter, QueryBuilderOperation } from '../../prometheus/querybuilder/shared/types';
 | 
					  VisualQueryBinary,
 | 
				
			||||||
 | 
					  QueryBuilderLabelFilter,
 | 
				
			||||||
 | 
					  QueryBuilderOperation,
 | 
				
			||||||
 | 
					  BINARY_OPERATIONS_KEY,
 | 
				
			||||||
 | 
					} from '@grafana/experimental';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Visual query model
 | 
					 * Visual query model
 | 
				
			||||||
| 
						 | 
					@ -29,7 +33,7 @@ export enum LokiVisualQueryOperationCategory {
 | 
				
			||||||
  Formats = 'Formats',
 | 
					  Formats = 'Formats',
 | 
				
			||||||
  LineFilters = 'Line filters',
 | 
					  LineFilters = 'Line filters',
 | 
				
			||||||
  LabelFilters = 'Label filters',
 | 
					  LabelFilters = 'Label filters',
 | 
				
			||||||
  BinaryOps = 'Binary operations',
 | 
					  BinaryOps = BINARY_OPERATIONS_KEY,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum LokiOperationId {
 | 
					export enum LokiOperationId {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,7 @@
 | 
				
			||||||
import { DashboardLoadedEvent, DataQueryRequest, dateTime } from '@grafana/data';
 | 
					import { DashboardLoadedEvent, DataQueryRequest, dateTime } from '@grafana/data';
 | 
				
			||||||
 | 
					import { QueryEditorMode } from '@grafana/experimental';
 | 
				
			||||||
import { reportInteraction } from '@grafana/runtime';
 | 
					import { reportInteraction } from '@grafana/runtime';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { QueryEditorMode } from '../prometheus/querybuilder/shared/types';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import pluginJson from './plugin.json';
 | 
					import pluginJson from './plugin.json';
 | 
				
			||||||
import { partitionTimeRange } from './querySplitting';
 | 
					import { partitionTimeRange } from './querySplitting';
 | 
				
			||||||
import { onDashboardLoadedHandler, trackGroupedQueries, trackQuery } from './tracking';
 | 
					import { onDashboardLoadedHandler, trackGroupedQueries, trackQuery } from './tracking';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,7 @@
 | 
				
			||||||
import { CoreApp, DashboardLoadedEvent, DataQueryRequest, DataQueryResponse } from '@grafana/data';
 | 
					import { CoreApp, DashboardLoadedEvent, DataQueryRequest, DataQueryResponse } from '@grafana/data';
 | 
				
			||||||
 | 
					import { QueryEditorMode } from '@grafana/experimental';
 | 
				
			||||||
import { reportInteraction, config } from '@grafana/runtime';
 | 
					import { reportInteraction, config } from '@grafana/runtime';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { QueryEditorMode } from '../prometheus/querybuilder/shared/types';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  REF_ID_STARTER_ANNOTATION,
 | 
					  REF_ID_STARTER_ANNOTATION,
 | 
				
			||||||
  REF_ID_DATA_SAMPLES,
 | 
					  REF_ID_DATA_SAMPLES,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue