Skip to content

Commit 52a1ce1

Browse files
authored
[Lens] Runtime field editor (#91882)
1 parent 90e3013 commit 52a1ce1

File tree

22 files changed

+488
-36
lines changed

22 files changed

+488
-36
lines changed

api_docs/lens.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@
330330
"description": [],
331331
"source": {
332332
"path": "x-pack/plugins/lens/public/indexpattern_datasource/types.ts",
333-
"lineNumber": 72
333+
"lineNumber": 73
334334
},
335335
"signature": [
336336
"Record<string, Pick<",
@@ -347,7 +347,7 @@
347347
],
348348
"source": {
349349
"path": "x-pack/plugins/lens/public/indexpattern_datasource/types.ts",
350-
"lineNumber": 71
350+
"lineNumber": 72
351351
},
352352
"initialIsOpen": false
353353
},
@@ -483,7 +483,7 @@
483483
],
484484
"source": {
485485
"path": "x-pack/plugins/lens/public/plugin.ts",
486-
"lineNumber": 88
486+
"lineNumber": 90
487487
},
488488
"signature": [
489489
"React.ComponentType<",
@@ -509,7 +509,7 @@
509509
],
510510
"source": {
511511
"path": "x-pack/plugins/lens/public/plugin.ts",
512-
"lineNumber": 97
512+
"lineNumber": 99
513513
},
514514
"signature": [
515515
"(input: ",
@@ -533,7 +533,7 @@
533533
],
534534
"source": {
535535
"path": "x-pack/plugins/lens/public/plugin.ts",
536-
"lineNumber": 101
536+
"lineNumber": 103
537537
},
538538
"signature": [
539539
"() => boolean"
@@ -542,7 +542,7 @@
542542
],
543543
"source": {
544544
"path": "x-pack/plugins/lens/public/plugin.ts",
545-
"lineNumber": 79
545+
"lineNumber": 81
546546
},
547547
"initialIsOpen": false
548548
},
@@ -1553,7 +1553,7 @@
15531553
"description": [],
15541554
"source": {
15551555
"path": "x-pack/plugins/lens/public/indexpattern_datasource/types.ts",
1556-
"lineNumber": 75
1556+
"lineNumber": 76
15571557
},
15581558
"signature": [
15591559
"{ columns: Record<string, IndexPatternColumn>; columnOrder: string[]; incompleteColumns?: Record<string, IncompleteColumn> | undefined; }"

src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/script_field.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,10 @@ export const ScriptField = React.memo(({ existingConcreteFields, links, syntaxEr
9191
const painlessSyntaxErrors = PainlessLang.getSyntaxErrors();
9292
// It is possible for there to be more than one editor in a view,
9393
// so we need to get the syntax errors based on the editor (aka model) ID
94-
const editorHasSyntaxErrors = editorId && painlessSyntaxErrors[editorId].length > 0;
94+
const editorHasSyntaxErrors =
95+
editorId &&
96+
painlessSyntaxErrors[editorId] &&
97+
painlessSyntaxErrors[editorId].length > 0;
9598

9699
if (editorHasSyntaxErrors) {
97100
return resolve({
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
import { FtrProviderContext } from '../ftr_provider_context';
10+
11+
export function FieldEditorProvider({ getService }: FtrProviderContext) {
12+
const browser = getService('browser');
13+
const retry = getService('retry');
14+
const testSubjects = getService('testSubjects');
15+
16+
class FieldEditor {
17+
public async setName(name: string) {
18+
await testSubjects.setValue('nameField > input', name);
19+
}
20+
public async enableValue() {
21+
await testSubjects.setEuiSwitch('valueRow > toggle', 'check');
22+
}
23+
public async disableValue() {
24+
await testSubjects.setEuiSwitch('valueRow > toggle', 'uncheck');
25+
}
26+
public async typeScript(script: string) {
27+
const editor = await (await testSubjects.find('valueRow')).findByClassName(
28+
'react-monaco-editor-container'
29+
);
30+
const textarea = await editor.findByClassName('monaco-mouse-cursor-text');
31+
32+
await textarea.click();
33+
await browser.pressKeys(script);
34+
}
35+
public async save() {
36+
await retry.try(async () => {
37+
await testSubjects.click('fieldSaveButton');
38+
await testSubjects.missingOrFail('fieldSaveButton', { timeout: 2000 });
39+
});
40+
}
41+
}
42+
43+
return new FieldEditor();
44+
}

test/functional/services/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { FilterBarProvider } from './filter_bar';
3131
import { FlyoutProvider } from './flyout';
3232
import { GlobalNavProvider } from './global_nav';
3333
import { InspectorProvider } from './inspector';
34+
import { FieldEditorProvider } from './field_editor';
3435
import { ManagementMenuProvider } from './management';
3536
import { QueryBarProvider } from './query_bar';
3637
import { RemoteProvider } from './remote';
@@ -74,6 +75,7 @@ export const services = {
7475
browser: BrowserProvider,
7576
pieChart: PieChartProvider,
7677
inspector: InspectorProvider,
78+
fieldEditor: FieldEditorProvider,
7779
vegaDebugInspector: VegaDebugInspectorViewProvider,
7880
appsMenu: AppsMenuProvider,
7981
globalNav: GlobalNavProvider,

x-pack/plugins/lens/kibana.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"uiActions",
1616
"embeddable",
1717
"share",
18-
"presentationUtil"
18+
"presentationUtil",
19+
"indexPatternFieldEditor"
1920
],
2021
"optionalPlugins": [
2122
"usageCollection",

x-pack/plugins/lens/public/indexpattern_datasource/datapanel.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
padding: $euiSize $euiSize 0;
55
}
66

7+
.lnsInnerIndexPatternDataPanel__switcher {
8+
min-width: 0;
9+
}
10+
711
.lnsInnerIndexPatternDataPanel__header {
812
display: flex;
913
align-items: center;

x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* 2.0.
66
*/
77

8-
import React, { ChangeEvent } from 'react';
8+
import React, { ChangeEvent, ReactElement } from 'react';
99
import { createMockedDragDropContext } from './mocks';
1010
import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks';
1111
import { InnerIndexPatternDataPanel, IndexPatternDataPanel, MemoizedDataPanel } from './datapanel';
@@ -19,6 +19,7 @@ import { ChangeIndexPattern } from './change_indexpattern';
1919
import { EuiProgress, EuiLoadingSpinner } from '@elastic/eui';
2020
import { documentField } from './document_field';
2121
import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks';
22+
import { indexPatternFieldEditorPluginMock } from '../../../../../src/plugins/index_pattern_field_editor/public/mocks';
2223
import { getFieldByNameFactory } from './pure_helpers';
2324

2425
const fieldsOne = [
@@ -240,14 +241,16 @@ describe('IndexPattern Data Panel', () => {
240241
let defaultProps: Parameters<typeof InnerIndexPatternDataPanel>[0] & {
241242
showNoDataPopover: () => void;
242243
};
243-
let core: ReturnType<typeof coreMock['createSetup']>;
244+
let core: ReturnType<typeof coreMock['createStart']>;
244245

245246
beforeEach(() => {
246-
core = coreMock.createSetup();
247+
core = coreMock.createStart();
247248
defaultProps = {
248249
indexPatternRefs: [],
249250
existingFields: {},
250251
data: dataPluginMock.createStartContract(),
252+
indexPatternFieldEditor: indexPatternFieldEditorPluginMock.createStartContract(),
253+
onUpdateIndexPattern: jest.fn(),
251254
dragDropContext: createMockedDragDropContext(),
252255
currentIndexPatternId: '1',
253256
indexPatterns: initialState.indexPatterns,
@@ -806,5 +809,78 @@ describe('IndexPattern Data Panel', () => {
806809
'memory',
807810
]);
808811
});
812+
describe('edit field list', () => {
813+
beforeEach(() => {
814+
props.indexPatternFieldEditor.userPermissions.editIndexPattern = () => true;
815+
});
816+
it('should call field editor plugin on clicking add button', async () => {
817+
const mockIndexPattern = {};
818+
(props.data.indexPatterns.get as jest.Mock).mockImplementation(() =>
819+
Promise.resolve(mockIndexPattern)
820+
);
821+
const wrapper = mountWithIntl(<InnerIndexPatternDataPanel {...props} />);
822+
act(() => {
823+
(wrapper
824+
.find('[data-test-subj="lnsIndexPatternActions-popover"]')
825+
.first()
826+
.prop('children') as ReactElement).props.items[0].props.onClick();
827+
});
828+
829+
// wait for indx pattern to be loaded
830+
await new Promise((r) => setTimeout(r, 0));
831+
832+
expect(props.indexPatternFieldEditor.openEditor).toHaveBeenCalledWith(
833+
expect.objectContaining({
834+
ctx: expect.objectContaining({
835+
indexPattern: mockIndexPattern,
836+
}),
837+
})
838+
);
839+
});
840+
841+
it('should reload index pattern if callback gets called', async () => {
842+
const mockIndexPattern = {
843+
id: '1',
844+
fields: [
845+
{
846+
name: 'fieldOne',
847+
aggregatable: true,
848+
},
849+
],
850+
metaFields: [],
851+
};
852+
(props.data.indexPatterns.get as jest.Mock).mockImplementation(() =>
853+
Promise.resolve(mockIndexPattern)
854+
);
855+
const wrapper = mountWithIntl(<InnerIndexPatternDataPanel {...props} />);
856+
act(() => {
857+
(wrapper
858+
.find('[data-test-subj="lnsIndexPatternActions-popover"]')
859+
.first()
860+
.prop('children') as ReactElement).props.items[0].props.onClick();
861+
});
862+
// wait for indx pattern to be loaded
863+
await new Promise((r) => setTimeout(r, 0));
864+
await (props.indexPatternFieldEditor.openEditor as jest.Mock).mock.calls[0][0].onSave();
865+
// wait for indx pattern to be loaded
866+
await new Promise((r) => setTimeout(r, 0));
867+
expect(props.onUpdateIndexPattern).toHaveBeenCalledWith(
868+
expect.objectContaining({
869+
fields: [
870+
expect.objectContaining({
871+
name: 'fieldOne',
872+
}),
873+
expect.anything(),
874+
],
875+
})
876+
);
877+
});
878+
879+
it('should not render add button without permissions', () => {
880+
props.indexPatternFieldEditor.userPermissions.editIndexPattern = () => false;
881+
const wrapper = mountWithIntl(<InnerIndexPatternDataPanel {...props} />);
882+
expect(wrapper.find('[data-test-subj="indexPattern-add-field"]').exists()).toBe(false);
883+
});
884+
});
809885
});
810886
});

0 commit comments

Comments
 (0)