mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Discover] Dismiss flyouts when opening another one (#193865)
- Closes https://github.com/elastic/kibana/issues/193452 ## Summary This PR makes sure that only one flyout is open at a time and automatically dismisses all others. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
This commit is contained in:
parent
54659e8ae0
commit
6fc017a597
15 changed files with 230 additions and 11 deletions
|
@ -56,6 +56,9 @@ export {
|
|||
getFieldValue,
|
||||
getVisibleColumns,
|
||||
canPrependTimeFieldColumn,
|
||||
DiscoverFlyouts,
|
||||
dismissAllFlyoutsExceptFor,
|
||||
dismissFlyouts,
|
||||
} from './src';
|
||||
|
||||
export type { LogsContextService } from './src';
|
||||
|
|
48
packages/kbn-discover-utils/src/utils/dismiss_flyouts.ts
Normal file
48
packages/kbn-discover-utils/src/utils/dismiss_flyouts.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export enum DiscoverFlyouts {
|
||||
lensEdit = 'lensEdit',
|
||||
docViewer = 'docViewer',
|
||||
esqlDocs = 'esqlDocs',
|
||||
}
|
||||
|
||||
const AllDiscoverFlyouts = Object.values(DiscoverFlyouts);
|
||||
|
||||
const getFlyoutCloseButton = (flyout: DiscoverFlyouts): HTMLElement | null => {
|
||||
switch (flyout) {
|
||||
case DiscoverFlyouts.lensEdit:
|
||||
return document.getElementById('lnsCancelEditOnFlyFlyout');
|
||||
case DiscoverFlyouts.docViewer:
|
||||
return document.querySelector('[data-test-subj="docViewerFlyoutCloseButton"]');
|
||||
case DiscoverFlyouts.esqlDocs:
|
||||
return document.querySelector(
|
||||
'[data-test-subj="esqlInlineDocumentationFlyout"] [data-test-subj="euiFlyoutCloseButton"]'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const dismissFlyouts = (
|
||||
selectedFlyouts: DiscoverFlyouts[] = AllDiscoverFlyouts,
|
||||
excludedFlyout?: DiscoverFlyouts
|
||||
) => {
|
||||
selectedFlyouts.forEach((flyout) => {
|
||||
if (flyout === excludedFlyout) {
|
||||
return;
|
||||
}
|
||||
const closeButton = getFlyoutCloseButton(flyout);
|
||||
if (closeButton) {
|
||||
closeButton.click?.();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const dismissAllFlyoutsExceptFor = (excludedFlyout: DiscoverFlyouts) => {
|
||||
dismissFlyouts(AllDiscoverFlyouts, excludedFlyout);
|
||||
};
|
|
@ -22,3 +22,4 @@ export * from './get_field_value';
|
|||
export * from './calc_field_counts';
|
||||
export * from './get_visible_columns';
|
||||
export { isLegacyTableEnabled } from './is_legacy_table_enabled';
|
||||
export { DiscoverFlyouts, dismissAllFlyoutsExceptFor, dismissFlyouts } from './dismiss_flyouts';
|
||||
|
|
|
@ -78,6 +78,7 @@ function DocumentationFlyout({
|
|||
ownFocus
|
||||
onClose={() => onHelpMenuVisibilityChange(false)}
|
||||
aria-labelledby="esqlInlineDocumentationFlyout"
|
||||
data-test-subj="esqlInlineDocumentationFlyout"
|
||||
type="push"
|
||||
size={DEFAULT_WIDTH}
|
||||
paddingSize="m"
|
||||
|
|
|
@ -12,6 +12,7 @@ import { type DataView, DataViewType } from '@kbn/data-views-plugin/public';
|
|||
import { DataViewPickerProps } from '@kbn/unified-search-plugin/public';
|
||||
import { ENABLE_ESQL } from '@kbn/esql-utils';
|
||||
import { TextBasedLanguages } from '@kbn/esql-utils';
|
||||
import { DiscoverFlyouts, dismissAllFlyoutsExceptFor } from '@kbn/discover-utils';
|
||||
import { useSavedSearchInitial } from '../../state_management/discover_state_provider';
|
||||
import { ESQL_TRANSITION_MODAL_KEY } from '../../../../../common/constants';
|
||||
import { useInternalStateSelector } from '../../state_management/discover_internal_state_container';
|
||||
|
@ -233,6 +234,12 @@ export const DiscoverTopNav = ({
|
|||
uiSettings,
|
||||
]);
|
||||
|
||||
const onESQLDocsFlyoutVisibilityChanged = useCallback((isOpen: boolean) => {
|
||||
if (isOpen) {
|
||||
dismissAllFlyoutsExceptFor(DiscoverFlyouts.esqlDocs);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const searchBarCustomization = useDiscoverCustomization('search_bar');
|
||||
|
||||
const SearchBar = useMemo(
|
||||
|
@ -278,6 +285,7 @@ export const DiscoverTopNav = ({
|
|||
<searchBarCustomization.PrependFilterBar />
|
||||
) : undefined
|
||||
}
|
||||
onESQLDocsFlyoutVisibilityChanged={onESQLDocsFlyoutVisibilityChanged}
|
||||
/>
|
||||
{isESQLToDataViewTransitionModalVisible && (
|
||||
<ESQLToDataViewTransitionModal onClose={onESQLToDataViewTransitionModalClose} />
|
||||
|
|
|
@ -11,6 +11,7 @@ import type { TopNavMenuBadgeProps } from '@kbn/navigation-plugin/public';
|
|||
import { getTopNavUnsavedChangesBadge } from '@kbn/unsaved-changes-badge';
|
||||
import { getManagedContentBadge } from '@kbn/managed-content-badge';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { dismissFlyouts, DiscoverFlyouts } from '@kbn/discover-utils';
|
||||
import { DiscoverStateContainer } from '../../state_management/discover_state';
|
||||
import type { TopNavCustomization } from '../../../../customizations';
|
||||
import { onSaveSearch } from './on_save_search';
|
||||
|
@ -47,10 +48,7 @@ export const getTopNavBadges = ({
|
|||
entries.push({
|
||||
data: getTopNavUnsavedChangesBadge({
|
||||
onRevert: async () => {
|
||||
const lensEditFlyoutCancelButton = document.getElementById('lnsCancelEditOnFlyFlyout');
|
||||
if (lensEditFlyoutCancelButton) {
|
||||
lensEditFlyoutCancelButton.click?.();
|
||||
}
|
||||
dismissFlyouts([DiscoverFlyouts.lensEdit]);
|
||||
await stateContainer.actions.undoSavedSearchChanges();
|
||||
},
|
||||
onSave:
|
||||
|
|
|
@ -7,13 +7,14 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { Filter, Query, AggregateQuery, isOfAggregateQueryType } from '@kbn/es-query';
|
||||
import { AggregateQuery, Filter, isOfAggregateQueryType, Query } from '@kbn/es-query';
|
||||
import type { DataTableRecord } from '@kbn/discover-utils/types';
|
||||
import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
|
||||
import type { DataTableColumnsMeta } from '@kbn/unified-data-table';
|
||||
import type { DocViewsRegistry } from '@kbn/unified-doc-viewer';
|
||||
import { DiscoverFlyouts, dismissAllFlyoutsExceptFor } from '@kbn/discover-utils';
|
||||
import { UnifiedDocViewerFlyout } from '@kbn/unified-doc-viewer-plugin/public';
|
||||
import { useDiscoverServices } from '../../hooks/use_discover_services';
|
||||
import { useFlyoutActions } from './use_flyout_actions';
|
||||
|
@ -88,6 +89,10 @@ export function DiscoverGridFlyout({
|
|||
return getDocViewer({ record: actualHit });
|
||||
}, [flyoutCustomization, getDocViewerAccessor, actualHit]);
|
||||
|
||||
useEffect(() => {
|
||||
dismissAllFlyoutsExceptFor(DiscoverFlyouts.docViewer);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<UnifiedDocViewerFlyout
|
||||
flyoutTitle={docViewer.title}
|
||||
|
|
|
@ -14,6 +14,7 @@ import { isEqual, isObject } from 'lodash';
|
|||
import type { LensEmbeddableOutput, Suggestion } from '@kbn/lens-plugin/public';
|
||||
import type { Datatable } from '@kbn/expressions-plugin/common';
|
||||
import { EditLensConfigPanelComponent } from '@kbn/lens-plugin/public/plugin';
|
||||
import { DiscoverFlyouts, dismissAllFlyoutsExceptFor } from '@kbn/discover-utils';
|
||||
import { deriveLensSuggestionFromLensAttributes } from '../utils/external_vis_context';
|
||||
|
||||
import {
|
||||
|
@ -144,5 +145,13 @@ export function ChartConfigPanel({
|
|||
currentSuggestionType,
|
||||
]);
|
||||
|
||||
return isPlainRecord ? editLensConfigPanel : null;
|
||||
const flyoutElement = isPlainRecord ? editLensConfigPanel : null;
|
||||
|
||||
useEffect(() => {
|
||||
if (flyoutElement) {
|
||||
dismissAllFlyoutsExceptFor(DiscoverFlyouts.lensEdit);
|
||||
}
|
||||
}, [flyoutElement]);
|
||||
|
||||
return flyoutElement;
|
||||
}
|
||||
|
|
|
@ -22,7 +22,13 @@ import { FEEDBACK_LINK } from '@kbn/esql-utils';
|
|||
import { LanguageDocumentationFlyout } from '@kbn/language-documentation';
|
||||
import type { IUnifiedSearchPluginServices } from '../types';
|
||||
|
||||
export const ESQLMenuPopover = () => {
|
||||
export interface ESQLMenuPopoverProps {
|
||||
onESQLDocsFlyoutVisibilityChanged?: (isOpen: boolean) => void;
|
||||
}
|
||||
|
||||
export const ESQLMenuPopover: React.FC<ESQLMenuPopoverProps> = ({
|
||||
onESQLDocsFlyoutVisibilityChanged,
|
||||
}) => {
|
||||
const kibana = useKibana<IUnifiedSearchPluginServices>();
|
||||
|
||||
const { docLinks } = kibana.services;
|
||||
|
@ -34,6 +40,14 @@ export const ESQLMenuPopover = () => {
|
|||
setIsESQLMenuPopoverOpen(false);
|
||||
}, [isLanguageComponentOpen]);
|
||||
|
||||
const onHelpMenuVisibilityChange = useCallback(
|
||||
(status: boolean) => {
|
||||
setIsLanguageComponentOpen(status);
|
||||
onESQLDocsFlyoutVisibilityChanged?.(status);
|
||||
},
|
||||
[setIsLanguageComponentOpen, onESQLDocsFlyoutVisibilityChanged]
|
||||
);
|
||||
|
||||
const esqlPanelItems = useMemo(() => {
|
||||
const panelItems: EuiContextMenuPanelProps['items'] = [];
|
||||
panelItems.push(
|
||||
|
@ -122,7 +136,7 @@ export const ESQLMenuPopover = () => {
|
|||
searchInDescription
|
||||
linkToDocumentation={docLinks?.links?.query?.queryESQL ?? ''}
|
||||
isHelpMenuOpen={isLanguageComponentOpen}
|
||||
onHelpMenuVisibilityChange={setIsLanguageComponentOpen}
|
||||
onHelpMenuVisibilityChange={onHelpMenuVisibilityChange}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -51,7 +51,7 @@ import { NoDataPopover } from './no_data_popover';
|
|||
import { shallowEqual } from '../utils/shallow_equal';
|
||||
import { AddFilterPopover } from './add_filter_popover';
|
||||
import { DataViewPicker, DataViewPickerProps } from '../dataview_picker';
|
||||
import { ESQLMenuPopover } from './esql_menu_popover';
|
||||
import { ESQLMenuPopover, type ESQLMenuPopoverProps } from './esql_menu_popover';
|
||||
|
||||
import { FilterButtonGroup } from '../filter_bar/filter_button_group/filter_button_group';
|
||||
import type {
|
||||
|
@ -186,6 +186,7 @@ export interface QueryBarTopRowProps<QT extends Query | AggregateQuery = Query>
|
|||
submitOnBlur?: boolean;
|
||||
renderQueryInputAppend?: () => React.ReactNode;
|
||||
disableExternalPadding?: boolean;
|
||||
onESQLDocsFlyoutVisibilityChanged?: ESQLMenuPopoverProps['onESQLDocsFlyoutVisibilityChanged'];
|
||||
}
|
||||
|
||||
export const SharingMetaFields = React.memo(function SharingMetaFields({
|
||||
|
@ -774,7 +775,11 @@ export const QueryBarTopRow = React.memo(
|
|||
wrap
|
||||
>
|
||||
{props.dataViewPickerOverride || renderDataViewsPicker()}
|
||||
{Boolean(isQueryLangSelected) && <ESQLMenuPopover />}
|
||||
{Boolean(isQueryLangSelected) && (
|
||||
<ESQLMenuPopover
|
||||
onESQLDocsFlyoutVisibilityChanged={props.onESQLDocsFlyoutVisibilityChanged}
|
||||
/>
|
||||
)}
|
||||
<EuiFlexItem
|
||||
grow={!shouldShowDatePickerAsBadge()}
|
||||
style={{ minWidth: shouldShowDatePickerAsBadge() ? 'auto' : 320, maxWidth: '100%' }}
|
||||
|
|
|
@ -268,6 +268,7 @@ export function createSearchBar({
|
|||
dataTestSubj={props.dataTestSubj}
|
||||
filtersForSuggestions={props.filtersForSuggestions}
|
||||
prependFilterBar={props.prependFilterBar}
|
||||
onESQLDocsFlyoutVisibilityChanged={props.onESQLDocsFlyoutVisibilityChanged}
|
||||
/>
|
||||
</core.i18n.Context>
|
||||
</KibanaContextProvider>
|
||||
|
|
|
@ -138,6 +138,7 @@ export interface SearchBarOwnProps<QT extends AggregateQuery | Query = Query> {
|
|||
submitOnBlur?: boolean;
|
||||
|
||||
renderQueryInputAppend?: () => React.ReactNode;
|
||||
onESQLDocsFlyoutVisibilityChanged?: QueryBarTopRowProps['onESQLDocsFlyoutVisibilityChanged'];
|
||||
}
|
||||
|
||||
export type SearchBarProps<QT extends Query | AggregateQuery = Query> = SearchBarOwnProps<QT> &
|
||||
|
@ -660,6 +661,7 @@ class SearchBarUI<QT extends (Query | AggregateQuery) | Query = Query> extends C
|
|||
suggestionsAbstraction={this.props.suggestionsAbstraction}
|
||||
renderQueryInputAppend={this.props.renderQueryInputAppend}
|
||||
disableExternalPadding={this.props.displayStyle === 'withBorders'}
|
||||
onESQLDocsFlyoutVisibilityChanged={this.props.onESQLDocsFlyoutVisibilityChanged}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
104
test/functional/apps/discover/group8/_flyouts.ts
Normal file
104
test/functional/apps/discover/group8/_flyouts.ts
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const { common, discover, timePicker, header } = getPageObjects([
|
||||
'common',
|
||||
'discover',
|
||||
'timePicker',
|
||||
'header',
|
||||
]);
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const security = getService('security');
|
||||
const retry = getService('retry');
|
||||
const dataGrid = getService('dataGrid');
|
||||
const esql = getService('esql');
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
describe('discover flyouts', function () {
|
||||
async function isLensEditFlyoutOpen() {
|
||||
return await testSubjects.exists('lnsChartSwitchPopover');
|
||||
}
|
||||
|
||||
async function openLensEditFlyout() {
|
||||
await testSubjects.click('unifiedHistogramEditFlyoutVisualization');
|
||||
await retry.waitFor('flyout', async () => {
|
||||
return await isLensEditFlyoutOpen();
|
||||
});
|
||||
}
|
||||
|
||||
before(async function () {
|
||||
await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader']);
|
||||
await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover');
|
||||
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional');
|
||||
await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' });
|
||||
await timePicker.setDefaultAbsoluteRangeViaUiSettings();
|
||||
});
|
||||
|
||||
beforeEach(async function () {
|
||||
await common.navigateToApp('discover');
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
await discover.selectTextBaseLang();
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover');
|
||||
await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional');
|
||||
await kibanaServer.uiSettings.replace({});
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
});
|
||||
|
||||
it('doc viewer flyout should get dismissed on opening ESQL docs flyout', async function () {
|
||||
await dataGrid.clickRowToggle({ rowIndex: 0 });
|
||||
expect(await dataGrid.isShowingDocViewer()).to.be(true);
|
||||
await esql.openQuickReferenceFlyout();
|
||||
expect(await dataGrid.isShowingDocViewer()).to.be(false);
|
||||
expect(await esql.isOpenQuickReferenceFlyout()).to.be(true);
|
||||
});
|
||||
|
||||
it('doc viewer flyout should get dismissed on opening Lens Edit flyout', async function () {
|
||||
await dataGrid.clickRowToggle({ rowIndex: 0 });
|
||||
expect(await dataGrid.isShowingDocViewer()).to.be(true);
|
||||
await openLensEditFlyout();
|
||||
expect(await dataGrid.isShowingDocViewer()).to.be(false);
|
||||
expect(await isLensEditFlyoutOpen()).to.be(true);
|
||||
});
|
||||
|
||||
it('ESQL docs flyout should get dismissed on opening doc viewer flyout', async function () {
|
||||
await esql.openQuickReferenceFlyout();
|
||||
expect(await esql.isOpenQuickReferenceFlyout()).to.be(true);
|
||||
await dataGrid.clickRowToggle({ rowIndex: 0 });
|
||||
expect(await dataGrid.isShowingDocViewer()).to.be(true);
|
||||
expect(await esql.isOpenQuickReferenceFlyout()).to.be(false);
|
||||
});
|
||||
|
||||
it('ESQL docs flyout should get dismissed on opening Lens Edit flyout', async function () {
|
||||
await esql.openQuickReferenceFlyout();
|
||||
expect(await esql.isOpenQuickReferenceFlyout()).to.be(true);
|
||||
await openLensEditFlyout();
|
||||
expect(await isLensEditFlyoutOpen()).to.be(true);
|
||||
expect(await esql.isOpenQuickReferenceFlyout()).to.be(false);
|
||||
});
|
||||
|
||||
it('Lens Edit flyout should get dismissed on opening doc viewer flyout', async function () {
|
||||
await openLensEditFlyout();
|
||||
expect(await isLensEditFlyoutOpen()).to.be(true);
|
||||
await dataGrid.clickRowToggle({ rowIndex: 0 });
|
||||
expect(await dataGrid.isShowingDocViewer()).to.be(true);
|
||||
expect(await isLensEditFlyoutOpen()).to.be(false);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -24,5 +24,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
|
|||
|
||||
loadTestFile(require.resolve('./_default_route'));
|
||||
loadTestFile(require.resolve('./_hide_announcements'));
|
||||
loadTestFile(require.resolve('./_flyouts'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -50,4 +50,23 @@ export class ESQLService extends FtrService {
|
|||
const toggle = await row.findByTestSubject('ESQLEditor-queryHistory-runQuery-button');
|
||||
await toggle.click();
|
||||
}
|
||||
|
||||
public async openHelpMenu() {
|
||||
await this.testSubjects.click('esql-menu-button');
|
||||
await this.retry.waitFor('popover to appear', async () => {
|
||||
return await this.testSubjects.exists('esql-quick-reference');
|
||||
});
|
||||
}
|
||||
|
||||
public async isOpenQuickReferenceFlyout() {
|
||||
return await this.testSubjects.exists('esqlInlineDocumentationFlyout');
|
||||
}
|
||||
|
||||
public async openQuickReferenceFlyout() {
|
||||
await this.openHelpMenu();
|
||||
await this.testSubjects.click('esql-quick-reference');
|
||||
await this.retry.waitFor('quick reference to appear', async () => {
|
||||
return await this.isOpenQuickReferenceFlyout();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue