mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 11:05:39 -04:00
[Discover] Prevent showing the ES|QL transition modal when a saved search without unsaved changes is open (#177107)
## Summary This PR prevents the ES|QL transition modal from showing when switching to a data view from a saved search that has no unsaved changes. Resolves #176772. ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [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] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
parent
cc915bd4d9
commit
36365bd911
12 changed files with 258 additions and 86 deletions
|
@ -13,4 +13,5 @@ export {
|
||||||
getLimitFromESQLQuery,
|
getLimitFromESQLQuery,
|
||||||
removeDropCommandsFromESQLQuery,
|
removeDropCommandsFromESQLQuery,
|
||||||
getIndexForESQLQuery,
|
getIndexForESQLQuery,
|
||||||
|
TextBasedLanguages,
|
||||||
} from './src';
|
} from './src';
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export { TextBasedLanguages } from './types';
|
||||||
export { getESQLAdHocDataview, getIndexForESQLQuery } from './utils/get_esql_adhoc_dataview';
|
export { getESQLAdHocDataview, getIndexForESQLQuery } from './utils/get_esql_adhoc_dataview';
|
||||||
export {
|
export {
|
||||||
getIndexPatternFromSQLQuery,
|
getIndexPatternFromSQLQuery,
|
||||||
|
|
12
packages/kbn-esql-utils/src/types.ts
Normal file
12
packages/kbn-esql-utils/src/types.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export enum TextBasedLanguages {
|
||||||
|
SQL = 'SQL',
|
||||||
|
ESQL = 'ESQL',
|
||||||
|
}
|
|
@ -9,13 +9,18 @@
|
||||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||||
import type { AggregateQuery, Query, TimeRange } from '@kbn/es-query';
|
import type { AggregateQuery, Query, TimeRange } from '@kbn/es-query';
|
||||||
import { type DataView, DataViewType } from '@kbn/data-views-plugin/public';
|
import { type DataView, DataViewType } from '@kbn/data-views-plugin/public';
|
||||||
import type { DataViewPickerProps } from '@kbn/unified-search-plugin/public';
|
import { DataViewPickerProps } from '@kbn/unified-search-plugin/public';
|
||||||
import { ENABLE_ESQL } from '@kbn/discover-utils';
|
import { ENABLE_ESQL } from '@kbn/discover-utils';
|
||||||
import { useSavedSearchInitial } from '../../services/discover_state_provider';
|
import { TextBasedLanguages } from '@kbn/esql-utils';
|
||||||
|
import {
|
||||||
|
useSavedSearch,
|
||||||
|
useSavedSearchHasChanged,
|
||||||
|
useSavedSearchInitial,
|
||||||
|
} from '../../services/discover_state_provider';
|
||||||
import { useInternalStateSelector } from '../../services/discover_internal_state_container';
|
import { useInternalStateSelector } from '../../services/discover_internal_state_container';
|
||||||
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
|
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
|
||||||
import { getHeaderActionMenuMounter } from '../../../../kibana_services';
|
import { getHeaderActionMenuMounter } from '../../../../kibana_services';
|
||||||
import { DiscoverStateContainer } from '../../services/discover_state';
|
import type { DiscoverStateContainer } from '../../services/discover_state';
|
||||||
import { onSaveSearch } from './on_save_search';
|
import { onSaveSearch } from './on_save_search';
|
||||||
import { useDiscoverCustomization } from '../../../../customizations';
|
import { useDiscoverCustomization } from '../../../../customizations';
|
||||||
import { addLog } from '../../../../utils/add_log';
|
import { addLog } from '../../../../utils/add_log';
|
||||||
|
@ -47,30 +52,25 @@ export const DiscoverTopNav = ({
|
||||||
isLoading,
|
isLoading,
|
||||||
onCancelClick,
|
onCancelClick,
|
||||||
}: DiscoverTopNavProps) => {
|
}: DiscoverTopNavProps) => {
|
||||||
|
const services = useDiscoverServices();
|
||||||
|
const { dataViewEditor, navigation, dataViewFieldEditor, data, uiSettings, dataViews } = services;
|
||||||
const query = useAppStateSelector((state) => state.query);
|
const query = useAppStateSelector((state) => state.query);
|
||||||
const adHocDataViews = useInternalStateSelector((state) => state.adHocDataViews);
|
const adHocDataViews = useInternalStateSelector((state) => state.adHocDataViews);
|
||||||
const dataView = useInternalStateSelector((state) => state.dataView!);
|
const dataView = useInternalStateSelector((state) => state.dataView!);
|
||||||
const savedDataViews = useInternalStateSelector((state) => state.savedDataViews);
|
const savedDataViews = useInternalStateSelector((state) => state.savedDataViews);
|
||||||
const savedSearch = useSavedSearchInitial();
|
const savedSearch = useSavedSearchInitial();
|
||||||
const isTextBased = useMemo(() => isTextBasedQuery(query), [query]);
|
|
||||||
const showDatePicker = useMemo(() => {
|
const showDatePicker = useMemo(() => {
|
||||||
// always show the timepicker for text based languages
|
// always show the timepicker for text based languages
|
||||||
|
const isTextBased = isTextBasedQuery(query);
|
||||||
return (
|
return (
|
||||||
isTextBased ||
|
isTextBased ||
|
||||||
(!isTextBased && dataView.isTimeBased() && dataView.type !== DataViewType.ROLLUP)
|
(!isTextBased && dataView.isTimeBased() && dataView.type !== DataViewType.ROLLUP)
|
||||||
);
|
);
|
||||||
}, [dataView, isTextBased]);
|
}, [dataView, query]);
|
||||||
const services = useDiscoverServices();
|
|
||||||
const { dataViewEditor, navigation, dataViewFieldEditor, data, uiSettings, dataViews } = services;
|
|
||||||
|
|
||||||
const canEditDataView =
|
|
||||||
Boolean(dataViewEditor?.userPermissions.editDataView()) || !dataView.isPersisted();
|
|
||||||
|
|
||||||
const closeFieldEditor = useRef<() => void | undefined>();
|
const closeFieldEditor = useRef<() => void | undefined>();
|
||||||
const closeDataViewEditor = useRef<() => void | undefined>();
|
const closeDataViewEditor = useRef<() => void | undefined>();
|
||||||
|
|
||||||
const { AggregateQueryTopNavMenu } = navigation.ui;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
// Make sure to close the editors when unmounting
|
// Make sure to close the editors when unmounting
|
||||||
|
@ -83,6 +83,9 @@ export const DiscoverTopNav = ({
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const canEditDataView =
|
||||||
|
Boolean(dataViewEditor?.userPermissions.editDataView()) || !dataView.isPersisted();
|
||||||
|
|
||||||
const editField = useMemo(
|
const editField = useMemo(
|
||||||
() =>
|
() =>
|
||||||
canEditDataView
|
canEditDataView
|
||||||
|
@ -116,7 +119,8 @@ export const DiscoverTopNav = ({
|
||||||
});
|
});
|
||||||
}, [dataViewEditor, stateContainer]);
|
}, [dataViewEditor, stateContainer]);
|
||||||
|
|
||||||
const onEditDataView = async (editedDataView: DataView) => {
|
const onEditDataView = useCallback(
|
||||||
|
async (editedDataView: DataView) => {
|
||||||
if (editedDataView.isPersisted()) {
|
if (editedDataView.isPersisted()) {
|
||||||
// Clear the current data view from the cache and create a new instance
|
// Clear the current data view from the cache and create a new instance
|
||||||
// of it, ensuring we have a new object reference to trigger a re-render
|
// of it, ensuring we have a new object reference to trigger a re-render
|
||||||
|
@ -128,7 +132,9 @@ export const DiscoverTopNav = ({
|
||||||
stateContainer.actions.loadDataViewList();
|
stateContainer.actions.loadDataViewList();
|
||||||
addLog('[DiscoverTopNav] onEditDataView triggers data fetching');
|
addLog('[DiscoverTopNav] onEditDataView triggers data fetching');
|
||||||
stateContainer.dataState.fetch();
|
stateContainer.dataState.fetch();
|
||||||
};
|
},
|
||||||
|
[dataViews, stateContainer.actions, stateContainer.dataState]
|
||||||
|
);
|
||||||
|
|
||||||
const updateSavedQueryId = (newSavedQueryId: string | undefined) => {
|
const updateSavedQueryId = (newSavedQueryId: string | undefined) => {
|
||||||
const { appState } = stateContainer;
|
const { appState } = stateContainer;
|
||||||
|
@ -143,41 +149,6 @@ export const DiscoverTopNav = ({
|
||||||
appState.set(newState);
|
appState.set(newState);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const setMenuMountPoint = useMemo(() => {
|
|
||||||
return getHeaderActionMenuMounter();
|
|
||||||
}, []);
|
|
||||||
const isESQLModeEnabled = uiSettings.get(ENABLE_ESQL);
|
|
||||||
const supportedTextBasedLanguages = [];
|
|
||||||
if (isESQLModeEnabled) {
|
|
||||||
supportedTextBasedLanguages.push('ESQL');
|
|
||||||
}
|
|
||||||
|
|
||||||
const searchBarCustomization = useDiscoverCustomization('search_bar');
|
|
||||||
|
|
||||||
const SearchBar = useMemo(
|
|
||||||
() => searchBarCustomization?.CustomSearchBar ?? AggregateQueryTopNavMenu,
|
|
||||||
[searchBarCustomization?.CustomSearchBar, AggregateQueryTopNavMenu]
|
|
||||||
);
|
|
||||||
|
|
||||||
const shouldHideDefaultDataviewPicker =
|
|
||||||
!!searchBarCustomization?.CustomDataViewPicker || !!searchBarCustomization?.hideDataViewPicker;
|
|
||||||
|
|
||||||
const dataViewPickerProps: DataViewPickerProps = {
|
|
||||||
trigger: {
|
|
||||||
label: dataView?.getName() || '',
|
|
||||||
'data-test-subj': 'discover-dataView-switch-link',
|
|
||||||
title: dataView?.getIndexPattern() || '',
|
|
||||||
},
|
|
||||||
currentDataViewId: dataView?.id,
|
|
||||||
onAddField: addField,
|
|
||||||
onDataViewCreated: createNewDataView,
|
|
||||||
onCreateDefaultAdHocDataView: stateContainer.actions.createAndAppendAdHocDataView,
|
|
||||||
onChangeDataView: stateContainer.actions.onChangeDataView,
|
|
||||||
textBasedLanguages: supportedTextBasedLanguages as DataViewPickerProps['textBasedLanguages'],
|
|
||||||
adHocDataViews,
|
|
||||||
savedDataViews,
|
|
||||||
onEditDataView,
|
|
||||||
};
|
|
||||||
|
|
||||||
const onTextBasedSavedAndExit = useCallback(
|
const onTextBasedSavedAndExit = useCallback(
|
||||||
({ onSave, onCancel }) => {
|
({ onSave, onCancel }) => {
|
||||||
|
@ -193,11 +164,66 @@ export const DiscoverTopNav = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
const { topNavBadges, topNavMenu } = useDiscoverTopNav({ stateContainer });
|
const { topNavBadges, topNavMenu } = useDiscoverTopNav({ stateContainer });
|
||||||
const topNavProps = !services.serverless && {
|
const setMenuMountPoint = getHeaderActionMenuMounter();
|
||||||
|
const topNavProps = useMemo(() => {
|
||||||
|
if (services.serverless) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
badges: topNavBadges,
|
badges: topNavBadges,
|
||||||
config: topNavMenu,
|
config: topNavMenu,
|
||||||
setMenuMountPoint,
|
setMenuMountPoint,
|
||||||
};
|
};
|
||||||
|
}, [services.serverless, setMenuMountPoint, topNavBadges, topNavMenu]);
|
||||||
|
|
||||||
|
const savedSearchId = useSavedSearch().id;
|
||||||
|
const savedSearchHasChanged = useSavedSearchHasChanged();
|
||||||
|
const dataViewPickerProps: DataViewPickerProps = useMemo(() => {
|
||||||
|
const isESQLModeEnabled = uiSettings.get(ENABLE_ESQL);
|
||||||
|
const supportedTextBasedLanguages: DataViewPickerProps['textBasedLanguages'] = isESQLModeEnabled
|
||||||
|
? [TextBasedLanguages.ESQL]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
trigger: {
|
||||||
|
label: dataView?.getName() || '',
|
||||||
|
'data-test-subj': 'discover-dataView-switch-link',
|
||||||
|
title: dataView?.getIndexPattern() || '',
|
||||||
|
},
|
||||||
|
currentDataViewId: dataView?.id,
|
||||||
|
onAddField: addField,
|
||||||
|
onDataViewCreated: createNewDataView,
|
||||||
|
onCreateDefaultAdHocDataView: stateContainer.actions.createAndAppendAdHocDataView,
|
||||||
|
onChangeDataView: stateContainer.actions.onChangeDataView,
|
||||||
|
textBasedLanguages: supportedTextBasedLanguages,
|
||||||
|
shouldShowTextBasedLanguageTransitionModal: !savedSearchId || savedSearchHasChanged,
|
||||||
|
adHocDataViews,
|
||||||
|
savedDataViews,
|
||||||
|
onEditDataView,
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
adHocDataViews,
|
||||||
|
addField,
|
||||||
|
createNewDataView,
|
||||||
|
dataView,
|
||||||
|
onEditDataView,
|
||||||
|
savedDataViews,
|
||||||
|
savedSearchHasChanged,
|
||||||
|
savedSearchId,
|
||||||
|
stateContainer,
|
||||||
|
uiSettings,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const searchBarCustomization = useDiscoverCustomization('search_bar');
|
||||||
|
|
||||||
|
const SearchBar = useMemo(
|
||||||
|
() => searchBarCustomization?.CustomSearchBar ?? navigation.ui.AggregateQueryTopNavMenu,
|
||||||
|
[searchBarCustomization?.CustomSearchBar, navigation.ui.AggregateQueryTopNavMenu]
|
||||||
|
);
|
||||||
|
|
||||||
|
const shouldHideDefaultDataviewPicker =
|
||||||
|
!!searchBarCustomization?.CustomDataViewPicker || !!searchBarCustomization?.hideDataViewPicker;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SearchBar
|
<SearchBar
|
||||||
|
|
|
@ -30,10 +30,19 @@ function createStateHelpers() {
|
||||||
container!.savedSearchState.getInitial$().getValue()
|
container!.savedSearchState.getInitial$().getValue()
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
const useSavedSearchHasChanged = () => {
|
||||||
|
const container = useContainer();
|
||||||
|
return useObservable<boolean>(
|
||||||
|
container!.savedSearchState.getHasChanged$(),
|
||||||
|
container!.savedSearchState.getHasChanged$().getValue()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Provider: context.Provider,
|
Provider: context.Provider,
|
||||||
useSavedSearch,
|
useSavedSearch,
|
||||||
useSavedSearchInitial,
|
useSavedSearchInitial,
|
||||||
|
useSavedSearchHasChanged,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +50,7 @@ export const {
|
||||||
Provider: DiscoverStateProvider,
|
Provider: DiscoverStateProvider,
|
||||||
useSavedSearchInitial,
|
useSavedSearchInitial,
|
||||||
useSavedSearch,
|
useSavedSearch,
|
||||||
|
useSavedSearchHasChanged,
|
||||||
} = createStateHelpers();
|
} = createStateHelpers();
|
||||||
|
|
||||||
export const DiscoverMainProvider = ({
|
export const DiscoverMainProvider = ({
|
||||||
|
|
|
@ -14,10 +14,13 @@ import { findTestSubject } from '@elastic/eui/lib/test';
|
||||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||||
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
||||||
import { indexPatternEditorPluginMock as dataViewEditorPluginMock } from '@kbn/data-view-editor-plugin/public/mocks';
|
import { indexPatternEditorPluginMock as dataViewEditorPluginMock } from '@kbn/data-view-editor-plugin/public/mocks';
|
||||||
|
import { TextBasedLanguages } from '@kbn/esql-utils';
|
||||||
import { ChangeDataView } from './change_dataview';
|
import { ChangeDataView } from './change_dataview';
|
||||||
import { DataViewSelector } from './data_view_selector';
|
import { DataViewSelector } from './data_view_selector';
|
||||||
import { dataViewMock } from './mocks/dataview';
|
import { dataViewMock } from './mocks/dataview';
|
||||||
import { DataViewPickerPropsExtended, TextBasedLanguages } from './data_view_picker';
|
import { DataViewPickerPropsExtended } from './data_view_picker';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
describe('DataView component', () => {
|
describe('DataView component', () => {
|
||||||
const createMockWebStorage = () => ({
|
const createMockWebStorage = () => ({
|
||||||
|
@ -224,4 +227,44 @@ describe('DataView component', () => {
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('test based language switch warning icon', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
// Enzyme doesn't clean the DOM between tests, so we need to do it manually
|
||||||
|
document.body.innerHTML = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show text based language switch warning icon', () => {
|
||||||
|
render(
|
||||||
|
wrapDataViewComponentInContext(
|
||||||
|
{
|
||||||
|
...props,
|
||||||
|
onDataViewCreated: jest.fn(),
|
||||||
|
textBasedLanguages: [TextBasedLanguages.ESQL],
|
||||||
|
textBasedLanguage: TextBasedLanguages.ESQL,
|
||||||
|
},
|
||||||
|
false
|
||||||
|
)
|
||||||
|
);
|
||||||
|
userEvent.click(screen.getByTestId('dataview-trigger'));
|
||||||
|
expect(screen.queryByTestId('textBasedLang-warning')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not show text based language switch warning icon when shouldShowTextBasedLanguageTransitionModal is false', () => {
|
||||||
|
render(
|
||||||
|
wrapDataViewComponentInContext(
|
||||||
|
{
|
||||||
|
...props,
|
||||||
|
onDataViewCreated: jest.fn(),
|
||||||
|
textBasedLanguages: [TextBasedLanguages.ESQL],
|
||||||
|
textBasedLanguage: TextBasedLanguages.ESQL,
|
||||||
|
shouldShowTextBasedLanguageTransitionModal: false,
|
||||||
|
},
|
||||||
|
false
|
||||||
|
)
|
||||||
|
);
|
||||||
|
userEvent.click(screen.getByTestId('dataview-trigger'));
|
||||||
|
expect(screen.queryByTestId('textBasedLang-warning')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -78,6 +78,7 @@ export function ChangeDataView({
|
||||||
onSaveTextLanguageQuery,
|
onSaveTextLanguageQuery,
|
||||||
onTextLangQuerySubmit,
|
onTextLangQuerySubmit,
|
||||||
textBasedLanguage,
|
textBasedLanguage,
|
||||||
|
shouldShowTextBasedLanguageTransitionModal = true,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
onEditDataView,
|
onEditDataView,
|
||||||
onCreateDefaultAdHocDataView,
|
onCreateDefaultAdHocDataView,
|
||||||
|
@ -238,7 +239,7 @@ export function ChangeDataView({
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiFlexGroup alignItems="center" gutterSize="xs" responsive={false}>
|
<EuiFlexGroup alignItems="center" gutterSize="xs" responsive={false}>
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
{Boolean(isTextBasedLangSelected) ? (
|
{isTextBasedLangSelected && shouldShowTextBasedLanguageTransitionModal ? (
|
||||||
<EuiToolTip
|
<EuiToolTip
|
||||||
position="top"
|
position="top"
|
||||||
content={i18n.translate(
|
content={i18n.translate(
|
||||||
|
@ -309,9 +310,14 @@ export function ChangeDataView({
|
||||||
onChangeDataView={async (newId) => {
|
onChangeDataView={async (newId) => {
|
||||||
setSelectedDataViewId(newId);
|
setSelectedDataViewId(newId);
|
||||||
setPopoverIsOpen(false);
|
setPopoverIsOpen(false);
|
||||||
if (isTextBasedLangSelected && !isTextLangTransitionModalDismissed) {
|
|
||||||
|
if (isTextBasedLangSelected) {
|
||||||
|
const showTransitionModal =
|
||||||
|
!isTextLangTransitionModalDismissed && shouldShowTextBasedLanguageTransitionModal;
|
||||||
|
|
||||||
|
if (showTransitionModal) {
|
||||||
setIsTextLangTransitionModalVisible(true);
|
setIsTextLangTransitionModalVisible(true);
|
||||||
} else if (isTextBasedLangSelected && isTextLangTransitionModalDismissed) {
|
} else {
|
||||||
setIsTextBasedLangSelected(false);
|
setIsTextBasedLangSelected(false);
|
||||||
// clean up the Text based language query
|
// clean up the Text based language query
|
||||||
onTextLangQuerySubmit?.({
|
onTextLangQuerySubmit?.({
|
||||||
|
@ -320,6 +326,7 @@ export function ChangeDataView({
|
||||||
});
|
});
|
||||||
onChangeDataView(newId);
|
onChangeDataView(newId);
|
||||||
setTriggerLabel(trigger.label);
|
setTriggerLabel(trigger.label);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
onChangeDataView(newId);
|
onChangeDataView(newId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import React from 'react';
|
||||||
import type { EuiButtonProps, EuiSelectableProps } from '@elastic/eui';
|
import type { EuiButtonProps, EuiSelectableProps } from '@elastic/eui';
|
||||||
import type { DataView, DataViewListItem, DataViewSpec } from '@kbn/data-views-plugin/public';
|
import type { DataView, DataViewListItem, DataViewSpec } from '@kbn/data-views-plugin/public';
|
||||||
import type { AggregateQuery, Query } from '@kbn/es-query';
|
import type { AggregateQuery, Query } from '@kbn/es-query';
|
||||||
|
import { TextBasedLanguages } from '@kbn/esql-utils';
|
||||||
import { ChangeDataView } from './change_dataview';
|
import { ChangeDataView } from './change_dataview';
|
||||||
|
|
||||||
export type ChangeDataViewTriggerProps = EuiButtonProps & {
|
export type ChangeDataViewTriggerProps = EuiButtonProps & {
|
||||||
|
@ -17,11 +18,6 @@ export type ChangeDataViewTriggerProps = EuiButtonProps & {
|
||||||
title?: string;
|
title?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum TextBasedLanguages {
|
|
||||||
SQL = 'SQL',
|
|
||||||
ESQL = 'ESQL',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OnSaveTextLanguageQueryProps {
|
export interface OnSaveTextLanguageQueryProps {
|
||||||
onSave: () => void;
|
onSave: () => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
|
@ -84,6 +80,11 @@ export interface DataViewPickerProps {
|
||||||
* Callback that is called when the user clicks the Save and switch transition modal button
|
* Callback that is called when the user clicks the Save and switch transition modal button
|
||||||
*/
|
*/
|
||||||
onSaveTextLanguageQuery?: ({ onSave, onCancel }: OnSaveTextLanguageQueryProps) => void;
|
onSaveTextLanguageQuery?: ({ onSave, onCancel }: OnSaveTextLanguageQueryProps) => void;
|
||||||
|
/**
|
||||||
|
* Determines if the text based language transition
|
||||||
|
* modal should be shown when switching data views
|
||||||
|
*/
|
||||||
|
shouldShowTextBasedLanguageTransitionModal?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes the picker disabled by disabling the popover trigger
|
* Makes the picker disabled by disabling the popover trigger
|
||||||
|
@ -117,6 +118,7 @@ export const DataViewPicker = ({
|
||||||
onSaveTextLanguageQuery,
|
onSaveTextLanguageQuery,
|
||||||
onTextLangQuerySubmit,
|
onTextLangQuerySubmit,
|
||||||
textBasedLanguage,
|
textBasedLanguage,
|
||||||
|
shouldShowTextBasedLanguageTransitionModal,
|
||||||
onCreateDefaultAdHocDataView,
|
onCreateDefaultAdHocDataView,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
}: DataViewPickerPropsExtended) => {
|
}: DataViewPickerPropsExtended) => {
|
||||||
|
@ -137,6 +139,7 @@ export const DataViewPicker = ({
|
||||||
onSaveTextLanguageQuery={onSaveTextLanguageQuery}
|
onSaveTextLanguageQuery={onSaveTextLanguageQuery}
|
||||||
onTextLangQuerySubmit={onTextLangQuerySubmit}
|
onTextLangQuerySubmit={onTextLangQuerySubmit}
|
||||||
textBasedLanguage={textBasedLanguage}
|
textBasedLanguage={textBasedLanguage}
|
||||||
|
shouldShowTextBasedLanguageTransitionModal={shouldShowTextBasedLanguageTransitionModal}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -41,7 +41,11 @@ export default function TextBasedLanguagesTransitionModal({
|
||||||
|
|
||||||
const language = getLanguageDisplayName(textBasedLanguage);
|
const language = getLanguageDisplayName(textBasedLanguage);
|
||||||
return (
|
return (
|
||||||
<EuiModal onClose={() => setIsTextLangTransitionModalVisible(false)} style={{ width: 700 }}>
|
<EuiModal
|
||||||
|
onClose={() => setIsTextLangTransitionModalVisible(false)}
|
||||||
|
style={{ width: 700 }}
|
||||||
|
data-test-subj="unifiedSearch_switch_modal"
|
||||||
|
>
|
||||||
<EuiModalHeader>
|
<EuiModalHeader>
|
||||||
<EuiModalHeaderTitle>
|
<EuiModalHeaderTitle>
|
||||||
{i18n.translate(
|
{i18n.translate(
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
"@kbn/code-editor",
|
"@kbn/code-editor",
|
||||||
"@kbn/calculate-width-from-char-count",
|
"@kbn/calculate-width-from-char-count",
|
||||||
"@kbn/react-kibana-context-render",
|
"@kbn/react-kibana-context-render",
|
||||||
|
"@kbn/esql-utils"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"target/**/*",
|
"target/**/*",
|
||||||
|
|
|
@ -18,6 +18,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
const testSubjects = getService('testSubjects');
|
const testSubjects = getService('testSubjects');
|
||||||
const monacoEditor = getService('monacoEditor');
|
const monacoEditor = getService('monacoEditor');
|
||||||
const security = getService('security');
|
const security = getService('security');
|
||||||
|
const retry = getService('retry');
|
||||||
|
const find = getService('find');
|
||||||
const PageObjects = getPageObjects([
|
const PageObjects = getPageObjects([
|
||||||
'common',
|
'common',
|
||||||
'discover',
|
'discover',
|
||||||
|
@ -33,6 +35,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
|
|
||||||
describe('discover esql view', async function () {
|
describe('discover esql view', async function () {
|
||||||
before(async () => {
|
before(async () => {
|
||||||
|
await kibanaServer.savedObjects.cleanStandardList();
|
||||||
await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader']);
|
await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader']);
|
||||||
log.debug('load kibana index with default index pattern');
|
log.debug('load kibana index with default index pattern');
|
||||||
await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover');
|
await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover');
|
||||||
|
@ -152,6 +155,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('errors', () => {
|
describe('errors', () => {
|
||||||
it('should show error messages for syntax errors in query', async function () {
|
it('should show error messages for syntax errors in query', async function () {
|
||||||
await PageObjects.discover.selectTextBaseLang();
|
await PageObjects.discover.selectTextBaseLang();
|
||||||
|
@ -179,5 +183,60 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('switch modal', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await PageObjects.common.navigateToApp('discover');
|
||||||
|
await PageObjects.timePicker.setDefaultAbsoluteRange();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show switch modal when switching to a data view', async () => {
|
||||||
|
await PageObjects.discover.selectTextBaseLang();
|
||||||
|
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||||
|
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||||
|
await PageObjects.discover.selectIndexPattern('logstash-*', false);
|
||||||
|
await retry.try(async () => {
|
||||||
|
await testSubjects.existOrFail('unifiedSearch_switch_modal');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not show switch modal when switching to a data view while a saved search is open', async () => {
|
||||||
|
await PageObjects.discover.selectTextBaseLang();
|
||||||
|
const testQuery = 'from logstash-* | limit 100 | drop @timestamp';
|
||||||
|
await monacoEditor.setCodeEditorValue(testQuery);
|
||||||
|
await testSubjects.click('querySubmitButton');
|
||||||
|
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||||
|
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||||
|
await PageObjects.discover.selectIndexPattern('logstash-*', false);
|
||||||
|
await retry.try(async () => {
|
||||||
|
await testSubjects.existOrFail('unifiedSearch_switch_modal');
|
||||||
|
});
|
||||||
|
await find.clickByCssSelector(
|
||||||
|
'[data-test-subj="unifiedSearch_switch_modal"] .euiModal__closeIcon'
|
||||||
|
);
|
||||||
|
await retry.try(async () => {
|
||||||
|
await testSubjects.missingOrFail('unifiedSearch_switch_modal');
|
||||||
|
});
|
||||||
|
await PageObjects.discover.saveSearch('esql_test');
|
||||||
|
await PageObjects.discover.selectIndexPattern('logstash-*');
|
||||||
|
await testSubjects.missingOrFail('unifiedSearch_switch_modal');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show switch modal when switching to a data view while a saved search with unsaved changes is open', async () => {
|
||||||
|
await PageObjects.discover.selectTextBaseLang();
|
||||||
|
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||||
|
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||||
|
await PageObjects.discover.saveSearch('esql_test2');
|
||||||
|
const testQuery = 'from logstash-* | limit 100 | drop @timestamp';
|
||||||
|
await monacoEditor.setCodeEditorValue(testQuery);
|
||||||
|
await testSubjects.click('querySubmitButton');
|
||||||
|
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||||
|
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||||
|
await PageObjects.discover.selectIndexPattern('logstash-*', false);
|
||||||
|
await retry.try(async () => {
|
||||||
|
await testSubjects.existOrFail('unifiedSearch_switch_modal');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -551,14 +551,19 @@ export class DiscoverPageObject extends FtrService {
|
||||||
return hasBadge;
|
return hasBadge;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async selectIndexPattern(indexPattern: string) {
|
public async selectIndexPattern(
|
||||||
|
indexPattern: string,
|
||||||
|
waitUntilLoadingHasFinished: boolean = true
|
||||||
|
) {
|
||||||
await this.testSubjects.click('discover-dataView-switch-link');
|
await this.testSubjects.click('discover-dataView-switch-link');
|
||||||
await this.find.setValue('[data-test-subj="indexPattern-switcher"] input', indexPattern);
|
await this.find.setValue('[data-test-subj="indexPattern-switcher"] input', indexPattern);
|
||||||
await this.find.clickByCssSelector(
|
await this.find.clickByCssSelector(
|
||||||
`[data-test-subj="indexPattern-switcher"] [title="${indexPattern}"]`
|
`[data-test-subj="indexPattern-switcher"] [title="${indexPattern}"]`
|
||||||
);
|
);
|
||||||
|
if (waitUntilLoadingHasFinished) {
|
||||||
await this.header.waitUntilLoadingHasFinished();
|
await this.header.waitUntilLoadingHasFinished();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async getIndexPatterns() {
|
public async getIndexPatterns() {
|
||||||
await this.testSubjects.click('discover-dataView-switch-link');
|
await this.testSubjects.click('discover-dataView-switch-link');
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue