Loki Query Editor: Make Monaco the default editor (#62247)

* feat(loki-editor): remove slate and make Monaco the default editor

* feat(loki-editor): remove unsupported usages of onBlur

* feat(loki-editor): remove monaco feature flag

* Chore: remove unused import
This commit is contained in:
Matias Chomicki 2023-02-06 11:18:01 +01:00 committed by GitHub
parent 4d268cbcdb
commit 57369915f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 22 additions and 191 deletions

View File

@ -6027,10 +6027,6 @@ exports[`better eslint`] = {
"public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx:5381": [
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"]
],
"public/app/plugins/datasource/loki/components/LokiQueryField.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"]
],
"public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryField.tsx:5381": [
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"]
],

View File

@ -23,7 +23,6 @@ Some stable features are enabled by default. You can disable a stable feature by
| ----------------------------------- | ------------------------------------------------------------------------------------ | ------------------ |
| `disableEnvelopeEncryption` | Disable envelope encryption (emergency only) | |
| `database_metrics` | Add Prometheus metrics for database tables | |
| `lokiMonacoEditor` | Access to Monaco query editor for Loki | Yes |
| `featureHighlights` | Highlight Grafana Enterprise features | |
| `cloudWatchDynamicLabels` | Use dynamic labels instead of alias patterns in CloudWatch datasource | Yes |
| `internationalization` | Enables internationalization | Yes |

View File

