mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Loki: enable Monaco Query Editor by default (#58080)
* feat(loki-monaco-editor): update tests * chore(loki): use unified datasource mock function in tests * chore: enable monaco feature flag in tests * feat(loki-monaco-editor): add test case for disabled feature * feat(loki-monaco-editor): enable by default * Revert "feat(loki-monaco-editor): enable by default" This reverts commit 08904f94a707a4fa32aa1e7f3f0de377575a7636. * feat(loki-monaco-editor): enable from registry * feat(loki-monaco-editor): make feature flag frontend only
This commit is contained in:
parent
29fcc46333
commit
25f79ef2b9
56
e2e/various-suite/loki-editor.spec.ts
Normal file
56
e2e/various-suite/loki-editor.spec.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
const dataSourceName = 'LokiEditor';
|
||||
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');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
e2e.scenario({
|
||||
describeName: 'Loki Query Editor',
|
||||
itName: 'Autocomplete features should work as expected.',
|
||||
addScenarioDataSource: false,
|
||||
addScenarioDashBoard: false,
|
||||
skipScenario: false,
|
||||
scenario: () => {
|
||||
addDataSource();
|
||||
|
||||
e2e().intercept(/labels?/, (req) => {
|
||||
req.reply({ status: 'success', data: ['instance', 'job', 'source'] });
|
||||
});
|
||||
|
||||
e2e().intercept(/series?/, (req) => {
|
||||
req.reply({ status: 'success', data: [{ instance: 'instance1' }] });
|
||||
});
|
||||
|
||||
// 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();
|
||||
|
||||
cy.contains('label', 'Code').click();
|
||||
|
||||
// we need to wait for the query-field being lazy-loaded, in two steps:
|
||||
// it is a two-step process:
|
||||
// 1. first we wait for the text 'Loading...' to appear
|
||||
// 1. then we wait for the text 'Loading...' to disappear
|
||||
const monacoLoadingText = 'Loading...';
|
||||
const queryText = `rate(http_requests_total{job="grafana"}[5m])`;
|
||||
e2e.components.QueryField.container().should('be.visible').should('have.text', monacoLoadingText);
|
||||
e2e.components.QueryField.container().should('be.visible').should('not.have.text', monacoLoadingText);
|
||||
e2e.components.QueryField.container().type(queryText, { parseSpecialCharSequences: false }).type('{backspace}');
|
||||
|
||||
cy.contains(queryText.slice(0, -1)).should('be.visible');
|
||||
|
||||
e2e.components.QueryField.container().type(e2e.typings.undo());
|
||||
|
||||
cy.contains(queryText).should('be.visible');
|
||||
},
|
||||
});
|
@ -1,113 +0,0 @@
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
const dataSourceName = 'LokiSlate';
|
||||
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 slate editor', () => {
|
||||
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('Braces plugin should insert closing brace', () => {
|
||||
e2e().intercept(/labels?/, (req) => {
|
||||
req.reply({ status: 'success', data: ['instance', 'job', 'source'] });
|
||||
});
|
||||
|
||||
e2e().intercept(/series?/, (req) => {
|
||||
req.reply({ status: 'success', data: [{ instance: 'instance1' }] });
|
||||
});
|
||||
|
||||
// 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();
|
||||
|
||||
// adds closing braces around empty value
|
||||
e2e().contains('Code').click();
|
||||
const queryField = e2e().get('.slate-query-field');
|
||||
queryField.type('time(');
|
||||
queryField.should(($el) => {
|
||||
expect($el.text().replace(/\uFEFF/g, '')).to.eq('time()');
|
||||
});
|
||||
|
||||
// removes closing brace when opening brace is removed
|
||||
queryField.type('{backspace}');
|
||||
queryField.should(($el) => {
|
||||
expect($el.text().replace(/\uFEFF/g, '')).to.eq('time');
|
||||
});
|
||||
|
||||
// keeps closing brace when opening brace is removed and inner values exist
|
||||
queryField.clear();
|
||||
queryField.type('time(test{leftArrow}{leftArrow}{leftArrow}{leftArrow}{backspace}');
|
||||
queryField.should(($el) => {
|
||||
expect($el.text().replace(/\uFEFF/g, '')).to.eq('timetest)');
|
||||
});
|
||||
|
||||
// overrides an automatically inserted brace
|
||||
queryField.clear();
|
||||
queryField.type('time()');
|
||||
queryField.should(($el) => {
|
||||
expect($el.text().replace(/\uFEFF/g, '')).to.eq('time()');
|
||||
});
|
||||
|
||||
// does not override manually inserted braces
|
||||
queryField.clear();
|
||||
queryField.type('))');
|
||||
queryField.should(($el) => {
|
||||
expect($el.text().replace(/\uFEFF/g, '')).to.eq('))');
|
||||
});
|
||||
|
||||
/** Clear Plugin */
|
||||
|
||||
//does not change the empty value
|
||||
queryField.clear();
|
||||
queryField.type('{ctrl+k}');
|
||||
queryField.should(($el) => {
|
||||
expect($el.text().replace(/\uFEFF/g, '')).to.match(/Enter a Loki query/);
|
||||
});
|
||||
|
||||
// clears to the end of the line
|
||||
queryField.clear();
|
||||
queryField.type('foo{leftArrow}{leftArrow}{leftArrow}{ctrl+k}');
|
||||
queryField.should(($el) => {
|
||||
expect($el.text().replace(/\uFEFF/g, '')).to.match(/Enter a Loki query/);
|
||||
});
|
||||
|
||||
// clears from the middle to the end of the line
|
||||
queryField.clear();
|
||||
queryField.type('foo bar{leftArrow}{leftArrow}{leftArrow}{leftArrow}{ctrl+k}');
|
||||
queryField.should(($el) => {
|
||||
expect($el.text().replace(/\uFEFF/g, '')).to.eq('foo');
|
||||
});
|
||||
|
||||
/** Runner plugin */
|
||||
|
||||
//should execute query when enter with shift is pressed
|
||||
queryField.clear();
|
||||
queryField.type('{shift+enter}');
|
||||
e2e().get('[data-testid="explore-no-data"]').should('be.visible');
|
||||
|
||||
/** Suggestions plugin */
|
||||
e2e().get('.slate-query-field').type(`{selectall}av`);
|
||||
e2e().get('.slate-typeahead').should('be.visible').contains('avg_over_time');
|
||||
});
|
||||
});
|
@ -113,9 +113,11 @@ var (
|
||||
State: FeatureStateAlpha,
|
||||
},
|
||||
{
|
||||
Name: "lokiMonacoEditor",
|
||||
Description: "Access to Monaco query editor for Loki",
|
||||
State: FeatureStateAlpha,
|
||||
Name: "lokiMonacoEditor",
|
||||
Description: "Access to Monaco query editor for Loki",
|
||||
State: FeatureStateAlpha,
|
||||
Expression: "true",
|
||||
FrontendOnly: true,
|
||||
},
|
||||
{
|
||||
Name: "swaggerUi",
|
||||
|
@ -3,10 +3,10 @@ import userEvent from '@testing-library/user-event';
|
||||
import { cloneDeep, defaultsDeep } from 'lodash';
|
||||
import React from 'react';
|
||||
|
||||
import { DataSourcePluginMeta } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { QueryEditorMode } from 'app/plugins/datasource/prometheus/querybuilder/shared/types';
|
||||
|
||||
import { LokiDatasource } from '../datasource';
|
||||
import { createLokiDatasource } from '../mocks';
|
||||
import { EXPLAIN_LABEL_FILTER_CONTENT } from '../querybuilder/components/LokiQueryBuilderExplained';
|
||||
import { LokiQuery, LokiQueryType } from '../types';
|
||||
|
||||
@ -36,24 +36,10 @@ const defaultQuery = {
|
||||
expr: '{label1="foo", label2="bar"}',
|
||||
};
|
||||
|
||||
const datasource = new LokiDatasource(
|
||||
{
|
||||
id: 1,
|
||||
uid: '',
|
||||
type: 'loki',
|
||||
name: 'loki-test',
|
||||
access: 'proxy',
|
||||
url: '',
|
||||
jsonData: {},
|
||||
meta: {} as DataSourcePluginMeta,
|
||||
readOnly: false,
|
||||
},
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
const datasource = createLokiDatasource();
|
||||
|
||||
datasource.languageProvider.fetchLabels = jest.fn().mockResolvedValue([]);
|
||||
datasource.getDataSamples = jest.fn().mockResolvedValue([]);
|
||||
jest.spyOn(datasource.languageProvider, 'fetchLabels').mockResolvedValue([]);
|
||||
jest.spyOn(datasource, 'getDataSamples').mockResolvedValue([]);
|
||||
|
||||
const defaultProps = {
|
||||
datasource,
|
||||
@ -62,11 +48,15 @@ const defaultProps = {
|
||||
onChange: () => {},
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
config.featureToggles.lokiMonacoEditor = true;
|
||||
});
|
||||
|
||||
describe('LokiQueryEditorSelector', () => {
|
||||
it('shows code editor if expr and nothing else', async () => {
|
||||
// We opt for showing code editor for queries created before this feature was added
|
||||
render(<LokiQueryEditor {...defaultProps} />);
|
||||
expectCodeEditor();
|
||||
await expectCodeEditor();
|
||||
});
|
||||
|
||||
it('shows builder if new query', async () => {
|
||||
@ -84,7 +74,7 @@ describe('LokiQueryEditorSelector', () => {
|
||||
|
||||
it('shows code editor when code mode is set', async () => {
|
||||
renderWithMode(QueryEditorMode.Code);
|
||||
expectCodeEditor();
|
||||
await expectCodeEditor();
|
||||
});
|
||||
|
||||
it('shows builder when builder mode is set', async () => {
|
||||
@ -94,6 +84,7 @@ describe('LokiQueryEditorSelector', () => {
|
||||
|
||||
it('changes to builder mode', async () => {
|
||||
const { onChange } = renderWithMode(QueryEditorMode.Code);
|
||||
await expectCodeEditor();
|
||||
await switchToMode(QueryEditorMode.Builder);
|
||||
expect(onChange).toBeCalledWith({
|
||||
refId: 'A',
|
||||
@ -129,7 +120,11 @@ describe('LokiQueryEditorSelector', () => {
|
||||
|
||||
it('changes to code mode', async () => {
|
||||
const { onChange } = renderWithMode(QueryEditorMode.Builder);
|
||||
|
||||
await expectBuilder();
|
||||
|
||||
await switchToMode(QueryEditorMode.Code);
|
||||
|
||||
expect(onChange).toBeCalledWith({
|
||||
refId: 'A',
|
||||
expr: defaultQuery.expr,
|
||||
@ -144,6 +139,7 @@ describe('LokiQueryEditorSelector', () => {
|
||||
expr: 'rate({instance="host.docker.internal:3000"}[$__interval])',
|
||||
editorMode: QueryEditorMode.Code,
|
||||
});
|
||||
await expectCodeEditor();
|
||||
await switchToMode(QueryEditorMode.Builder);
|
||||
rerender(
|
||||
<LokiQueryEditor
|
||||
@ -174,9 +170,9 @@ function renderWithProps(overrides?: Partial<LokiQuery>) {
|
||||
return { onChange, ...stuff };
|
||||
}
|
||||
|
||||
function expectCodeEditor() {
|
||||
async function expectCodeEditor() {
|
||||
// Log browser shows this until log labels are loaded.
|
||||
expect(screen.getByText('Loading labels...')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Loading...')).toBeInTheDocument();
|
||||
}
|
||||
|
||||
async function expectBuilder() {
|
||||
|
@ -1,49 +1,43 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React, { ComponentProps } from 'react';
|
||||
|
||||
import { dateTime } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
import LokiLanguageProvider from '../LanguageProvider';
|
||||
import { LokiDatasource } from '../datasource';
|
||||
import syntax from '../syntax';
|
||||
import { createLokiDatasource } from '../mocks';
|
||||
|
||||
import { LokiQueryField } from './LokiQueryField';
|
||||
|
||||
type Props = ComponentProps<typeof LokiQueryField>;
|
||||
|
||||
const defaultProps: Props = {
|
||||
datasource: {
|
||||
languageProvider: {
|
||||
start: () => Promise.resolve(['label1']),
|
||||
fetchLabels: Promise.resolve(['label1']),
|
||||
getSyntax: () => syntax,
|
||||
getLabelKeys: () => ['label1'],
|
||||
getLabelValues: () => Promise.resolve(['value1']),
|
||||
} as unknown as LokiLanguageProvider,
|
||||
} as LokiDatasource,
|
||||
range: {
|
||||
from: dateTime([2021, 1, 11, 12, 0, 0]),
|
||||
to: dateTime([2021, 1, 11, 18, 0, 0]),
|
||||
raw: {
|
||||
from: 'now-1h',
|
||||
to: 'now',
|
||||
},
|
||||
},
|
||||
query: { expr: '', refId: '' },
|
||||
onRunQuery: () => {},
|
||||
onChange: () => {},
|
||||
history: [],
|
||||
};
|
||||
|
||||
describe('LokiQueryField', () => {
|
||||
it('refreshes metrics when time range changes over 1 minute', async () => {
|
||||
const fetchLabelsMock = jest.fn();
|
||||
const props = defaultProps;
|
||||
props.datasource.languageProvider.fetchLabels = fetchLabelsMock;
|
||||
let props: Props;
|
||||
beforeEach(() => {
|
||||
props = {
|
||||
datasource: createLokiDatasource(),
|
||||
range: {
|
||||
from: dateTime([2021, 1, 11, 12, 0, 0]),
|
||||
to: dateTime([2021, 1, 11, 18, 0, 0]),
|
||||
raw: {
|
||||
from: 'now-1h',
|
||||
to: 'now',
|
||||
},
|
||||
},
|
||||
query: { expr: '', refId: '' },
|
||||
onRunQuery: () => {},
|
||||
onChange: () => {},
|
||||
history: [],
|
||||
};
|
||||
jest.spyOn(props.datasource.languageProvider, 'start').mockResolvedValue([]);
|
||||
jest.spyOn(props.datasource.languageProvider, 'fetchLabels').mockResolvedValue(['label1']);
|
||||
config.featureToggles.lokiMonacoEditor = true;
|
||||
});
|
||||
|
||||
it('refreshes metrics when time range changes over 1 minute', async () => {
|
||||
const { rerender } = render(<LokiQueryField {...props} />);
|
||||
|
||||
expect(fetchLabelsMock).not.toHaveBeenCalled();
|
||||
expect(await screen.findByText('Loading...')).toBeInTheDocument();
|
||||
|
||||
expect(props.datasource.languageProvider.fetchLabels).not.toHaveBeenCalled();
|
||||
|
||||
// 2 minutes difference over the initial time
|
||||
const newRange = {
|
||||
@ -56,17 +50,15 @@ describe('LokiQueryField', () => {
|
||||
};
|
||||
|
||||
rerender(<LokiQueryField {...props} range={newRange} />);
|
||||
expect(fetchLabelsMock).toHaveBeenCalledTimes(1);
|
||||
expect(props.datasource.languageProvider.fetchLabels).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('does not refreshes metrics when time range change by less than 1 minute', async () => {
|
||||
const fetchLabelsMock = jest.fn();
|
||||
const props = defaultProps;
|
||||
props.datasource.languageProvider.fetchLabels = fetchLabelsMock;
|
||||
|
||||
const { rerender } = render(<LokiQueryField {...props} />);
|
||||
|
||||
expect(fetchLabelsMock).not.toHaveBeenCalled();
|
||||
expect(await screen.findByText('Loading...')).toBeInTheDocument();
|
||||
|
||||
expect(props.datasource.languageProvider.fetchLabels).not.toHaveBeenCalled();
|
||||
|
||||
// 20 seconds difference over the initial time
|
||||
const newRange = {
|
||||
@ -79,6 +71,14 @@ describe('LokiQueryField', () => {
|
||||
};
|
||||
|
||||
rerender(<LokiQueryField {...props} range={newRange} />);
|
||||
expect(fetchLabelsMock).not.toHaveBeenCalled();
|
||||
expect(props.datasource.languageProvider.fetchLabels).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('can fall back to the legacy editor', async () => {
|
||||
config.featureToggles.lokiMonacoEditor = false;
|
||||
render(<LokiQueryField {...props} />);
|
||||
|
||||
expect(await screen.findByText('Enter a Loki query (run with Shift+Enter)')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { DataSourceInstanceSettings, DataSourcePluginMeta } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
import { LokiDatasource } from '../../datasource';
|
||||
import { createLokiDatasource } from '../../mocks';
|
||||
import { LokiQuery } from '../../types';
|
||||
|
||||
import { EXPLAIN_LABEL_FILTER_CONTENT } from './LokiQueryBuilderExplained';
|
||||
@ -15,15 +15,7 @@ const defaultQuery: LokiQuery = {
|
||||
};
|
||||
|
||||
const createDefaultProps = () => {
|
||||
const datasource = new LokiDatasource(
|
||||
{
|
||||
url: '',
|
||||
jsonData: {},
|
||||
meta: {} as DataSourcePluginMeta,
|
||||
} as DataSourceInstanceSettings,
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
const datasource = createLokiDatasource();
|
||||
|
||||
const props = {
|
||||
datasource,
|
||||
@ -35,19 +27,25 @@ const createDefaultProps = () => {
|
||||
return props;
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
config.featureToggles.lokiMonacoEditor = true;
|
||||
});
|
||||
|
||||
describe('LokiQueryCodeEditor', () => {
|
||||
it('shows explain section when showExplain is true', () => {
|
||||
it('shows explain section when showExplain is true', async () => {
|
||||
const props = createDefaultProps();
|
||||
props.showExplain = true;
|
||||
props.datasource.metadataRequest = jest.fn().mockResolvedValue([]);
|
||||
render(<LokiQueryCodeEditor {...props} query={defaultQuery} />);
|
||||
expect(await screen.findByText('Loading...')).toBeInTheDocument();
|
||||
expect(screen.getByText(EXPLAIN_LABEL_FILTER_CONTENT)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not show explain section when showExplain is false', () => {
|
||||
it('does not show explain section when showExplain is false', async () => {
|
||||
const props = createDefaultProps();
|
||||
props.datasource.metadataRequest = jest.fn().mockResolvedValue([]);
|
||||
render(<LokiQueryCodeEditor {...props} query={defaultQuery} />);
|
||||
expect(await screen.findByText('Loading...')).toBeInTheDocument();
|
||||
expect(screen.queryByText(EXPLAIN_LABEL_FILTER_CONTENT)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user