Show error when operations, but no stream selector (#47890)

This commit is contained in:
Ivana Huckova 2022-04-19 16:54:08 +02:00 committed by GitHub
parent 992c0604f9
commit 677327ea07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 34 additions and 9 deletions

View File

@ -3,7 +3,7 @@ import { render, screen, getAllByRole, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { LokiQueryBuilder } from './LokiQueryBuilder'; import { LokiQueryBuilder } from './LokiQueryBuilder';
import { LokiDatasource } from '../../datasource'; import { LokiDatasource } from '../../datasource';
import { LokiVisualQuery } from '../types'; import { LokiOperationId, LokiVisualQuery } from '../types';
import { PanelData } from '@grafana/data'; import { PanelData } from '@grafana/data';
const defaultQuery: LokiVisualQuery = { const defaultQuery: LokiVisualQuery = {
@ -17,10 +17,20 @@ describe('LokiQueryBuilder', () => {
datasource.languageProvider.fetchSeriesLabels = jest.fn().mockReturnValue({ job: ['a'], instance: ['b'] }); datasource.languageProvider.fetchSeriesLabels = jest.fn().mockReturnValue({ job: ['a'], instance: ['b'] });
userEvent.click(screen.getByLabelText('Add')); userEvent.click(screen.getByLabelText('Add'));
const labels = screen.getByText(/Labels/); const labels = screen.getByText(/Labels/);
const selects = getAllByRole(labels.parentElement!, 'combobox'); const selects = getAllByRole(labels.parentElement!.parentElement!.parentElement!, 'combobox');
userEvent.click(selects[3]); userEvent.click(selects[3]);
await waitFor(() => expect(screen.getByText('job')).toBeInTheDocument()); await waitFor(() => expect(screen.getByText('job')).toBeInTheDocument());
}); });
it('shows error for query with operations and no stream selector', async () => {
setup({ labels: [], operations: [{ id: LokiOperationId.Logfmt, params: [] }] });
expect(screen.getByText('You need to specify at least 1 label filter (stream selector)')).toBeInTheDocument();
});
it('shows no error for query with empty __line_contains operation and no stream selector', async () => {
setup({ labels: [], operations: [{ id: LokiOperationId.LineContains, params: [''] }] });
expect(screen.queryByText('You need to specify at least 1 label filter (stream selector)')).not.toBeInTheDocument();
});
}); });
function setup(query: LokiVisualQuery = defaultQuery, data?: PanelData) { function setup(query: LokiVisualQuery = defaultQuery, data?: PanelData) {

View File

@ -1,5 +1,5 @@
import React from 'react'; import React, { useMemo } from 'react';
import { LokiVisualQuery } from '../types'; import { LokiOperationId, LokiVisualQuery } from '../types';
import { LokiDatasource } from '../../datasource'; import { LokiDatasource } from '../../datasource';
import { LabelFilters } from 'app/plugins/datasource/prometheus/querybuilder/shared/LabelFilters'; import { LabelFilters } from 'app/plugins/datasource/prometheus/querybuilder/shared/LabelFilters';
import { OperationList } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationList'; import { OperationList } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationList';
@ -57,6 +57,18 @@ export const LokiQueryBuilder = React.memo<Props>(({ datasource, query, nested,
return result[forLabelInterpolated] ?? []; return result[forLabelInterpolated] ?? [];
}; };
const labelFilterError: string | undefined = useMemo(() => {
const { labels, operations: op } = query;
if (!labels.length && op.length) {
// We don't want to show error for initial state with empty line contains operation
if (op.length === 1 && op[0].id === LokiOperationId.LineContains && op[0].params[0] === '') {
return undefined;
}
return 'You need to specify at least 1 label filter (stream selector)';
}
return undefined;
}, [query]);
return ( return (
<> <>
<EditorRow> <EditorRow>
@ -69,6 +81,7 @@ export const LokiQueryBuilder = React.memo<Props>(({ datasource, query, nested,
} }
labelsFilters={query.labels} labelsFilters={query.labels}
onChange={onChangeLabels} onChange={onChangeLabels}
error={labelFilterError}
/> />
</EditorRow> </EditorRow>
<OperationsEditorRow> <OperationsEditorRow>

View File

@ -1,5 +1,6 @@
import { SelectableValue } from '@grafana/data'; import { SelectableValue } from '@grafana/data';
import { EditorField, EditorFieldGroup, EditorList } from '@grafana/experimental'; import { EditorFieldGroup, EditorList } from '@grafana/experimental';
import { Field } from '@grafana/ui';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { QueryBuilderLabelFilter } from '../shared/types'; import { QueryBuilderLabelFilter } from '../shared/types';
@ -10,9 +11,10 @@ export interface Props {
onChange: (labelFilters: QueryBuilderLabelFilter[]) => void; onChange: (labelFilters: QueryBuilderLabelFilter[]) => void;
onGetLabelNames: (forLabel: Partial<QueryBuilderLabelFilter>) => Promise<SelectableValue[]>; onGetLabelNames: (forLabel: Partial<QueryBuilderLabelFilter>) => Promise<SelectableValue[]>;
onGetLabelValues: (forLabel: Partial<QueryBuilderLabelFilter>) => Promise<SelectableValue[]>; onGetLabelValues: (forLabel: Partial<QueryBuilderLabelFilter>) => Promise<SelectableValue[]>;
error?: string;
} }
export function LabelFilters({ labelsFilters, onChange, onGetLabelNames, onGetLabelValues }: Props) { export function LabelFilters({ labelsFilters, onChange, onGetLabelNames, onGetLabelValues, error }: Props) {
const defaultOp = '='; const defaultOp = '=';
const [items, setItems] = useState<Array<Partial<QueryBuilderLabelFilter>>>([{ op: defaultOp }]); const [items, setItems] = useState<Array<Partial<QueryBuilderLabelFilter>>>([{ op: defaultOp }]);
@ -36,7 +38,7 @@ export function LabelFilters({ labelsFilters, onChange, onGetLabelNames, onGetLa
return ( return (
<EditorFieldGroup> <EditorFieldGroup>
<EditorField label="Labels"> <Field label="Labels" error={error} invalid={!!error}>
<EditorList <EditorList
items={items} items={items}
onChange={onLabelsChange} onChange={onLabelsChange}
@ -51,7 +53,7 @@ export function LabelFilters({ labelsFilters, onChange, onGetLabelNames, onGetLa
/> />
)} )}
/> />
</EditorField> </Field>
</EditorFieldGroup> </EditorFieldGroup>
); );
} }

View File

@ -2,7 +2,7 @@ import { screen, getAllByRole } from '@testing-library/react';
export function getLabelSelects(index = 0) { export function getLabelSelects(index = 0) {
const labels = screen.getByText(/Labels/); const labels = screen.getByText(/Labels/);
const selects = getAllByRole(labels.parentElement!, 'combobox'); const selects = getAllByRole(labels.parentElement!.parentElement!.parentElement!, 'combobox');
return { return {
name: selects[3 * index], name: selects[3 * index],
value: selects[3 * index + 2], value: selects[3 * index + 2],