@ -32,7 +32,6 @@ export interface FeatureToggles {
publicDashboardsEmailSharing?: boolean;
lokiLive?: boolean;
lokiDataframeApi?: boolean;
lokiMonacoEditor?: boolean;
swaggerUi?: boolean;
featureHighlights?: boolean;
dashboardComments?: boolean;

View File

@ -94,13 +94,6 @@ var (
Description: "Use experimental loki api for WebSocket streaming (early prototype)",
State: FeatureStateAlpha,
},
{
Name: "lokiMonacoEditor",
Description: "Access to Monaco query editor for Loki",
State: FeatureStateStable,
Expression: "true",
FrontendOnly: true,
},
{
Name: "swaggerUi",
Description: "Serves swagger UI",

View File

@ -71,10 +71,6 @@ const (
// Use experimental loki api for WebSocket streaming (early prototype)
FlagLokiDataframeApi = "lokiDataframeApi"
// FlagLokiMonacoEditor
// Access to Monaco query editor for Loki
FlagLokiMonacoEditor = "lokiMonacoEditor"
// FlagSwaggerUi
// Serves swagger UI
FlagSwaggerUi = "swaggerUi"

View File

@ -56,7 +56,6 @@ export const LokiAnnotationsQueryEditor = memo(function LokiAnnotationQueryEdito
query={queryWithRefId}
onChange={onChangeQuery}
onRunQuery={() => {}}
onBlur={() => {}}
history={history}
ExtraFieldElement={
<LokiOptionFields

View File

@ -4,7 +4,6 @@ import { cloneDeep, defaultsDeep } from 'lodash';
import React from 'react';
import { CoreApp } from '@grafana/data';
import { config } from '@grafana/runtime';
import { QueryEditorMode } from 'app/plugins/datasource/prometheus/querybuilder/shared/types';
import { createLokiDatasource } from '../mocks';
@ -59,10 +58,6 @@ 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

View File

@ -12,7 +12,6 @@ export function LokiQueryEditorForAlerting(props: LokiQueryEditorProps) {
query={query}
onChange={onChange}
onRunQuery={onRunQuery}
onBlur={onRunQuery}
history={history}
data={data}
placeholder="Enter a Loki query"

View File

@ -2,7 +2,6 @@ import { render, screen } from '@testing-library/react';
import React, { ComponentProps } from 'react';
import { dateTime } from '@grafana/data';
import { config } from '@grafana/runtime';
import { createLokiDatasource } from '../mocks';
@ -29,7 +28,6 @@ describe('LokiQueryField', () => {
};
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 () => {
@ -73,12 +71,4 @@ describe('LokiQueryField', () => {
rerender(<LokiQueryField {...props} range={newRange} />);
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();
});
});

View File

@ -1,66 +1,13 @@
import { LanguageMap, languages as prismLanguages } from 'prismjs';
import React, { ReactNode } from 'react';
import { Plugin, Node } from 'slate';
import { Editor } from 'slate-react';
import { CoreApp, QueryEditorProps } from '@grafana/data';
import { config } from '@grafana/runtime';
import {
SlatePrism,
TypeaheadOutput,
SuggestionsState,
QueryField,
TypeaheadInput,
BracesPlugin,
DOMUtil,
} from '@grafana/ui';
import { LocalStorageValueProvider } from 'app/core/components/LocalStorageValueProvider';
import LokiLanguageProvider from '../LanguageProvider';
import { LokiDatasource } from '../datasource';
import { escapeLabelValueInSelector, shouldRefreshLabels } from '../languageUtils';
import { shouldRefreshLabels } from '../languageUtils';
import { LokiQuery, LokiOptions } from '../types';
import { MonacoQueryFieldWrapper } from './monaco-query-field/MonacoQueryFieldWrapper';
const LAST_USED_LABELS_KEY = 'grafana.datasources.loki.browser.labels';
function willApplySuggestion(suggestion: string, { typeaheadContext, typeaheadText }: SuggestionsState): string {
// Modify suggestion based on context
switch (typeaheadContext) {
case 'context-labels': {
const nextChar = DOMUtil.getNextCharacter();
if (!nextChar || nextChar === '}' || nextChar === ',') {
suggestion += '=';
}
break;
}
case 'context-label-values': {
// Always add quotes and remove existing ones instead
let suggestionModified = '';
if (!typeaheadText.match(/^(!?=~?"|")/)) {
suggestionModified = '"';
}
suggestionModified += escapeLabelValueInSelector(suggestion, typeaheadText);
if (DOMUtil.getNextCharacter() !== '"') {
suggestionModified += '"';
}
suggestion = suggestionModified;
break;
}
default:
}
return suggestion;
}
export interface LokiQueryFieldProps extends QueryEditorProps<LokiDatasource, LokiQuery, LokiOptions> {
ExtraFieldElement?: ReactNode;
placeholder?: string;
@ -69,28 +16,15 @@ export interface LokiQueryFieldProps extends QueryEditorProps<LokiDatasource, Lo
interface LokiQueryFieldState {
labelsLoaded: boolean;
labelBrowserVisible: boolean;
}
export class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, LokiQueryFieldState> {
plugins: Array<Plugin<Editor>>;
_isMounted = false;
constructor(props: LokiQueryFieldProps) {
super(props);
this.state = { labelsLoaded: false, labelBrowserVisible: false };
this.plugins = [
BracesPlugin(),
SlatePrism(
{
onlyIn: (node: Node) => node.object === 'block' && node.type === 'code_block',
getSyntax: (node: Node) => 'logql',
},
{ ...(prismLanguages as LanguageMap), logql: this.props.datasource.languageProvider.getSyntax() }
),
];
this.state = { labelsLoaded: false };
}
async componentDidMount() {
@ -117,11 +51,6 @@ export class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, Lok
}
}
onChangeLabelBrowser = (selector: string) => {
this.onChangeQuery(selector, true);
this.setState({ labelBrowserVisible: false });
};
onChangeQuery = (value: string, override?: boolean) => {
// Send text change to parent
const { query, onChange, onRunQuery } = this.props;
@ -135,76 +64,28 @@ export class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, Lok
}
};
onTypeahead = async (typeahead: TypeaheadInput): Promise<TypeaheadOutput> => {
const { datasource } = this.props;
if (!datasource.languageProvider) {
return { suggestions: [] };
}
const lokiLanguageProvider = datasource.languageProvider as LokiLanguageProvider;
const { history } = this.props;
const { prefix, text, value, wrapperClasses, labelKey } = typeahead;
const result = await lokiLanguageProvider.provideCompletionItems(
{ text, value, prefix, wrapperClasses, labelKey },
{ history }
);
return result;
};
render() {
const {
ExtraFieldElement,
query,
app,
datasource,
placeholder = 'Enter a Loki query (run with Shift+Enter)',
history,
onRunQuery,
onBlur,
} = this.props;
const { ExtraFieldElement, query, app, datasource, history, onRunQuery } = this.props;
return (
<LocalStorageValueProvider<string[]> storageKey={LAST_USED_LABELS_KEY} defaultValue={[]}>
{(lastUsedLabels, onLastUsedLabelsSave, onLastUsedLabelsDelete) => {
return (
<>
<div
className="gf-form-inline gf-form-inline--xs-view-flex-column flex-grow-1"
data-testid={this.props['data-testid']}
>
<div className="gf-form gf-form--grow flex-shrink-1 min-width-15">
{config.featureToggles.lokiMonacoEditor ? (
<MonacoQueryFieldWrapper
runQueryOnBlur={app !== CoreApp.Explore}
datasource={datasource}
history={history ?? []}
onChange={this.onChangeQuery}
onRunQuery={onRunQuery}
initialValue={query.expr ?? ''}
/>
) : (
<QueryField
additionalPlugins={this.plugins}
cleanText={datasource.languageProvider.cleanText}
query={query.expr}
onTypeahead={this.onTypeahead}
onWillApplySuggestion={willApplySuggestion}
onChange={this.onChangeQuery}
onBlur={onBlur}
onRunQuery={onRunQuery}
placeholder={placeholder}
portalOrigin="loki"
/>
)}
</div>
</div>
{ExtraFieldElement}
</>
);
}}
</LocalStorageValueProvider>
<>
<div
className="gf-form-inline gf-form-inline--xs-view-flex-column flex-grow-1"
data-testid={this.props['data-testid']}
>
<div className="gf-form gf-form--grow flex-shrink-1 min-width-15">
<MonacoQueryFieldWrapper
runQueryOnBlur={app !== CoreApp.Explore}
datasource={datasource}
history={history ?? []}
onChange={this.onChangeQuery}
onRunQuery={onRunQuery}
initialValue={query.expr ?? ''}
/>
</div>
</div>
{ExtraFieldElement}
</>
);
}
}

View File

@ -1,8 +1,6 @@
import { render, screen } from '@testing-library/react';
import React from 'react';
import { config } from '@grafana/runtime';
import { createLokiDatasource } from '../../mocks';
import { LokiQuery } from '../../types';
@ -27,10 +25,6 @@ const createDefaultProps = () => {
return props;
};
beforeAll(() => {
config.featureToggles.lokiMonacoEditor = true;
});
describe('LokiQueryCodeEditor', () => {
it('shows explain section when showExplain is true', async () => {
const props = createDefaultProps();

View File

@ -1,7 +1,7 @@
import { css } from '@emotion/css';
import React from 'react';
import { CoreApp, GrafanaTheme2 } from '@grafana/data';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '@grafana/ui';
import { testIds } from '../../components/LokiQueryEditor';
@ -27,15 +27,6 @@ export function LokiQueryCodeEditor({
}: Props) {
const styles = useStyles2(getStyles);
// the inner QueryField works like this when a blur event happens:
// - if it has an onBlur prop, it calls it
// - else it calls onRunQuery (some extra conditions apply)
//
// we want it to not do anything when a blur event happens in explore mode,
// so we set an empty-function in such case. otherwise we set `undefined`,
// which will cause it to run the query when blur happens.
const onBlur = app === CoreApp.Explore ? () => undefined : undefined;
return (
<div className={styles.wrapper}>
<LokiQueryField
@ -44,7 +35,6 @@ export function LokiQueryCodeEditor({
range={range}
onRunQuery={onRunQuery}
onChange={onChange}
onBlur={onBlur}
history={history}
data={data}
app={app}