Skip to content
105 changes: 54 additions & 51 deletions src/platform/packages/private/kbn-esql-editor/src/esql_editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ export const ESQLEditor = memo(function ESQLEditor({
esqlVariables,
}: ESQLEditorProps) {
const popoverRef = useRef<HTMLDivElement>(null);
const editorModel = useRef<monaco.editor.ITextModel>();
const editor1 = useRef<monaco.editor.IStandaloneCodeEditor>();
const containerRef = useRef<HTMLElement>(null);

const datePickerOpenStatusRef = useRef<boolean>(false);
const theme = useEuiTheme();
const kibana = useKibana<ESQLEditorDeps>();
Expand Down Expand Up @@ -330,6 +334,12 @@ export const ESQLEditor = memo(function ESQLEditor({
);
});

editor1.current?.addCommand(
// eslint-disable-next-line no-bitwise
monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter,
onQuerySubmit
);

const styles = esqlEditorStyles(
theme.euiTheme,
editorHeight,
Expand All @@ -339,9 +349,6 @@ export const ESQLEditor = memo(function ESQLEditor({
Boolean(editorIsInline),
Boolean(hasOutline)
);
const editorModel = useRef<monaco.editor.ITextModel>();
const editor1 = useRef<monaco.editor.IStandaloneCodeEditor>();
const containerRef = useRef<HTMLElement>(null);

const onMouseDownResize = useCallback<typeof onMouseDownResizeHandler>(
(
Expand Down Expand Up @@ -656,47 +663,50 @@ export const ESQLEditor = memo(function ESQLEditor({

onLayoutChangeRef.current = onLayoutChange;

const codeEditorOptions: CodeEditorProps['options'] = {
hover: {
above: false,
},
accessibilitySupport: 'off',
autoIndent: 'none',
automaticLayout: true,
fixedOverflowWidgets: true,
folding: false,
fontSize: 14,
hideCursorInOverviewRuler: true,
// this becomes confusing with multiple markers, so quick fixes
// will be proposed only within the tooltip
lightbulb: {
enabled: false,
},
lineDecorationsWidth: 20,
lineNumbers: 'on',
lineNumbersMinChars: 3,
minimap: { enabled: false },
overviewRulerLanes: 0,
overviewRulerBorder: false,
padding: {
top: 8,
bottom: 8,
},
quickSuggestions: true,
readOnly: isDisabled,
renderLineHighlight: 'line',
renderLineHighlightOnlyWhenFocus: true,
scrollbar: {
horizontal: 'hidden',
horizontalScrollbarSize: 6,
vertical: 'auto',
verticalScrollbarSize: 6,
},
scrollBeyondLastLine: false,
theme: ESQL_LANG_ID,
wordWrap: 'on',
wrappingIndent: 'none',
};
const codeEditorOptions: CodeEditorProps['options'] = useMemo(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ Irrelevant with the PR, just saw that we do an extra rerender here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch

() => ({
hover: {
above: false,
},
accessibilitySupport: 'off',
autoIndent: 'none',
automaticLayout: true,
fixedOverflowWidgets: true,
folding: false,
fontSize: 14,
hideCursorInOverviewRuler: true,
// this becomes confusing with multiple markers, so quick fixes
// will be proposed only within the tooltip
lightbulb: {
enabled: false,
},
lineDecorationsWidth: 20,
lineNumbers: 'on',
lineNumbersMinChars: 3,
minimap: { enabled: false },
overviewRulerLanes: 0,
overviewRulerBorder: false,
padding: {
top: 8,
bottom: 8,
},
quickSuggestions: true,
readOnly: isDisabled,
renderLineHighlight: 'line',
renderLineHighlightOnlyWhenFocus: true,
scrollbar: {
horizontal: 'hidden',
horizontalScrollbarSize: 6,
vertical: 'auto',
verticalScrollbarSize: 6,
},
scrollBeyondLastLine: false,
theme: ESQL_LANG_ID,
wordWrap: 'on',
wrappingIndent: 'none',
}),
[isDisabled]
);

const editorPanel = (
<>
Expand Down Expand Up @@ -793,13 +803,6 @@ export const ESQLEditor = memo(function ESQLEditor({
onEditorFocus();
});

// on CMD/CTRL + Enter submit the query
editor.addCommand(
// eslint-disable-next-line no-bitwise
monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter,
onQuerySubmit
);

// on CMD/CTRL + / comment out the entire line
editor.addCommand(
// eslint-disable-next-line no-bitwise
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
import { getESQLResults } from '@kbn/esql-utils';
import type { LensPluginStartDependencies } from '../../../plugin';
import type { TypedLensSerializedState } from '../../../react_embeddable/types';
import { createMockStartDependencies } from '../../../editor_frame_service/mocks';
import {
mockVisualizationMap,
Expand All @@ -15,7 +16,7 @@ import {
mockAllSuggestions,
} from '../../../mocks';
import { suggestionsApi } from '../../../lens_suggestions_api';
import { getSuggestions } from './helpers';
import { getSuggestions, injectESQLQueryIntoLensLayers } from './helpers';

const mockSuggestionApi = suggestionsApi as jest.Mock;
const mockFetchData = getESQLResults as jest.Mock;
Expand Down Expand Up @@ -82,74 +83,146 @@ jest.mock('@kbn/esql-utils', () => {
};
});

describe('getSuggestions', () => {
const query = {
esql: 'from index1 | limit 10 | stats average = avg(bytes)',
};
const mockStartDependencies =
createMockStartDependencies() as unknown as LensPluginStartDependencies;
const dataViews = dataViewPluginMocks.createStartContract();
dataViews.create.mockResolvedValue(mockDataViewWithTimefield);
const dataviewSpecArr = [
{
id: 'd2588ae7-9ea0-4439-9f5b-f808754a3b97',
title: 'index1',
timeFieldName: '@timestamp',
sourceFilters: [],
fieldFormats: {},
runtimeFieldMap: {},
fieldAttrs: {},
allowNoIndex: false,
name: 'index1',
},
];
const startDependencies = {
...mockStartDependencies,
dataViews,
};
describe('Lens inline editing helpers', () => {
describe('getSuggestions', () => {
const query = {
esql: 'from index1 | limit 10 | stats average = avg(bytes)',
};
const mockStartDependencies =
createMockStartDependencies() as unknown as LensPluginStartDependencies;
const dataViews = dataViewPluginMocks.createStartContract();
dataViews.create.mockResolvedValue(mockDataViewWithTimefield);
const dataviewSpecArr = [
{
id: 'd2588ae7-9ea0-4439-9f5b-f808754a3b97',
title: 'index1',
timeFieldName: '@timestamp',
sourceFilters: [],
fieldFormats: {},
runtimeFieldMap: {},
fieldAttrs: {},
allowNoIndex: false,
name: 'index1',
},
];
const startDependencies = {
...mockStartDependencies,
dataViews,
};

it('returns the suggestions attributes correctly', async () => {
const suggestionsAttributes = await getSuggestions(
query,
startDependencies,
mockDatasourceMap(),
mockVisualizationMap(),
dataviewSpecArr,
jest.fn()
);
expect(suggestionsAttributes?.visualizationType).toBe(mockAllSuggestions[0].visualizationId);
expect(suggestionsAttributes?.state.visualization).toStrictEqual(
mockAllSuggestions[0].visualizationState
);
});
it('returns the suggestions attributes correctly', async () => {
const suggestionsAttributes = await getSuggestions(
query,
startDependencies,
mockDatasourceMap(),
mockVisualizationMap(),
dataviewSpecArr,
jest.fn()
);
expect(suggestionsAttributes?.visualizationType).toBe(mockAllSuggestions[0].visualizationId);
expect(suggestionsAttributes?.state.visualization).toStrictEqual(
mockAllSuggestions[0].visualizationState
);
});

it('returns undefined if no suggestions are computed', async () => {
mockSuggestionApi.mockResolvedValueOnce([]);
const suggestionsAttributes = await getSuggestions(
query,
startDependencies,
mockDatasourceMap(),
mockVisualizationMap(),
dataviewSpecArr,
jest.fn()
);
expect(suggestionsAttributes).toBeUndefined();
it('returns undefined if no suggestions are computed', async () => {
mockSuggestionApi.mockResolvedValueOnce([]);
const suggestionsAttributes = await getSuggestions(
query,
startDependencies,
mockDatasourceMap(),
mockVisualizationMap(),
dataviewSpecArr,
jest.fn()
);
expect(suggestionsAttributes).toBeUndefined();
});

it('returns an error if fetching the data fails', async () => {
mockFetchData.mockImplementation(() => {
throw new Error('sorry!');
});
const setErrorsSpy = jest.fn();
const suggestionsAttributes = await getSuggestions(
query,
startDependencies,
mockDatasourceMap(),
mockVisualizationMap(),
dataviewSpecArr,
setErrorsSpy
);
expect(suggestionsAttributes).toBeUndefined();
expect(setErrorsSpy).toHaveBeenCalled();
});
});

it('returns an error if fetching the data fails', async () => {
mockFetchData.mockImplementation(() => {
throw new Error('sorry!');
describe('injectESQLQueryIntoLensLayers', () => {
const query = {
esql: 'from index1 | limit 10 | stats average = avg(bytes)',
};

it('should inject the query correctly for ES|QL charts', async () => {
const lensAttributes = {
title: 'test',
visualizationType: 'testVis',
state: {
datasourceStates: {
textBased: { layers: { layer1: { query: { esql: 'from index1 | limit 10' } } } },
},
visualization: { preferredSeriesType: 'line' },
},
filters: [],
query: {
esql: 'from index1 | limit 10',
},
references: [],
} as unknown as TypedLensSerializedState['attributes'];

const expectedLensAttributes = {
...lensAttributes,
state: {
...lensAttributes.state,
datasourceStates: {
...lensAttributes.state.datasourceStates,
textBased: {
...lensAttributes.state.datasourceStates.textBased,
layers: {
layer1: {
query: { esql: 'from index1 | limit 10 | stats average = avg(bytes)' },
},
},
},
},
},
};
const newAttributes = injectESQLQueryIntoLensLayers(lensAttributes, query);
expect(newAttributes).toStrictEqual(expectedLensAttributes);
});

it('should return the Lens attributes as they are for unknown datasourceId', async () => {
const attributes = {
visualizationType: 'lnsXY',
state: {
visualization: { preferredSeriesType: 'line' },
datasourceStates: { unknownId: { layers: {} } },
},
} as unknown as TypedLensSerializedState['attributes'];
expect(injectESQLQueryIntoLensLayers(attributes, { esql: 'from foo' })).toStrictEqual(
attributes
);
});

it('should return the Lens attributes as they are for form based charts', async () => {
const attributes = {
visualizationType: 'lnsXY',
state: {
visualization: { preferredSeriesType: 'line' },
datasourceStates: { formBased: { layers: {} } },
},
} as TypedLensSerializedState['attributes'];
expect(injectESQLQueryIntoLensLayers(attributes, { esql: 'from foo' })).toStrictEqual(
attributes
);
});
const setErrorsSpy = jest.fn();
const suggestionsAttributes = await getSuggestions(
query,
startDependencies,
mockDatasourceMap(),
mockVisualizationMap(),
dataviewSpecArr,
setErrorsSpy
);
expect(suggestionsAttributes).toBeUndefined();
expect(setErrorsSpy).toHaveBeenCalled();
});
});
Loading