diff --git a/src/plugins/controls/public/react_controls/control_group/components/control_group_editor.test.tsx b/src/plugins/controls/public/react_controls/control_group/components/control_group_editor.test.tsx new file mode 100644 index 0000000000000..99feed42d0857 --- /dev/null +++ b/src/plugins/controls/public/react_controls/control_group/components/control_group_editor.test.tsx @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { BehaviorSubject } from 'rxjs'; +import { render } from '@testing-library/react'; +import { ControlGroupEditor } from './control_group_editor'; +import { ControlGroupApi, ControlStyle, ParentIgnoreSettings } from '../../..'; +import { ControlGroupChainingSystem, DEFAULT_CONTROL_STYLE } from '../../../../common'; +import { DefaultControlApi } from '../../controls/types'; + +describe('render', () => { + const children$ = new BehaviorSubject<{ [key: string]: DefaultControlApi }>({}); + const props = { + api: { + children$, + } as unknown as ControlGroupApi, + onCancel: () => {}, + onSave: () => {}, + onDeleteAll: () => {}, + stateManager: { + chainingSystem: new BehaviorSubject('HIERARCHICAL'), + labelPosition: new BehaviorSubject(DEFAULT_CONTROL_STYLE), + autoApplySelections: new BehaviorSubject(true), + ignoreParentSettings: new BehaviorSubject(undefined), + }, + }; + + beforeEach(() => { + children$.next({}); + }); + + test('should not display delete all controls button when there are no controls', () => { + const editor = render(); + expect(editor.queryByTestId('delete-all-controls-button')).not.toBeInTheDocument(); + }); + + test('should display delete all controls button when there are controls', () => { + children$.next({ + alpha: {} as unknown as DefaultControlApi, + }); + const editor = render(); + expect(editor.queryByTestId('delete-all-controls-button')).toBeInTheDocument(); + }); +}); diff --git a/src/plugins/controls/public/react_controls/control_group/control_group_editor.tsx b/src/plugins/controls/public/react_controls/control_group/components/control_group_editor.tsx similarity index 95% rename from src/plugins/controls/public/react_controls/control_group/control_group_editor.tsx rename to src/plugins/controls/public/react_controls/control_group/components/control_group_editor.tsx index 9c8ca5b52b0ae..a65c8c1ac5bba 100644 --- a/src/plugins/controls/public/react_controls/control_group/control_group_editor.tsx +++ b/src/plugins/controls/public/react_controls/control_group/components/control_group_editor.tsx @@ -27,11 +27,11 @@ import { } from '@elastic/eui'; import { css } from '@emotion/react'; import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; -import { ControlStyle, ParentIgnoreSettings } from '../..'; +import { ControlStyle, ParentIgnoreSettings } from '../../..'; -import { ControlStateManager } from '../controls/types'; -import { ControlGroupStrings } from './control_group_strings'; -import { ControlGroupApi, ControlGroupEditorState } from './types'; +import { ControlStateManager } from '../../controls/types'; +import { ControlGroupStrings } from '../control_group_strings'; +import { ControlGroupApi, ControlGroupEditorState } from '../types'; const CONTROL_LAYOUT_OPTIONS = [ { @@ -46,7 +46,7 @@ const CONTROL_LAYOUT_OPTIONS = [ }, ]; -interface EditControlGroupProps { +interface Props { onCancel: () => void; onSave: () => void; onDeleteAll: () => void; @@ -54,13 +54,7 @@ interface EditControlGroupProps { api: ControlGroupApi; // controls must always have a parent API } -export const ControlGroupEditor = ({ - onCancel, - onSave, - onDeleteAll, - stateManager, - api, -}: EditControlGroupProps) => { +export const ControlGroupEditor = ({ onCancel, onSave, onDeleteAll, stateManager, api }: Props) => { const [ children, selectedLabelPosition, diff --git a/src/plugins/controls/public/react_controls/control_group/components/control_panel.test.tsx b/src/plugins/controls/public/react_controls/control_group/components/control_panel.test.tsx new file mode 100644 index 0000000000000..34d3f344b1a81 --- /dev/null +++ b/src/plugins/controls/public/react_controls/control_group/components/control_panel.test.tsx @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useImperativeHandle } from 'react'; +import { BehaviorSubject } from 'rxjs'; +import { render, waitFor } from '@testing-library/react'; +import { ControlPanel } from './control_panel'; +import { registry as presentationUtilServicesRegistry } from '@kbn/presentation-util-plugin/public/services/plugin_services.story'; +import { pluginServices as presentationUtilPluginServices } from '@kbn/presentation-util-plugin/public/services'; +import { ControlStyle, ControlWidth } from '../../..'; + +describe('render', () => { + let mockApi = {}; + const Component = React.forwardRef((_, ref) => { + // expose the api into the imperative handle + useImperativeHandle(ref, () => mockApi, []); + + return
; + }) as any; + + beforeAll(() => { + presentationUtilServicesRegistry.start({}); + presentationUtilPluginServices.setRegistry(presentationUtilServicesRegistry); + presentationUtilPluginServices.getServices().uiActions.getTriggerCompatibleActions = jest + .fn() + .mockImplementation(() => { + return [ + { + isCompatible: jest.fn().mockResolvedValue(true), + id: 'testAction', + MenuItem: () =>
test1
, + }, + ]; + }); + }); + + beforeEach(() => { + mockApi = {}; + }); + + describe('control width', () => { + test('defaults to medium and grow enabled', async () => { + const controlPanel = render(); + await waitFor(() => { + const controlFrame = controlPanel.getByTestId('control-frame'); + expect(controlFrame.getAttribute('class')).toContain('controlFrameWrapper--medium'); + expect(controlFrame.getAttribute('class')).toContain('controlFrameWrapper--grow'); + }); + }); + + test('should use small class when using small width', async () => { + mockApi = { + uuid: 'control1', + width: new BehaviorSubject('small'), + }; + const controlPanel = render(); + await waitFor(() => { + const controlFrame = controlPanel.getByTestId('control-frame'); + expect(controlFrame.getAttribute('class')).toContain('controlFrameWrapper--small'); + }); + }); + }); + + describe('label position', () => { + test('should use one line layout class when using one line layout', async () => { + mockApi = { + uuid: 'control1', + parentApi: { + labelPosition: new BehaviorSubject('oneLine'), + }, + }; + const controlPanel = render(); + await waitFor(() => { + const floatingActions = controlPanel.getByTestId( + 'presentationUtil__floatingActions__control1' + ); + expect(floatingActions.getAttribute('class')).toContain( + 'controlFrameFloatingActions--oneLine' + ); + }); + }); + + test('should use two line layout class when using two line layout', async () => { + mockApi = { + uuid: 'control1', + parentApi: { + labelPosition: new BehaviorSubject('twoLine'), + }, + }; + const controlPanel = render(); + await waitFor(() => { + const floatingActions = controlPanel.getByTestId( + 'presentationUtil__floatingActions__control1' + ); + expect(floatingActions.getAttribute('class')).toContain( + 'controlFrameFloatingActions--twoLine' + ); + }); + }); + }); +}); diff --git a/src/plugins/controls/public/react_controls/control_group/components/control_panel.tsx b/src/plugins/controls/public/react_controls/control_group/components/control_panel.tsx index b83b0b464b5c5..9ed24904f25cd 100644 --- a/src/plugins/controls/public/react_controls/control_group/components/control_panel.tsx +++ b/src/plugins/controls/public/react_controls/control_group/components/control_panel.tsx @@ -27,7 +27,7 @@ import { useBatchedOptionalPublishingSubjects, } from '@kbn/presentation-publishing'; import { FloatingActions } from '@kbn/presentation-util-plugin/public'; -import { DEFAULT_CONTROL_WIDTH } from '../../../../common'; +import { DEFAULT_CONTROL_GROW, DEFAULT_CONTROL_WIDTH } from '../../../../common'; import { ControlPanelProps, DefaultControlApi } from '../../controls/types'; import { ControlError } from './control_error'; @@ -121,17 +121,18 @@ export const ControlPanel = { before(async () => { - await dashboard.navigateToApp(); - await dashboard.gotoDashboardLandingPage(); - await dashboard.clickNewDashboard(); - await timePicker.setDefaultDataRange(); - await dashboard.saveDashboard('Test Control Group Settings'); - }); - - it('adjust layout of controls', async () => { + await dashboard.loadSavedDashboard('control group settings test dashboard'); await dashboard.switchToEditMode(); - await dashboardControls.createControl({ - controlType: OPTIONS_LIST_CONTROL, - dataViewTitle: 'animals-*', - fieldName: 'sound.keyword', - }); - await dashboardControls.adjustControlsLayout('twoLine'); - const controlGroupWrapper = await testSubjects.find('controls-group-wrapper'); - expect(await controlGroupWrapper.elementHasClass('controlsWrapper--twoLine')).to.be(true); - }); - - describe('apply new default width and grow', async () => { - it('defaults to medium width and grow enabled', async () => { - await dashboardControls.openCreateControlFlyout(); - const mediumWidthButton = await testSubjects.find('control-editor-width-medium'); - expect(await mediumWidthButton.elementHasClass('euiButtonGroupButton-isSelected')).to.be( - true - ); - const growSwitch = await testSubjects.find('control-editor-grow-switch'); - expect(await growSwitch.getAttribute('aria-checked')).to.be('true'); - await testSubjects.click('control-editor-cancel'); - }); - - it('sets default to width and grow of last created control', async () => { - await dashboardControls.createControl({ - controlType: OPTIONS_LIST_CONTROL, - dataViewTitle: 'animals-*', - fieldName: 'name.keyword', - width: 'small', - grow: false, - }); - - const controlIds = await dashboardControls.getAllControlIds(); - const firstControl = await find.byXPath(`//div[@data-control-id="${controlIds[0]}"]`); - expect(await firstControl.elementHasClass('controlFrameWrapper--medium')).to.be(true); - expect(await firstControl.getAttribute('class')).not.to.contain('euiFlexItem-growZero'); - const secondControl = await find.byXPath(`//div[@data-control-id="${controlIds[1]}"]`); - expect(await secondControl.elementHasClass('controlFrameWrapper--small')).to.be(true); - expect(await secondControl.getAttribute('class')).to.contain('euiFlexItem-growZero'); - - await dashboardControls.openCreateControlFlyout(); - const smallWidthButton = await testSubjects.find('control-editor-width-small'); - expect(await smallWidthButton.elementHasClass('euiButtonGroupButton-isSelected')).to.be( - true - ); - const growSwitch = await testSubjects.find('control-editor-grow-switch'); - expect(await growSwitch.getAttribute('aria-checked')).to.be('false'); - await testSubjects.click('control-editor-cancel'); - }); }); describe('filtering settings', async () => { - let firstOptionsListId: string; + const firstOptionsListId = 'bcb81550-0843-44ea-9020-6c1ebf3228ac'; let beforeCount: number; - let rangeSliderId: string; + const rangeSliderId = '15925456-9e12-4b08-b2e6-4ae6ac27114d'; let beforeRange: number; const getRange = async () => { @@ -106,19 +48,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }; before(async () => { - await dashboardControls.createControl({ - controlType: RANGE_SLIDER_CONTROL, - dataViewTitle: 'animals-*', - fieldName: 'weightLbs', - }); - await dashboard.clickQuickSave(); - - firstOptionsListId = (await dashboardControls.getAllControlIds())[0]; await dashboardControls.optionsListWaitForLoading(firstOptionsListId); await dashboardControls.optionsListOpenPopover(firstOptionsListId); beforeCount = await dashboardControls.optionsListPopoverGetAvailableOptionsCount(); - rangeSliderId = (await dashboardControls.getAllControlIds())[2]; beforeRange = await getRange(); }); @@ -172,65 +105,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe('flyout only show settings that are relevant', async () => { - before(async () => { - await dashboard.switchToEditMode(); - }); - - it('when no controls', async () => { - await dashboardControls.deleteAllControls(); - await dashboardControls.openControlGroupSettingsFlyout(); - await testSubjects.missingOrFail('delete-all-controls-button'); - }); - - it('when at least one control', async () => { - await dashboardControls.createControl({ - controlType: OPTIONS_LIST_CONTROL, - dataViewTitle: 'animals-*', - fieldName: 'sound.keyword', - }); - await dashboardControls.openControlGroupSettingsFlyout(); - await testSubjects.existOrFail('delete-all-controls-button'); - }); - - afterEach(async () => { - await testSubjects.click('euiFlyoutCloseButton'); - if (await testSubjects.exists('confirmModalConfirmButton')) { - await testSubjects.click('confirmModalConfirmButton'); - } - }); - - after(async () => { - await dashboardControls.deleteAllControls(); - }); - }); - describe('control group settings flyout closes', async () => { - it('on save', async () => { - await dashboardControls.openControlGroupSettingsFlyout(); - await dashboard.saveDashboard('Test Control Group Settings', { - saveAsNew: false, - exitFromEditMode: false, - }); - await testSubjects.missingOrFail('control-group-settings-flyout'); - }); - - it('on view mode change', async () => { - await dashboardControls.openControlGroupSettingsFlyout(); - await dashboard.clickCancelOutOfEditMode(); - await testSubjects.missingOrFail('control-group-settings-flyout'); - }); - it('when navigating away from dashboard', async () => { await dashboard.switchToEditMode(); await dashboardControls.openControlGroupSettingsFlyout(); await dashboard.gotoDashboardLandingPage(); await testSubjects.missingOrFail('control-group-settings-flyout'); }); - - after(async () => { - await dashboard.loadSavedDashboard('Test Control Group Settings'); - }); }); }); } diff --git a/test/functional/apps/dashboard_elements/controls/common/index.ts b/test/functional/apps/dashboard_elements/controls/common/index.ts index c5fb1621e61f3..58c9a3dcb80fe 100644 --- a/test/functional/apps/dashboard_elements/controls/common/index.ts +++ b/test/functional/apps/dashboard_elements/controls/common/index.ts @@ -26,7 +26,6 @@ export default function ({ loadTestFile, getService, getPageObjects }: FtrProvid defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - // enable the controls lab and navigate to the dashboard listing page to start await dashboard.navigateToApp(); await dashboard.preserveCrossAppState(); } diff --git a/test/functional/fixtures/kbn_archiver/dashboard/current/kibana.json b/test/functional/fixtures/kbn_archiver/dashboard/current/kibana.json index 500443f11900a..a89dcf714dfc3 100644 --- a/test/functional/fixtures/kbn_archiver/dashboard/current/kibana.json +++ b/test/functional/fixtures/kbn_archiver/dashboard/current/kibana.json @@ -3175,3 +3175,53 @@ "coreMigrationVersion": "8.8.0", "typeMigrationVersion": "8.9.0" } + +{ + "id": "153e3302-1b37-4c45-9b11-91deec40ab47", + "type": "dashboard", + "namespaces": [ + "default" + ], + "updated_at": "2024-08-21T20:48:22.388Z", + "created_at": "2024-08-21T20:46:21.250Z", + "version": "WzQ4MCwxXQ==", + "attributes": { + "version": 2, + "controlGroupInput": { + "controlStyle": "oneLine", + "chainingSystem": "HIERARCHICAL", + "showApplySelections": false, + "panelsJSON": "{\"bcb81550-0843-44ea-9020-6c1ebf3228ac\":{\"type\":\"optionsListControl\",\"order\":0,\"grow\":true,\"width\":\"medium\",\"explicitInput\":{\"id\":\"bcb81550-0843-44ea-9020-6c1ebf3228ac\",\"fieldName\":\"sound.keyword\",\"title\":\"sound.keyword\",\"grow\":true,\"width\":\"medium\",\"searchTechnique\":\"prefix\",\"enhancements\":{}}},\"15925456-9e12-4b08-b2e6-4ae6ac27114d\":{\"type\":\"rangeSliderControl\",\"order\":1,\"grow\":true,\"width\":\"medium\",\"explicitInput\":{\"fieldName\":\"weightLbs\",\"title\":\"weightLbs\",\"searchTechnique\":\"exact\",\"id\":\"15925456-9e12-4b08-b2e6-4ae6ac27114d\",\"enhancements\":{}}}}", + "ignoreParentSettingsJSON": "{\"ignoreFilters\":false,\"ignoreQuery\":false,\"ignoreTimerange\":false,\"ignoreValidations\":false}" + }, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}" + }, + "description": "", + "refreshInterval": { + "pause": true, + "value": 60000 + }, + "timeRestore": true, + "optionsJSON": "{\"useMargins\":true,\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"hidePanelTitles\":false}", + "panelsJSON": "[]", + "timeFrom": "2018-01-01T00:00:00.000Z", + "title": "control group settings test dashboard", + "timeTo": "2018-04-13T00:00:00.000Z" + }, + "references": [ + { + "name": "controlGroup_bcb81550-0843-44ea-9020-6c1ebf3228ac:optionsListDataView", + "type": "index-pattern", + "id": "a0f483a0-3dc9-11e8-8660-4d65aa086b3c" + }, + { + "name": "controlGroup_15925456-9e12-4b08-b2e6-4ae6ac27114d:rangeSliderDataView", + "type": "index-pattern", + "id": "a0f483a0-3dc9-11e8-8660-4d65aa086b3c" + } + ], + "managed": false, + "coreMigrationVersion": "8.8.0", + "typeMigrationVersion": "10.2.0" +}