Skip to content

Commit 0758240

Browse files
[8.9] [controls] fix Dashboard getting stuck at loading in Kibana when Controls is used and mapping changed from integer to keyword (#163529) (#163750)
# Backport This will backport the following commits from `main` to `8.9`: - [[controls] fix Dashboard getting stuck at loading in Kibana when Controls is used and mapping changed from integer to keyword (#163529)](#163529) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Nathan Reese","email":"reese.nathan@elastic.co"},"sourceCommit":{"committedDate":"2023-08-11T19:58:19Z","message":"[controls] fix Dashboard getting stuck at loading in Kibana when Controls is used and mapping changed from integer to keyword (#163529)\n\nCloses https://github.com/elastic/kibana/issues/162474\r\n\r\n### Changes\r\n* RangeSliderEmbeddable - call setInitializationFinished when\r\nrunRangeSliderQuery throws. This fixes the issue\r\n* Investigated if OptionsListEmbeddable is vulnerable to the same issue.\r\nIt's not because it uses its own REST API that has a service wrapper\r\n`OptionsListService`. `OptionsListService` handles REST API errors.\r\n* Add unit test verifying OptionsListService.runOptionsListRequest does\r\nnot throw when there are REST API errors and always returns a response.\r\n* Add unit tests ensuring setInitializationFinished is called for both\r\nRangeSliderEmbeddable and OptionsListEmbeddable in all cases\r\n* Other clean up\r\n* Fix uses of `dataViewsService.get`. `dataViewsService.get` throws when\r\ndata view is not found. It does not return undefined. PR updates\r\nOptionsListEmbeddable, RangeSliderEmbeddable, and mocked data service\r\n* Fix uses of `dataView.getFieldByName`. `dataView.getFieldByName`\r\nreturns undefined when field is not found and never throws. PR updates\r\nOptionsListEmbeddable and RangeSliderEmbeddable\r\n * Remove `resp` wrapper around mocked `fetch` results.\r\n\r\n### Test instructions\r\n1) In console run \r\n ```\r\n PUT test1\r\n\r\n PUT test1/_mapping\r\n {\r\n \"properties\": {\r\n \"value\": {\r\n \"type\": \"integer\"\r\n }\r\n }\r\n }\r\n\r\n PUT test1/_doc/1\r\n {\r\n \"value\" : 1\r\n }\r\n\r\n PUT test1/_doc/2\r\n {\r\n \"value\" : 10\r\n }\r\n ```\r\n2) create data view `test*`\r\n3) create dashboard with range slider control on test*.value.\r\n4) select a range in the range slider\r\n5) save dashboard\r\n6) run the following in console\r\n ```\r\n PUT test2\r\n\r\n PUT test2/_mapping\r\n {\r\n \"properties\": {\r\n \"value\": {\r\n \"type\": \"keyword\"\r\n }\r\n }\r\n }\r\n\r\n PUT test2/_doc/1\r\n {\r\n \"value\" : \"foo\"\r\n }\r\n\r\n DELETE test1\r\n ```\r\n7) Open dashboard saved above. Verify dashboard opens and control\r\ndisplays an error message about being unable to run aggregation on\r\nkeyword field.\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Devon Thomson <devon.thomson@elastic.co>","sha":"0a74fa03a028e7002b5fdebdbfa7dc0c7283aa75","branchLabelMapping":{"^v8.10.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","Feature:Input Control","Team:Presentation","v8.10.0","v8.9.2"],"number":163529,"url":"https://github.com/elastic/kibana/pull/163529","mergeCommit":{"message":"[controls] fix Dashboard getting stuck at loading in Kibana when Controls is used and mapping changed from integer to keyword (#163529)\n\nCloses https://github.com/elastic/kibana/issues/162474\r\n\r\n### Changes\r\n* RangeSliderEmbeddable - call setInitializationFinished when\r\nrunRangeSliderQuery throws. This fixes the issue\r\n* Investigated if OptionsListEmbeddable is vulnerable to the same issue.\r\nIt's not because it uses its own REST API that has a service wrapper\r\n`OptionsListService`. `OptionsListService` handles REST API errors.\r\n* Add unit test verifying OptionsListService.runOptionsListRequest does\r\nnot throw when there are REST API errors and always returns a response.\r\n* Add unit tests ensuring setInitializationFinished is called for both\r\nRangeSliderEmbeddable and OptionsListEmbeddable in all cases\r\n* Other clean up\r\n* Fix uses of `dataViewsService.get`. `dataViewsService.get` throws when\r\ndata view is not found. It does not return undefined. PR updates\r\nOptionsListEmbeddable, RangeSliderEmbeddable, and mocked data service\r\n* Fix uses of `dataView.getFieldByName`. `dataView.getFieldByName`\r\nreturns undefined when field is not found and never throws. PR updates\r\nOptionsListEmbeddable and RangeSliderEmbeddable\r\n * Remove `resp` wrapper around mocked `fetch` results.\r\n\r\n### Test instructions\r\n1) In console run \r\n ```\r\n PUT test1\r\n\r\n PUT test1/_mapping\r\n {\r\n \"properties\": {\r\n \"value\": {\r\n \"type\": \"integer\"\r\n }\r\n }\r\n }\r\n\r\n PUT test1/_doc/1\r\n {\r\n \"value\" : 1\r\n }\r\n\r\n PUT test1/_doc/2\r\n {\r\n \"value\" : 10\r\n }\r\n ```\r\n2) create data view `test*`\r\n3) create dashboard with range slider control on test*.value.\r\n4) select a range in the range slider\r\n5) save dashboard\r\n6) run the following in console\r\n ```\r\n PUT test2\r\n\r\n PUT test2/_mapping\r\n {\r\n \"properties\": {\r\n \"value\": {\r\n \"type\": \"keyword\"\r\n }\r\n }\r\n }\r\n\r\n PUT test2/_doc/1\r\n {\r\n \"value\" : \"foo\"\r\n }\r\n\r\n DELETE test1\r\n ```\r\n7) Open dashboard saved above. Verify dashboard opens and control\r\ndisplays an error message about being unable to run aggregation on\r\nkeyword field.\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Devon Thomson <devon.thomson@elastic.co>","sha":"0a74fa03a028e7002b5fdebdbfa7dc0c7283aa75"}},"sourceBranch":"main","suggestedTargetBranches":["8.9"],"targetPullRequestStates":[{"branch":"main","label":"v8.10.0","labelRegex":"^v8.10.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/163529","number":163529,"mergeCommit":{"message":"[controls] fix Dashboard getting stuck at loading in Kibana when Controls is used and mapping changed from integer to keyword (#163529)\n\nCloses https://github.com/elastic/kibana/issues/162474\r\n\r\n### Changes\r\n* RangeSliderEmbeddable - call setInitializationFinished when\r\nrunRangeSliderQuery throws. This fixes the issue\r\n* Investigated if OptionsListEmbeddable is vulnerable to the same issue.\r\nIt's not because it uses its own REST API that has a service wrapper\r\n`OptionsListService`. `OptionsListService` handles REST API errors.\r\n* Add unit test verifying OptionsListService.runOptionsListRequest does\r\nnot throw when there are REST API errors and always returns a response.\r\n* Add unit tests ensuring setInitializationFinished is called for both\r\nRangeSliderEmbeddable and OptionsListEmbeddable in all cases\r\n* Other clean up\r\n* Fix uses of `dataViewsService.get`. `dataViewsService.get` throws when\r\ndata view is not found. It does not return undefined. PR updates\r\nOptionsListEmbeddable, RangeSliderEmbeddable, and mocked data service\r\n* Fix uses of `dataView.getFieldByName`. `dataView.getFieldByName`\r\nreturns undefined when field is not found and never throws. PR updates\r\nOptionsListEmbeddable and RangeSliderEmbeddable\r\n * Remove `resp` wrapper around mocked `fetch` results.\r\n\r\n### Test instructions\r\n1) In console run \r\n ```\r\n PUT test1\r\n\r\n PUT test1/_mapping\r\n {\r\n \"properties\": {\r\n \"value\": {\r\n \"type\": \"integer\"\r\n }\r\n }\r\n }\r\n\r\n PUT test1/_doc/1\r\n {\r\n \"value\" : 1\r\n }\r\n\r\n PUT test1/_doc/2\r\n {\r\n \"value\" : 10\r\n }\r\n ```\r\n2) create data view `test*`\r\n3) create dashboard with range slider control on test*.value.\r\n4) select a range in the range slider\r\n5) save dashboard\r\n6) run the following in console\r\n ```\r\n PUT test2\r\n\r\n PUT test2/_mapping\r\n {\r\n \"properties\": {\r\n \"value\": {\r\n \"type\": \"keyword\"\r\n }\r\n }\r\n }\r\n\r\n PUT test2/_doc/1\r\n {\r\n \"value\" : \"foo\"\r\n }\r\n\r\n DELETE test1\r\n ```\r\n7) Open dashboard saved above. Verify dashboard opens and control\r\ndisplays an error message about being unable to run aggregation on\r\nkeyword field.\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Devon Thomson <devon.thomson@elastic.co>","sha":"0a74fa03a028e7002b5fdebdbfa7dc0c7283aa75"}},{"branch":"8.9","label":"v8.9.2","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Nathan Reese <reese.nathan@elastic.co>
1 parent b177676 commit 0758240

12 files changed

Lines changed: 471 additions & 104 deletions

File tree

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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 { ControlGroupInput } from '../../../common';
10+
import { lazyLoadReduxToolsPackage } from '@kbn/presentation-util-plugin/public';
11+
import { storybookFlightsDataView } from '@kbn/presentation-util-plugin/public/mocks';
12+
import { OPTIONS_LIST_CONTROL } from '../../../common';
13+
import { ControlGroupContainer } from '../../control_group/embeddable/control_group_container';
14+
import { pluginServices } from '../../services';
15+
import { injectStorybookDataView } from '../../services/data_views/data_views.story';
16+
import { OptionsListEmbeddableFactory } from './options_list_embeddable_factory';
17+
import { OptionsListEmbeddable } from './options_list_embeddable';
18+
19+
pluginServices.getServices().controls.getControlFactory = jest
20+
.fn()
21+
.mockImplementation((type: string) => {
22+
if (type === OPTIONS_LIST_CONTROL) return new OptionsListEmbeddableFactory();
23+
});
24+
25+
describe('initialize', () => {
26+
describe('without selected options', () => {
27+
test('should notify control group when initialization is finished', async () => {
28+
const reduxEmbeddablePackage = await lazyLoadReduxToolsPackage();
29+
const controlGroupInput = { chainingSystem: 'NONE', panels: {} } as ControlGroupInput;
30+
const container = new ControlGroupContainer(reduxEmbeddablePackage, controlGroupInput);
31+
32+
// data view not required for test case
33+
// setInitializationFinished is called before fetching options when value is not provided
34+
injectStorybookDataView(undefined);
35+
36+
const control = await container.addOptionsListControl({
37+
dataViewId: 'demoDataFlights',
38+
fieldName: 'OriginCityName',
39+
});
40+
41+
expect(container.getInput().panels[control.getInput().id].type).toBe(OPTIONS_LIST_CONTROL);
42+
expect(container.getOutput().embeddableLoaded[control.getInput().id]).toBe(true);
43+
});
44+
});
45+
46+
describe('with selected options', () => {
47+
test('should set error message when data view can not be found', async () => {
48+
const reduxEmbeddablePackage = await lazyLoadReduxToolsPackage();
49+
const controlGroupInput = { chainingSystem: 'NONE', panels: {} } as ControlGroupInput;
50+
const container = new ControlGroupContainer(reduxEmbeddablePackage, controlGroupInput);
51+
52+
injectStorybookDataView(undefined);
53+
54+
const control = (await container.addOptionsListControl({
55+
dataViewId: 'demoDataFlights',
56+
fieldName: 'OriginCityName',
57+
selectedOptions: ['Seoul', 'Tokyo'],
58+
})) as OptionsListEmbeddable;
59+
60+
// await redux dispatch
61+
await new Promise((resolve) => process.nextTick(resolve));
62+
63+
const reduxState = control.getState();
64+
expect(reduxState.output.loading).toBe(false);
65+
expect(reduxState.componentState.error).toBe(
66+
'mock DataViews service currentDataView is undefined, call injectStorybookDataView to set'
67+
);
68+
});
69+
70+
test('should set error message when field can not be found', async () => {
71+
const reduxEmbeddablePackage = await lazyLoadReduxToolsPackage();
72+
const controlGroupInput = { chainingSystem: 'NONE', panels: {} } as ControlGroupInput;
73+
const container = new ControlGroupContainer(reduxEmbeddablePackage, controlGroupInput);
74+
75+
injectStorybookDataView(storybookFlightsDataView);
76+
77+
const control = (await container.addOptionsListControl({
78+
dataViewId: 'demoDataFlights',
79+
fieldName: 'myField',
80+
selectedOptions: ['Seoul', 'Tokyo'],
81+
})) as OptionsListEmbeddable;
82+
83+
// await redux dispatch
84+
await new Promise((resolve) => process.nextTick(resolve));
85+
86+
const reduxState = control.getState();
87+
expect(reduxState.output.loading).toBe(false);
88+
expect(reduxState.componentState.error).toBe('Could not locate field: myField');
89+
});
90+
91+
test('should notify control group when initialization is finished', async () => {
92+
const reduxEmbeddablePackage = await lazyLoadReduxToolsPackage();
93+
const controlGroupInput = { chainingSystem: 'NONE', panels: {} } as ControlGroupInput;
94+
const container = new ControlGroupContainer(reduxEmbeddablePackage, controlGroupInput);
95+
96+
injectStorybookDataView(storybookFlightsDataView);
97+
98+
const control = await container.addOptionsListControl({
99+
dataViewId: 'demoDataFlights',
100+
fieldName: 'OriginCityName',
101+
selectedOptions: ['Seoul', 'Tokyo'],
102+
});
103+
104+
expect(container.getInput().panels[control.getInput().id].type).toBe(OPTIONS_LIST_CONTROL);
105+
expect(container.getOutput().embeddableLoaded[control.getInput().id]).toBe(true);
106+
});
107+
});
108+
});

src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -245,13 +245,6 @@ export class OptionsListEmbeddable
245245
if (!this.dataView || this.dataView.id !== dataViewId) {
246246
try {
247247
this.dataView = await this.dataViewsService.get(dataViewId);
248-
if (!this.dataView)
249-
throw new Error(
250-
i18n.translate('controls.optionsList.errors.dataViewNotFound', {
251-
defaultMessage: 'Could not locate data view: {dataViewId}',
252-
values: { dataViewId },
253-
})
254-
);
255248
} catch (e) {
256249
this.dispatch.setErrorMessage(e.message);
257250
}
@@ -260,25 +253,21 @@ export class OptionsListEmbeddable
260253
}
261254

262255
if (this.dataView && (!this.field || this.field.name !== fieldName)) {
263-
try {
264-
const originalField = this.dataView.getFieldByName(fieldName);
265-
if (!originalField) {
266-
throw new Error(
267-
i18n.translate('controls.optionsList.errors.fieldNotFound', {
268-
defaultMessage: 'Could not locate field: {fieldName}',
269-
values: { fieldName },
270-
})
271-
);
272-
}
273-
274-
this.field = originalField.toSpec();
275-
} catch (e) {
276-
this.dispatch.setErrorMessage(e.message);
256+
const field = this.dataView.getFieldByName(fieldName);
257+
if (field) {
258+
this.field = field.toSpec();
259+
this.dispatch.setField(this.field);
260+
} else {
261+
this.dispatch.setErrorMessage(
262+
i18n.translate('controls.optionsList.errors.fieldNotFound', {
263+
defaultMessage: 'Could not locate field: {fieldName}',
264+
values: { fieldName },
265+
})
266+
);
277267
}
278-
this.dispatch.setField(this.field);
279268
}
280269

281-
return { dataView: this.dataView, field: this.field! };
270+
return { dataView: this.dataView, field: this.field };
282271
};
283272

284273
private runOptionsListQuery = async (size: number = MIN_OPTIONS_LIST_REQUEST_SIZE) => {
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
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 { of } from 'rxjs';
10+
import { ControlGroupInput } from '../../../common';
11+
import { lazyLoadReduxToolsPackage } from '@kbn/presentation-util-plugin/public';
12+
import { storybookFlightsDataView } from '@kbn/presentation-util-plugin/public/mocks';
13+
import { RANGE_SLIDER_CONTROL } from '../../../common';
14+
import { ControlGroupContainer } from '../../control_group/embeddable/control_group_container';
15+
import { pluginServices } from '../../services';
16+
import { injectStorybookDataView } from '../../services/data_views/data_views.story';
17+
import { RangeSliderEmbeddableFactory } from './range_slider_embeddable_factory';
18+
import { RangeSliderEmbeddable } from './range_slider_embeddable';
19+
20+
let totalResults = 20;
21+
beforeEach(() => {
22+
totalResults = 20;
23+
24+
pluginServices.getServices().controls.getControlFactory = jest
25+
.fn()
26+
.mockImplementation((type: string) => {
27+
if (type === RANGE_SLIDER_CONTROL) return new RangeSliderEmbeddableFactory();
28+
});
29+
30+
pluginServices.getServices().data.searchSource.create = jest.fn().mockImplementation(() => {
31+
let isAggsRequest = false;
32+
return {
33+
setField: (key: string) => {
34+
if (key === 'aggs') {
35+
isAggsRequest = true;
36+
}
37+
},
38+
fetch$: () => {
39+
return isAggsRequest
40+
? of({
41+
rawResponse: { aggregations: { minAgg: { value: 0 }, maxAgg: { value: 1000 } } },
42+
})
43+
: of({
44+
rawResponse: { hits: { total: { value: totalResults } } },
45+
});
46+
},
47+
};
48+
});
49+
});
50+
51+
describe('initialize', () => {
52+
describe('without selected range', () => {
53+
test('should notify control group when initialization is finished', async () => {
54+
const reduxEmbeddablePackage = await lazyLoadReduxToolsPackage();
55+
const controlGroupInput = { chainingSystem: 'NONE', panels: {} } as ControlGroupInput;
56+
const container = new ControlGroupContainer(reduxEmbeddablePackage, controlGroupInput);
57+
58+
// data view not required for test case
59+
// setInitializationFinished is called before fetching slider range when value is not provided
60+
injectStorybookDataView(undefined);
61+
62+
const control = await container.addRangeSliderControl({
63+
dataViewId: 'demoDataFlights',
64+
fieldName: 'AvgTicketPrice',
65+
});
66+
67+
expect(container.getInput().panels[control.getInput().id].type).toBe(RANGE_SLIDER_CONTROL);
68+
expect(container.getOutput().embeddableLoaded[control.getInput().id]).toBe(true);
69+
});
70+
});
71+
72+
describe('with selected range', () => {
73+
test('should set error message when data view can not be found', async () => {
74+
const reduxEmbeddablePackage = await lazyLoadReduxToolsPackage();
75+
const controlGroupInput = { chainingSystem: 'NONE', panels: {} } as ControlGroupInput;
76+
const container = new ControlGroupContainer(reduxEmbeddablePackage, controlGroupInput);
77+
78+
injectStorybookDataView(undefined);
79+
80+
const control = (await container.addRangeSliderControl({
81+
dataViewId: 'demoDataFlights',
82+
fieldName: 'AvgTicketPrice',
83+
value: ['150', '300'],
84+
})) as RangeSliderEmbeddable;
85+
86+
// await redux dispatch
87+
await new Promise((resolve) => process.nextTick(resolve));
88+
89+
const reduxState = control.getState();
90+
expect(reduxState.output.loading).toBe(false);
91+
expect(reduxState.componentState.error).toBe(
92+
'mock DataViews service currentDataView is undefined, call injectStorybookDataView to set'
93+
);
94+
});
95+
96+
test('should set error message when field can not be found', async () => {
97+
const reduxEmbeddablePackage = await lazyLoadReduxToolsPackage();
98+
const controlGroupInput = { chainingSystem: 'NONE', panels: {} } as ControlGroupInput;
99+
const container = new ControlGroupContainer(reduxEmbeddablePackage, controlGroupInput);
100+
101+
injectStorybookDataView(storybookFlightsDataView);
102+
103+
const control = (await container.addRangeSliderControl({
104+
dataViewId: 'demoDataFlights',
105+
fieldName: 'myField',
106+
value: ['150', '300'],
107+
})) as RangeSliderEmbeddable;
108+
109+
// await redux dispatch
110+
await new Promise((resolve) => process.nextTick(resolve));
111+
112+
const reduxState = control.getState();
113+
expect(reduxState.output.loading).toBe(false);
114+
expect(reduxState.componentState.error).toBe('Could not locate field: myField');
115+
});
116+
117+
test('should set invalid state when filter returns zero results', async () => {
118+
const reduxEmbeddablePackage = await lazyLoadReduxToolsPackage();
119+
const controlGroupInput = { chainingSystem: 'NONE', panels: {} } as ControlGroupInput;
120+
const container = new ControlGroupContainer(reduxEmbeddablePackage, controlGroupInput);
121+
122+
injectStorybookDataView(storybookFlightsDataView);
123+
totalResults = 0;
124+
125+
const control = (await container.addRangeSliderControl({
126+
dataViewId: 'demoDataFlights',
127+
fieldName: 'AvgTicketPrice',
128+
value: ['150', '300'],
129+
})) as RangeSliderEmbeddable;
130+
131+
// await redux dispatch
132+
await new Promise((resolve) => process.nextTick(resolve));
133+
134+
const reduxState = control.getState();
135+
expect(reduxState.output.filters?.length).toBe(0);
136+
expect(reduxState.componentState.isInvalid).toBe(true);
137+
});
138+
139+
test('should set range and filter', async () => {
140+
const reduxEmbeddablePackage = await lazyLoadReduxToolsPackage();
141+
const controlGroupInput = { chainingSystem: 'NONE', panels: {} } as ControlGroupInput;
142+
const container = new ControlGroupContainer(reduxEmbeddablePackage, controlGroupInput);
143+
144+
injectStorybookDataView(storybookFlightsDataView);
145+
146+
const control = (await container.addRangeSliderControl({
147+
dataViewId: 'demoDataFlights',
148+
fieldName: 'AvgTicketPrice',
149+
value: ['150', '300'],
150+
})) as RangeSliderEmbeddable;
151+
152+
// await redux dispatch
153+
await new Promise((resolve) => process.nextTick(resolve));
154+
155+
const reduxState = control.getState();
156+
expect(reduxState.output.filters?.length).toBe(1);
157+
expect(reduxState.output.filters?.[0].query).toEqual({
158+
range: {
159+
AvgTicketPrice: {
160+
gte: 150,
161+
lte: 300,
162+
},
163+
},
164+
});
165+
expect(reduxState.componentState.isInvalid).toBe(false);
166+
expect(reduxState.componentState.min).toBe(0);
167+
expect(reduxState.componentState.max).toBe(1000);
168+
});
169+
170+
test('should notify control group when initialization is finished', async () => {
171+
const reduxEmbeddablePackage = await lazyLoadReduxToolsPackage();
172+
const controlGroupInput = { chainingSystem: 'NONE', panels: {} } as ControlGroupInput;
173+
const container = new ControlGroupContainer(reduxEmbeddablePackage, controlGroupInput);
174+
175+
injectStorybookDataView(storybookFlightsDataView);
176+
177+
const control = await container.addRangeSliderControl({
178+
dataViewId: 'demoDataFlights',
179+
fieldName: 'AvgTicketPrice',
180+
value: ['150', '300'],
181+
});
182+
183+
expect(container.getInput().panels[control.getInput().id].type).toBe(RANGE_SLIDER_CONTROL);
184+
expect(container.getOutput().embeddableLoaded[control.getInput().id]).toBe(true);
185+
});
186+
187+
test('should notify control group when initialization throws', async () => {
188+
const reduxEmbeddablePackage = await lazyLoadReduxToolsPackage();
189+
const controlGroupInput = { chainingSystem: 'NONE', panels: {} } as ControlGroupInput;
190+
const container = new ControlGroupContainer(reduxEmbeddablePackage, controlGroupInput);
191+
192+
injectStorybookDataView(storybookFlightsDataView);
193+
194+
pluginServices.getServices().data.searchSource.create = jest.fn().mockImplementation(() => ({
195+
setField: () => {},
196+
fetch$: () => {
197+
throw new Error('Simulated _search request error');
198+
},
199+
}));
200+
201+
const control = await container.addRangeSliderControl({
202+
dataViewId: 'demoDataFlights',
203+
fieldName: 'AvgTicketPrice',
204+
value: ['150', '300'],
205+
});
206+
207+
expect(container.getInput().panels[control.getInput().id].type).toBe(RANGE_SLIDER_CONTROL);
208+
expect(container.getOutput().embeddableLoaded[control.getInput().id]).toBe(true);
209+
});
210+
});
211+
});

0 commit comments

Comments
 (0)