mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
4d268cbcdb
commit
57369915f5
@ -6027,10 +6027,6 @@ exports[`better eslint`] = {
|
|||||||
"public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx:5381": [
|
"public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx:5381": [
|
||||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"]
|
[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": [
|
"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"]
|
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"]
|
||||||
],
|
],
|
||||||
|
@ -23,7 +23,6 @@ Some stable features are enabled by default. You can disable a stable feature by
|
|||||||
| ----------------------------------- | ------------------------------------------------------------------------------------ | ------------------ |
|
| ----------------------------------- | ------------------------------------------------------------------------------------ | ------------------ |
|
||||||
| `disableEnvelopeEncryption` | Disable envelope encryption (emergency only) | |
|
| `disableEnvelopeEncryption` | Disable envelope encryption (emergency only) | |
|
||||||
| `database_metrics` | Add Prometheus metrics for database tables | |
|
| `database_metrics` | Add Prometheus metrics for database tables | |
|
||||||
| `lokiMonacoEditor` | Access to Monaco query editor for Loki | Yes |
|
|
||||||
| `featureHighlights` | Highlight Grafana Enterprise features | |
|
| `featureHighlights` | Highlight Grafana Enterprise features | |
|
||||||
| `cloudWatchDynamicLabels` | Use dynamic labels instead of alias patterns in CloudWatch datasource | Yes |
|
| `cloudWatchDynamicLabels` | Use dynamic labels instead of alias patterns in CloudWatch datasource | Yes |
|
||||||
| `internationalization` | Enables internationalization | Yes |
|
| `internationalization` | Enables internationalization | Yes |
|
||||||
|
@ -32,7 +32,6 @@ export interface FeatureToggles {
|
|||||||
publicDashboardsEmailSharing?: boolean;
|
publicDashboardsEmailSharing?: boolean;
|
||||||
lokiLive?: boolean;
|
lokiLive?: boolean;
|
||||||
lokiDataframeApi?: boolean;
|
lokiDataframeApi?: boolean;
|
||||||
lokiMonacoEditor?: boolean;
|
|
||||||
swaggerUi?: boolean;
|
swaggerUi?: boolean;
|
||||||
featureHighlights?: boolean;
|
featureHighlights?: boolean;
|
||||||
dashboardComments?: boolean;
|
dashboardComments?: boolean;
|
||||||
|
@ -94,13 +94,6 @@ var (
|
|||||||
Description: "Use experimental loki api for WebSocket streaming (early prototype)",
|
Description: "Use experimental loki api for WebSocket streaming (early prototype)",
|
||||||
State: FeatureStateAlpha,
|
State: FeatureStateAlpha,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "lokiMonacoEditor",
|
|
||||||
Description: "Access to Monaco query editor for Loki",
|
|
||||||
State: FeatureStateStable,
|
|
||||||
Expression: "true",
|
|
||||||
FrontendOnly: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "swaggerUi",
|
Name: "swaggerUi",
|
||||||
Description: "Serves swagger UI",
|
Description: "Serves swagger UI",
|
||||||
|
@ -71,10 +71,6 @@ const (
|
|||||||
// Use experimental loki api for WebSocket streaming (early prototype)
|
// Use experimental loki api for WebSocket streaming (early prototype)
|
||||||
FlagLokiDataframeApi = "lokiDataframeApi"
|
FlagLokiDataframeApi = "lokiDataframeApi"
|
||||||
|
|
||||||
// FlagLokiMonacoEditor
|
|
||||||
// Access to Monaco query editor for Loki
|
|
||||||
FlagLokiMonacoEditor = "lokiMonacoEditor"
|
|
||||||
|
|
||||||
// FlagSwaggerUi
|
// FlagSwaggerUi
|
||||||
// Serves swagger UI
|
// Serves swagger UI
|
||||||
FlagSwaggerUi = "swaggerUi"
|
FlagSwaggerUi = "swaggerUi"
|
||||||
|
@ -56,7 +56,6 @@ export const LokiAnnotationsQueryEditor = memo(function LokiAnnotationQueryEdito
|
|||||||
query={queryWithRefId}
|
query={queryWithRefId}
|
||||||
onChange={onChangeQuery}
|
onChange={onChangeQuery}
|
||||||
onRunQuery={() => {}}
|
onRunQuery={() => {}}
|
||||||
onBlur={() => {}}
|
|
||||||
history={history}
|
history={history}
|
||||||
ExtraFieldElement={
|
ExtraFieldElement={
|
||||||
<LokiOptionFields
|
<LokiOptionFields
|
||||||
|
@ -4,7 +4,6 @@ import { cloneDeep, defaultsDeep } from 'lodash';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { CoreApp } from '@grafana/data';
|
import { CoreApp } from '@grafana/data';
|
||||||
import { config } from '@grafana/runtime';
|
|
||||||
import { QueryEditorMode } from 'app/plugins/datasource/prometheus/querybuilder/shared/types';
|
import { QueryEditorMode } from 'app/plugins/datasource/prometheus/querybuilder/shared/types';
|
||||||
|
|
||||||
import { createLokiDatasource } from '../mocks';
|
import { createLokiDatasource } from '../mocks';
|
||||||
@ -59,10 +58,6 @@ const defaultProps = {
|
|||||||
onChange: () => {},
|
onChange: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
config.featureToggles.lokiMonacoEditor = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('LokiQueryEditorSelector', () => {
|
describe('LokiQueryEditorSelector', () => {
|
||||||
it('shows code editor if expr and nothing else', async () => {
|
it('shows code editor if expr and nothing else', async () => {
|
||||||
// We opt for showing code editor for queries created before this feature was added
|
// We opt for showing code editor for queries created before this feature was added
|
||||||
|
@ -12,7 +12,6 @@ export function LokiQueryEditorForAlerting(props: LokiQueryEditorProps) {
|
|||||||
query={query}
|
query={query}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onRunQuery={onRunQuery}
|
onRunQuery={onRunQuery}
|
||||||
onBlur={onRunQuery}
|
|
||||||
history={history}
|
history={history}
|
||||||
data={data}
|
data={data}
|
||||||
placeholder="Enter a Loki query"
|
placeholder="Enter a Loki query"
|
||||||
|
@ -2,7 +2,6 @@ import { render, screen } from '@testing-library/react';
|
|||||||
import React, { ComponentProps } from 'react';
|
import React, { ComponentProps } from 'react';
|
||||||
|
|
||||||
import { dateTime } from '@grafana/data';
|
import { dateTime } from '@grafana/data';
|
||||||
import { config } from '@grafana/runtime';
|
|
||||||
|
|
||||||
import { createLokiDatasource } from '../mocks';
|
import { createLokiDatasource } from '../mocks';
|
||||||
|
|
||||||
@ -29,7 +28,6 @@ describe('LokiQueryField', () => {
|
|||||||
};
|
};
|
||||||
jest.spyOn(props.datasource.languageProvider, 'start').mockResolvedValue([]);
|
jest.spyOn(props.datasource.languageProvider, 'start').mockResolvedValue([]);
|
||||||
jest.spyOn(props.datasource.languageProvider, 'fetchLabels').mockResolvedValue(['label1']);
|
jest.spyOn(props.datasource.languageProvider, 'fetchLabels').mockResolvedValue(['label1']);
|
||||||
config.featureToggles.lokiMonacoEditor = true;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('refreshes metrics when time range changes over 1 minute', async () => {
|
it('refreshes metrics when time range changes over 1 minute', async () => {
|
||||||
@ -73,12 +71,4 @@ describe('LokiQueryField', () => {
|
|||||||
rerender(<LokiQueryField {...props} range={newRange} />);
|
rerender(<LokiQueryField {...props} range={newRange} />);
|
||||||
expect(props.datasource.languageProvider.fetchLabels).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,66 +1,13 @@
|
|||||||
import { LanguageMap, languages as prismLanguages } from 'prismjs';
|
|
||||||
import React, { ReactNode } from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
import { Plugin, Node } from 'slate';
|
|
||||||
import { Editor } from 'slate-react';
|
|
||||||
|
|
||||||
import { CoreApp, QueryEditorProps } from '@grafana/data';
|
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 { LokiDatasource } from '../datasource';
|
||||||
import { escapeLabelValueInSelector, shouldRefreshLabels } from '../languageUtils';
|
import { shouldRefreshLabels } from '../languageUtils';
|
||||||
import { LokiQuery, LokiOptions } from '../types';
|
import { LokiQuery, LokiOptions } from '../types';
|
||||||
|
|
||||||
import { MonacoQueryFieldWrapper } from './monaco-query-field/MonacoQueryFieldWrapper';
|
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> {
|
export interface LokiQueryFieldProps extends QueryEditorProps<LokiDatasource, LokiQuery, LokiOptions> {
|
||||||
ExtraFieldElement?: ReactNode;
|
ExtraFieldElement?: ReactNode;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
@ -69,28 +16,15 @@ export interface LokiQueryFieldProps extends QueryEditorProps<LokiDatasource, Lo
|
|||||||
|
|
||||||
interface LokiQueryFieldState {
|
interface LokiQueryFieldState {
|
||||||
labelsLoaded: boolean;
|
labelsLoaded: boolean;
|
||||||
labelBrowserVisible: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, LokiQueryFieldState> {
|
export class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, LokiQueryFieldState> {
|
||||||
plugins: Array<Plugin<Editor>>;
|
|
||||||
_isMounted = false;
|
_isMounted = false;
|
||||||
|
|
||||||
constructor(props: LokiQueryFieldProps) {
|
constructor(props: LokiQueryFieldProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = { labelsLoaded: false, labelBrowserVisible: false };
|
this.state = { labelsLoaded: 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() }
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
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) => {
|
onChangeQuery = (value: string, override?: boolean) => {
|
||||||
// Send text change to parent
|
// Send text change to parent
|
||||||
const { query, onChange, onRunQuery } = this.props;
|
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() {
|
render() {
|
||||||
const {
|
const { ExtraFieldElement, query, app, datasource, history, onRunQuery } = this.props;
|
||||||
ExtraFieldElement,
|
|
||||||
query,
|
|
||||||
app,
|
|
||||||
datasource,
|
|
||||||
placeholder = 'Enter a Loki query (run with Shift+Enter)',
|
|
||||||
history,
|
|
||||||
onRunQuery,
|
|
||||||
onBlur,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LocalStorageValueProvider<string[]> storageKey={LAST_USED_LABELS_KEY} defaultValue={[]}>
|
<>
|
||||||
{(lastUsedLabels, onLastUsedLabelsSave, onLastUsedLabelsDelete) => {
|
<div
|
||||||
return (
|
className="gf-form-inline gf-form-inline--xs-view-flex-column flex-grow-1"
|
||||||
<>
|
data-testid={this.props['data-testid']}
|
||||||
<div
|
>
|
||||||
className="gf-form-inline gf-form-inline--xs-view-flex-column flex-grow-1"
|
<div className="gf-form gf-form--grow flex-shrink-1 min-width-15">
|
||||||
data-testid={this.props['data-testid']}
|
<MonacoQueryFieldWrapper
|
||||||
>
|
runQueryOnBlur={app !== CoreApp.Explore}
|
||||||
<div className="gf-form gf-form--grow flex-shrink-1 min-width-15">
|
datasource={datasource}
|
||||||
{config.featureToggles.lokiMonacoEditor ? (
|
history={history ?? []}
|
||||||
<MonacoQueryFieldWrapper
|
onChange={this.onChangeQuery}
|
||||||
runQueryOnBlur={app !== CoreApp.Explore}
|
onRunQuery={onRunQuery}
|
||||||
datasource={datasource}
|
initialValue={query.expr ?? ''}
|
||||||
history={history ?? []}
|
/>
|
||||||
onChange={this.onChangeQuery}
|
</div>
|
||||||
onRunQuery={onRunQuery}
|
</div>
|
||||||
initialValue={query.expr ?? ''}
|
{ExtraFieldElement}
|
||||||
/>
|
</>
|
||||||
) : (
|
|
||||||
<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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { config } from '@grafana/runtime';
|
|
||||||
|
|
||||||
import { createLokiDatasource } from '../../mocks';
|
import { createLokiDatasource } from '../../mocks';
|
||||||
import { LokiQuery } from '../../types';
|
import { LokiQuery } from '../../types';
|
||||||
|
|
||||||
@ -27,10 +25,6 @@ const createDefaultProps = () => {
|
|||||||
return props;
|
return props;
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
config.featureToggles.lokiMonacoEditor = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('LokiQueryCodeEditor', () => {
|
describe('LokiQueryCodeEditor', () => {
|
||||||
it('shows explain section when showExplain is true', async () => {
|
it('shows explain section when showExplain is true', async () => {
|
||||||
const props = createDefaultProps();
|
const props = createDefaultProps();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { CoreApp, GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { useStyles2 } from '@grafana/ui';
|
import { useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
import { testIds } from '../../components/LokiQueryEditor';
|
import { testIds } from '../../components/LokiQueryEditor';
|
||||||
@ -27,15 +27,6 @@ export function LokiQueryCodeEditor({
|
|||||||
}: Props) {
|
}: Props) {
|
||||||
const styles = useStyles2(getStyles);
|
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 (
|
return (
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
<LokiQueryField
|
<LokiQueryField
|
||||||
@ -44,7 +35,6 @@ export function LokiQueryCodeEditor({
|
|||||||
range={range}
|
range={range}
|
||||||
onRunQuery={onRunQuery}
|
onRunQuery={onRunQuery}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onBlur={onBlur}
|
|
||||||
history={history}
|
history={history}
|
||||||
data={data}
|
data={data}
|
||||||
app={app}
|
app={app}
|
||||||
|
Loading…
Reference in New Issue
Block a user