diff --git a/src/core_plugins/kibana/public/dashboard/dashboard_app.js b/src/core_plugins/kibana/public/dashboard/dashboard_app.js index ce6ac9e7d48e47..5926e2ed133619 100644 --- a/src/core_plugins/kibana/public/dashboard/dashboard_app.js +++ b/src/core_plugins/kibana/public/dashboard/dashboard_app.js @@ -372,10 +372,7 @@ app.directive('dashboardApp', function ($injector) { $scope.$apply(); }; - const isLabsEnabled = config.get('visualize:enableLabs'); - const listingLimit = config.get('savedObjects:listingLimit'); - - showAddPanel(chrome.getSavedObjectsClient(), dashboardStateManager.addNewPanel, addNewVis, listingLimit, isLabsEnabled, visTypes); + showAddPanel(dashboardStateManager.addNewPanel, addNewVis, visTypes); }; navActions[TopNavIds.OPTIONS] = (menuItem, navController, anchorElement) => { showOptionsPopover({ diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/add_panel.test.js.snap b/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/add_panel.test.js.snap index 45e90bc21fa3ea..85684aecc461fb 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/add_panel.test.js.snap +++ b/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/add_panel.test.js.snap @@ -11,28 +11,13 @@ exports[`render 1`] = ` size="s" > - - - -

- Add Panels -

-
-
-
+

+ Add Panels +

+ } - find={[Function]} key="visSavedObjectFinder" noItemsMessage="No matching visualizations found." onChoose={[Function]} savedObjectType="visualization" + visTypes={Object {}} />
diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/add_panel.js b/src/core_plugins/kibana/public/dashboard/top_nav/add_panel.js index 3346a41a5dbd95..cee97c4f07dad8 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/add_panel.js +++ b/src/core_plugins/kibana/public/dashboard/top_nav/add_panel.js @@ -23,8 +23,6 @@ import { toastNotifications } from 'ui/notify'; import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; import { - EuiFlexGroup, - EuiFlexItem, EuiFlyout, EuiFlyoutBody, EuiButton, @@ -60,7 +58,7 @@ export class DashboardAddPanel extends React.Component { key="visSavedObjectFinder" callToActionButton={addNewVisBtn} onChoose={this.onAddPanel} - find={this.props.find} + visTypes={this.props.visTypes} noItemsMessage="No matching visualizations found." savedObjectType="visualization" /> @@ -74,7 +72,6 @@ export class DashboardAddPanel extends React.Component { @@ -133,13 +130,9 @@ export class DashboardAddPanel extends React.Component { > - - - -

Add Panels

-
-
-
+ +

Add Panels

+
{this.renderTabs()} @@ -157,7 +150,7 @@ export class DashboardAddPanel extends React.Component { DashboardAddPanel.propTypes = { onClose: PropTypes.func.isRequired, - find: PropTypes.func.isRequired, + visTypes: PropTypes.object.isRequired, addNewPanel: PropTypes.func.isRequired, addNewVis: PropTypes.func.isRequired, }; diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/add_panel.test.js b/src/core_plugins/kibana/public/dashboard/top_nav/add_panel.test.js index 6af26997acc8be..9c17980f7b9cda 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/add_panel.test.js +++ b/src/core_plugins/kibana/public/dashboard/top_nav/add_panel.test.js @@ -40,7 +40,7 @@ beforeEach(() => { test('render', () => { const component = shallow( {}} + visTypes={{}} addNewPanel={() => {}} addNewVis={() => {}} />); diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/show_add_panel.js b/src/core_plugins/kibana/public/dashboard/top_nav/show_add_panel.js index 0f7bf3b72a3a38..00719a850aca42 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/show_add_panel.js +++ b/src/core_plugins/kibana/public/dashboard/top_nav/show_add_panel.js @@ -23,7 +23,7 @@ import ReactDOM from 'react-dom'; let isOpen = false; -export function showAddPanel(savedObjectsClient, addNewPanel, addNewVis, listingLimit, isLabsEnabled, visTypes) { +export function showAddPanel(addNewPanel, addNewVis, visTypes) { if (isOpen) { return; } @@ -35,26 +35,6 @@ export function showAddPanel(savedObjectsClient, addNewPanel, addNewVis, listing document.body.removeChild(container); isOpen = false; }; - const find = async (type, search) => { - const resp = await savedObjectsClient.find({ - type: type, - fields: ['title', 'visState'], - search: search ? `${search}*` : undefined, - page: 1, - perPage: listingLimit, - searchFields: ['title^3', 'description'] - }); - - if (type === 'visualization' && !isLabsEnabled) { - resp.savedObjects = resp.savedObjects.filter(savedObject => { - const typeName = JSON.parse(savedObject.attributes.visState).type; - const visType = visTypes.byName[typeName]; - return visType.stage !== 'lab'; - }); - } - - return resp; - }; const addNewVisWithCleanup = () => { onClose(); @@ -65,7 +45,7 @@ export function showAddPanel(savedObjectsClient, addNewPanel, addNewVis, listing const element = ( diff --git a/src/core_plugins/kibana/public/discover/controllers/discover.js b/src/core_plugins/kibana/public/discover/controllers/discover.js index f62ea2794d3861..3085856b570734 100644 --- a/src/core_plugins/kibana/public/discover/controllers/discover.js +++ b/src/core_plugins/kibana/public/discover/controllers/discover.js @@ -58,6 +58,7 @@ import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing'; import { Inspector } from 'ui/inspector'; import { RequestAdapter } from 'ui/inspector/adapters'; import { getRequestInspectorStats, getResponseInspectorStats } from 'ui/courier/utils/courier_inspector_utils'; +import { showOpenSearchPanel } from '../top_nav/show_open_search_panel'; import { tabifyAggResponse } from 'ui/agg_response/tabify'; const app = uiModules.get('apps/discover', [ @@ -198,8 +199,14 @@ function discoverController( }, { key: 'open', description: 'Open Saved Search', - template: require('plugins/kibana/discover/partials/load_search.html'), testId: 'discoverOpenButton', + run: () => { + showOpenSearchPanel({ + makeUrl: (searchId) => { + return kbnUrl.eval('#/discover/{{id}}', { id: searchId }); + } + }); + } }, { key: 'share', description: 'Share Search', diff --git a/src/core_plugins/kibana/public/discover/partials/load_search.html b/src/core_plugins/kibana/public/discover/partials/load_search.html deleted file mode 100644 index 4b944def8dcae2..00000000000000 --- a/src/core_plugins/kibana/public/discover/partials/load_search.html +++ /dev/null @@ -1,7 +0,0 @@ -
-

- Open Search -

- - -
diff --git a/src/core_plugins/kibana/public/discover/top_nav/__snapshots__/open_search_panel.test.js.snap b/src/core_plugins/kibana/public/discover/top_nav/__snapshots__/open_search_panel.test.js.snap new file mode 100644 index 00000000000000..e35d7fc76868e8 --- /dev/null +++ b/src/core_plugins/kibana/public/discover/top_nav/__snapshots__/open_search_panel.test.js.snap @@ -0,0 +1,44 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`render 1`] = ` + + + +

+ Open Search +

+
+ + + Manage searches + + } + makeUrl={[Function]} + noItemsMessage="No matching searches found." + onChoose={[Function]} + savedObjectType="search" + /> +
+
+`; diff --git a/src/core_plugins/kibana/public/discover/top_nav/open_search_panel.js b/src/core_plugins/kibana/public/discover/top_nav/open_search_panel.js new file mode 100644 index 00000000000000..f06dcf5d9c3d65 --- /dev/null +++ b/src/core_plugins/kibana/public/discover/top_nav/open_search_panel.js @@ -0,0 +1,80 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; +import rison from 'rison-node'; + +import { + EuiSpacer, + EuiFlyout, + EuiFlyoutBody, + EuiTitle, + EuiButton, +} from '@elastic/eui'; + +const SEARCH_OBJECT_TYPE = 'search'; + +export class OpenSearchPanel extends React.Component { + + renderMangageSearchesButton() { + return ( + + Manage searches + + ); + } + + render() { + return ( + + + + +

Open Search

+
+ + + + + +
+
+ ); + } +} + +OpenSearchPanel.propTypes = { + onClose: PropTypes.func.isRequired, + makeUrl: PropTypes.func.isRequired, +}; diff --git a/src/core_plugins/kibana/public/discover/top_nav/open_search_panel.test.js b/src/core_plugins/kibana/public/discover/top_nav/open_search_panel.test.js new file mode 100644 index 00000000000000..2bec532110379e --- /dev/null +++ b/src/core_plugins/kibana/public/discover/top_nav/open_search_panel.test.js @@ -0,0 +1,33 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { + OpenSearchPanel, +} from './open_search_panel'; + +test('render', () => { + const component = shallow( {}} + makeUrl={() => {}} + />); + expect(component).toMatchSnapshot(); +}); diff --git a/src/core_plugins/kibana/public/discover/top_nav/show_open_search_panel.js b/src/core_plugins/kibana/public/discover/top_nav/show_open_search_panel.js new file mode 100644 index 00000000000000..5b76f9ebc66527 --- /dev/null +++ b/src/core_plugins/kibana/public/discover/top_nav/show_open_search_panel.js @@ -0,0 +1,47 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { OpenSearchPanel } from './open_search_panel'; + +let isOpen = false; + +export function showOpenSearchPanel({ makeUrl }) { + if (isOpen) { + return; + } + + isOpen = true; + const container = document.createElement('div'); + const onClose = () => { + ReactDOM.unmountComponentAtNode(container); + document.body.removeChild(container); + isOpen = false; + }; + + document.body.appendChild(container); + const element = ( + + ); + ReactDOM.render(element, container); +} diff --git a/src/ui/public/saved_objects/components/saved_object_finder.js b/src/ui/public/saved_objects/components/saved_object_finder.js index 68d709e29f99ba..fd8819449d21d4 100644 --- a/src/ui/public/saved_objects/components/saved_object_finder.js +++ b/src/ui/public/saved_objects/components/saved_object_finder.js @@ -20,6 +20,7 @@ import _ from 'lodash'; import React from 'react'; import PropTypes from 'prop-types'; +import chrome from 'ui/chrome'; import { EuiFieldSearch, @@ -104,7 +105,24 @@ export class SavedObjectFinder extends React.Component { } debouncedFetch = _.debounce(async (filter) => { - const response = await this.props.find(this.props.savedObjectType, filter); + const resp = await chrome.getSavedObjectsClient().find({ + type: this.props.savedObjectType, + fields: ['title', 'visState'], + search: filter ? `${filter}*` : undefined, + page: 1, + perPage: chrome.getUiSettingsClient().get('savedObjects:listingLimit'), + searchFields: ['title^3', 'description'] + }); + + if (this.props.savedObjectType === 'visualization' + && !chrome.getUiSettingsClient().get('visualize:enableLabs') + && this.props.visTypes) { + resp.savedObjects = resp.savedObjects.filter(savedObject => { + const typeName = JSON.parse(savedObject.attributes.visState).type; + const visType = this.props.visTypes.byName[typeName]; + return visType.stage !== 'lab'; + }); + } if (!this._isMounted) { return; @@ -115,7 +133,7 @@ export class SavedObjectFinder extends React.Component { if (filter === this.state.filter) { this.setState({ isFetchingItems: false, - items: response.savedObjects.map(savedObject => { + items: resp.savedObjects.map(savedObject => { return { title: savedObject.attributes.title, id: savedObject.id, @@ -183,16 +201,26 @@ export class SavedObjectFinder extends React.Component { field: 'title', name: 'Title', sortable: true, - render: (field, record) => ( - { - this.props.onChoose(record.id, record.type); - }} - data-test-subj={`addPanel${field.split(' ').join('-')}`} - > - {field} - - ) + render: (title, record) => { + const { + onChoose, + makeUrl + } = this.props; + + if (!onChoose && !makeUrl) { + return {title}; + } + + return ( + { onChoose(record.id, record.type); } : undefined} + href={makeUrl ? makeUrl(record.id) : undefined} + data-test-subj={`savedObjectTitle${title.split(' ').join('-')}`} + > + {title} + + ); + } } ]; const items = this.state.items.length === 0 ? [] : this.getPageOfItems(); @@ -221,8 +249,9 @@ export class SavedObjectFinder extends React.Component { SavedObjectFinder.propTypes = { callToActionButton: PropTypes.node, - onChoose: PropTypes.func.isRequired, - find: PropTypes.func.isRequired, + onChoose: PropTypes.func, + makeUrl: PropTypes.func, noItemsMessage: PropTypes.node, savedObjectType: PropTypes.oneOf(['visualization', 'search']).isRequired, + visTypes: PropTypes.object, }; diff --git a/test/functional/apps/visualize/_lab_mode.js b/test/functional/apps/visualize/_lab_mode.js index f0fd6f59721c5b..d45d9cc6366221 100644 --- a/test/functional/apps/visualize/_lab_mode.js +++ b/test/functional/apps/visualize/_lab_mode.js @@ -29,9 +29,10 @@ export default function ({ getService, getPageObjects }) { it('disabling does not break loading saved searches', async () => { await PageObjects.common.navigateToUrl('discover', ''); await PageObjects.discover.saveSearch('visualize_lab_mode_test'); - await PageObjects.discover.openSavedSearch(); + await PageObjects.discover.openLoadSavedSearchPanel(); const hasSaved = await PageObjects.discover.hasSavedSearch('visualize_lab_mode_test'); expect(hasSaved).to.be(true); + await PageObjects.discover.closeLoadSaveSearchPanel(); log.info('found saved search before toggling enableLabs mode'); @@ -42,13 +43,14 @@ export default function ({ getService, getPageObjects }) { // Expect the discover still to list that saved visualization in the open list await PageObjects.header.clickDiscover(); - await PageObjects.discover.openSavedSearch(); + await PageObjects.discover.openLoadSavedSearchPanel(); const stillHasSaved = await PageObjects.discover.hasSavedSearch('visualize_lab_mode_test'); expect(stillHasSaved).to.be(true); log.info('found saved search after toggling enableLabs mode'); }); after(async () => { + await PageObjects.discover.closeLoadSaveSearchPanel(); await PageObjects.header.clickManagement(); await PageObjects.settings.clickKibanaSettings(); await PageObjects.settings.clearAdvancedSettings('visualize:enableLabs'); diff --git a/test/functional/page_objects/discover_page.js b/test/functional/page_objects/discover_page.js index 438aae6441fb16..b76959026a54c9 100644 --- a/test/functional/page_objects/discover_page.js +++ b/test/functional/page_objects/discover_page.js @@ -25,6 +25,7 @@ export function DiscoverPageProvider({ getService, getPageObjects }) { const retry = getService('retry'); const testSubjects = getService('testSubjects'); const find = getService('find'); + const flyout = getService('flyout'); const PageObjects = getPageObjects(['header', 'common']); const getRemote = () => ( @@ -71,24 +72,38 @@ export function DiscoverPageProvider({ getService, getPageObjects }) { return await Promise.all(headerElements.map(el => el.getVisibleText())); } - async openSavedSearch() { + async openLoadSavedSearchPanel() { + const isOpen = await testSubjects.exists('loadSearchForm'); + if (isOpen) { + return; + } + // We need this try loop here because previous actions in Discover like // saving a search cause reloading of the page and the "Open" menu item goes stale. await retry.try(async () => { await this.clickLoadSavedSearchButton(); await PageObjects.header.waitUntilLoadingHasFinished(); - const loadIsOpen = await testSubjects.exists('loadSearchForm'); - expect(loadIsOpen).to.be(true); + const isOpen = await testSubjects.exists('loadSearchForm'); + expect(isOpen).to.be(true); }); } + async closeLoadSaveSearchPanel() { + const isOpen = await testSubjects.exists('loadSearchForm'); + if (!isOpen) { + return; + } + + await flyout.close('loadSearchForm'); + } + async hasSavedSearch(searchName) { const searchLink = await find.byPartialLinkText(searchName); return searchLink.isDisplayed(); } async loadSavedSearch(searchName) { - await this.clickLoadSavedSearchButton(); + await this.openLoadSavedSearchPanel(); const searchLink = await find.byPartialLinkText(searchName); await searchLink.click(); await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/test/functional/services/dashboard/add_panel.js b/test/functional/services/dashboard/add_panel.js index d6d3ad6dd282f7..6973bf9ef0ddc1 100644 --- a/test/functional/services/dashboard/add_panel.js +++ b/test/functional/services/dashboard/add_panel.js @@ -151,7 +151,7 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { await this.clickSavedSearchTab(); await this.filterEmbeddableNames(searchName); - await testSubjects.click(`addPanel${searchName.split(' ').join('-')}`); + await testSubjects.click(`savedObjectTitle${searchName.split(' ').join('-')}`); await testSubjects.exists('addSavedSearchToDashboardSuccess'); await this.closeAddPanel(); } @@ -173,7 +173,7 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { log.debug(`DashboardAddPanel.addVisualization(${vizName})`); await this.ensureAddPanelIsShowing(); await this.filterEmbeddableNames(`"${vizName.replace('-', ' ')}"`); - await testSubjects.click(`addPanel${vizName.split(' ').join('-')}`); + await testSubjects.click(`savedObjectTitle${vizName.split(' ').join('-')}`); await this.closeAddPanel(); } @@ -188,7 +188,7 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { log.debug(`DashboardAddPanel.panelAddLinkExists(${name})`); await this.ensureAddPanelIsShowing(); await this.filterEmbeddableNames(`"${name}"`); - return await testSubjects.exists(`addPanel${name.split(' ').join('-')}`); + return await testSubjects.exists(`savedObjectTitle${name.split(' ').join('-')}`); } }; }