diff --git a/.betterer.results b/.betterer.results index deed1f04187..95e87d79a52 100644 --- a/.betterer.results +++ b/.betterer.results @@ -10406,20 +10406,20 @@ exports[`better eslint`] = { [163, 25, 27, "Do not use any type assertions.", "3051431932"], [163, 49, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilterItem.tsx:3990888924": [ - [65, 23, 155, "Do not use any type assertions.", "2419762696"], - [65, 23, 128, "Do not use any type assertions.", "1950878359"], - [69, 19, 3, "Unexpected any. Specify a different type.", "193409811"], - [80, 23, 63, "Do not use any type assertions.", "168255657"], - [80, 23, 36, "Do not use any type assertions.", "381764118"], - [80, 56, 3, "Unexpected any. Specify a different type.", "193409811"], - [108, 23, 92, "Do not use any type assertions.", "511072900"], - [108, 23, 65, "Do not use any type assertions.", "791801755"], - [108, 85, 3, "Unexpected any. Specify a different type.", "193409811"], - [111, 30, 3, "Unexpected any. Specify a different type.", "193409811"], - [115, 23, 87, "Do not use any type assertions.", "1044305842"], - [115, 23, 60, "Do not use any type assertions.", "4026468781"], - [115, 80, 3, "Unexpected any. Specify a different type.", "193409811"] + "public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilterItem.tsx:2056401604": [ + [67, 23, 155, "Do not use any type assertions.", "2419762696"], + [67, 23, 128, "Do not use any type assertions.", "1950878359"], + [71, 19, 3, "Unexpected any. Specify a different type.", "193409811"], + [83, 23, 63, "Do not use any type assertions.", "168255657"], + [83, 23, 36, "Do not use any type assertions.", "381764118"], + [83, 56, 3, "Unexpected any. Specify a different type.", "193409811"], + [112, 23, 92, "Do not use any type assertions.", "511072900"], + [112, 23, 65, "Do not use any type assertions.", "791801755"], + [112, 85, 3, "Unexpected any. Specify a different type.", "193409811"], + [115, 30, 3, "Unexpected any. Specify a different type.", "193409811"], + [119, 23, 87, "Do not use any type assertions.", "1044305842"], + [119, 23, 60, "Do not use any type assertions.", "4026468781"], + [119, 80, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilters.tsx:1456752614": [ [36, 15, 38, "Do not use any type assertions.", "1544016021"] diff --git a/e2e/various-suite/loki-query-builder.spec.ts b/e2e/various-suite/loki-query-builder.spec.ts new file mode 100644 index 00000000000..e617a42468f --- /dev/null +++ b/e2e/various-suite/loki-query-builder.spec.ts @@ -0,0 +1,81 @@ +import { e2e } from '@grafana/e2e'; + +const dataSourceName = 'LokiBuilder'; +const addDataSource = () => { + e2e.flows.addDataSource({ + type: 'Loki', + expectedAlertMessage: + 'Unable to fetch labels from Loki (Failed to call resource), please check the server logs for more details', + name: dataSourceName, + form: () => { + e2e.components.DataSource.DataSourceHttpSettings.urlInput().type('http://loki-url:3100'); + }, + }); +}; + +describe('Loki query builder', () => { + beforeEach(() => { + e2e.flows.login('admin', 'admin'); + + e2e() + .request({ url: `${e2e.env('BASE_URL')}/api/datasources/name/${dataSourceName}`, failOnStatusCode: false }) + .then((response) => { + if (response.isOkStatusCode) { + return; + } + addDataSource(); + }); + }); + + it('should be able to use all modes', () => { + e2e().intercept(/labels?/, (req) => { + req.reply({ status: 'success', data: ['instance', 'job', 'source'] }); + }); + + e2e().intercept(/series?/, (req) => { + req.reply({ status: 'success', data: [{ instance: 'instance1' }] }); + }); + + const finalQuery = 'rate({instance=~"instance1|instance2"} | logfmt | __error__=`` [$__interval]'; + + // Go to Explore and choose Loki data source + e2e.pages.Explore.visit(); + e2e.components.DataSourcePicker.container().should('be.visible').click(); + e2e().contains(dataSourceName).scrollIntoView().should('be.visible').click(); + + // Start in builder mode, click and choose query pattern + e2e.components.QueryBuilder.queryPatterns().click().type('Log query with parsing{enter}'); + e2e().contains('No pipeline errors').should('be.visible'); + e2e().contains('Logfmt').should('be.visible'); + e2e().contains('{} | logfmt | __error__=``').should('be.visible'); + + // Add operation + e2e().contains('Operations').should('be.visible').click(); + e2e().contains('Range functions').should('be.visible').click(); + e2e().contains('Rate').should('be.visible').click(); + e2e().contains('rate({} | logfmt | __error__=`` [$__interval]').should('be.visible'); + + // Check for expected error + e2e().contains('You need to specify at least 1 label filter (stream selector)').should('be.visible'); + + // Add labels to remove error + e2e.components.QueryBuilder.labelSelect().should('be.visible').click().type('instance{enter}'); + e2e.components.QueryBuilder.matchOperatorSelect().should('be.visible').click().type('=~{enter}'); + e2e.components.QueryBuilder.valueSelect() + .should('be.visible') + .click() + .type('instance1{enter}') + .type('instance2{enter}'); + e2e().contains('You need to specify at least 1 label filter (stream selector)').should('not.exist'); + e2e().contains(finalQuery).should('be.visible'); + + // Switch to code editor and check if query was parsed + for (const word of finalQuery.split(' ')) { + e2e().contains(word).should('be.visible'); + } + + // Switch to explain mode and check if query is visible + e2e().contains('label', 'Explain').click(); + e2e().contains(finalQuery).should('be.visible'); + }); +}); diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index 2bcbed6db14..1bdc2583dc1 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -268,6 +268,12 @@ export const Components = { spanBar: 'data-testid SpanBar--wrapper', }, QueryField: { container: 'Query field' }, + QueryBuilder: { + queryPatterns: 'Query patterns', + labelSelect: 'Select label', + valueSelect: 'Select value', + matchOperatorSelect: 'Select match operator', + }, ValuePicker: { button: (name: string) => `Value picker button ${name}`, select: (name: string) => `Value picker select ${name}`, diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx index 94082d109df..45a3d2b5fbe 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx @@ -1,6 +1,7 @@ import React, { SyntheticEvent, useCallback, useEffect, useState } from 'react'; import { CoreApp, LoadingState } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { EditorHeader, EditorRows, FlexItem, InlineSelect, Space } from '@grafana/experimental'; import { reportInteraction } from '@grafana/runtime'; import { Button, ConfirmModal } from '@grafana/ui'; @@ -83,6 +84,7 @@ export const LokiQueryEditorSelector = React.memo((props) { const result = buildVisualQueryFromString(query.expr || ''); diff --git a/public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilterItem.tsx b/public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilterItem.tsx index 44800ebb69b..bc4ae61a25a 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilterItem.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilterItem.tsx @@ -2,6 +2,7 @@ import { uniqBy } from 'lodash'; import React, { useState } from 'react'; import { SelectableValue, toOption } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { AccessoryButton, InputGroup } from '@grafana/experimental'; import { Select } from '@grafana/ui'; @@ -50,6 +51,7 @@ export function LabelFilterItem({ item, defaultOp, onChange, onDelete, onGetLabe