- {{::savedObj.title}}
+ {{::title}}
-
-
-
-
-
+
diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel.js b/src/core_plugins/kibana/public/dashboard/panel/panel.js
index 173f0c0454812..2c2a53e1b643a 100644
--- a/src/core_plugins/kibana/public/dashboard/panel/panel.js
+++ b/src/core_plugins/kibana/public/dashboard/panel/panel.js
@@ -1,21 +1,16 @@
-import _ from 'lodash';
import 'ui/visualize';
import 'ui/doc_table';
-import * as columnActions from 'ui/doc_table/actions/columns';
-import 'plugins/kibana/dashboard/panel/get_object_loaders_for_dashboard';
import 'plugins/kibana/visualize/saved_visualizations';
import 'plugins/kibana/discover/saved_searches';
import { uiModules } from 'ui/modules';
import panelTemplate from 'plugins/kibana/dashboard/panel/panel.html';
import { savedObjectManagementRegistry } from 'plugins/kibana/management/saved_object_registry';
-import { getPersistedStateId } from 'plugins/kibana/dashboard/panel/panel_state';
-import { loadSavedObject } from 'plugins/kibana/dashboard/panel/load_saved_object';
import { DashboardViewMode } from '../dashboard_view_mode';
+import { EmbeddableHandlersRegistryProvider } from 'ui/embeddable/embeddable_handlers_registry';
uiModules
.get('app/dashboard')
-.directive('dashboardPanel', function (savedVisualizations, savedSearches, Notifier, Private, $injector, getObjectLoadersForDashboard) {
-
+.directive('dashboardPanel', function (Notifier, Private, $injector) {
const services = savedObjectManagementRegistry.all().map(function (serviceObj) {
const service = $injector.get(serviceObj.service);
return {
@@ -38,17 +33,6 @@ uiModules
* @type {boolean}
*/
isFullScreenMode: '=',
- /**
- * Used to create a child persisted state for the panel from parent state.
- * @type {function} - Returns a {PersistedState} child uiState for this scope.
- */
- createChildUiState: '=',
- /**
- * Registers an index pattern with the dashboard app used by this panel. Used by the filter bar for
- * generating field suggestions.
- * @type {function(IndexPattern)}
- */
- registerPanelIndexPattern: '=',
/**
* Contains information about this panel.
* @type {PanelState}
@@ -70,122 +54,58 @@ uiModules
*/
isExpanded: '=',
/**
- * Returns a click handler for a visualization.
- * @type {function}
- */
- getVisClickHandler: '=',
- /**
- * Returns a brush event handler for a visualization.
- * @type {function}
+ * @type {DashboardContainerApi}
*/
- getVisBrushHandler: '=',
- /**
- * Call when changes should be propagated to the url and thus saved in state.
- * @type {function}
- */
- saveState: '=',
- /**
- * Called when a filter action has been triggered
- * @type {function}
- */
- onFilter: '=',
- appState: '=',
+ containerApi: '='
},
link: function ($scope, element) {
if (!$scope.panel.id || !$scope.panel.type) return;
- /**
- * Initializes the panel for the saved object.
- * @param {{savedObj: SavedObject, editUrl: String}} savedObjectInfo
- */
- function initializePanel(savedObjectInfo) {
- $scope.savedObj = savedObjectInfo.savedObj;
- $scope.editUrl = savedObjectInfo.editUrl;
-
- element.on('$destroy', function () {
- $scope.savedObj.destroy();
- $scope.$destroy();
- });
-
- // create child ui state from the savedObj
- const uiState = $scope.savedObj.uiStateJSON ? JSON.parse($scope.savedObj.uiStateJSON) : {};
- $scope.uiState = $scope.createChildUiState(getPersistedStateId($scope.panel), uiState);
+ $scope.isViewOnlyMode = () => {
+ return $scope.dashboardViewMode === DashboardViewMode.VIEW || $scope.isFullScreenMode;
+ };
- if ($scope.panel.type === savedVisualizations.type && $scope.savedObj.vis) {
- $scope.savedObj.vis.setUiState($scope.uiState);
- $scope.savedObj.vis.listeners.click = $scope.getVisClickHandler();
- $scope.savedObj.vis.listeners.brush = $scope.getVisBrushHandler();
- $scope.registerPanelIndexPattern($scope.panel.panelIndex, $scope.savedObj.vis.indexPattern);
- } else if ($scope.panel.type === savedSearches.type) {
- if ($scope.savedObj.searchSource) {
- $scope.registerPanelIndexPattern($scope.panel.panelIndex, $scope.savedObj.searchSource.get('index'));
- }
- // This causes changes to a saved search to be hidden, but also allows
- // the user to locally modify and save changes to a saved search only in a dashboard.
- // See https://github.com/elastic/kibana/issues/9523 for more details.
- $scope.panel.columns = $scope.panel.columns || $scope.savedObj.columns;
- $scope.panel.sort = $scope.panel.sort || $scope.savedObj.sort;
+ const panelId = $scope.panel.id;
- $scope.setSortOrder = function setSortOrder(columnName, direction) {
- $scope.panel.sort = [columnName, direction];
- $scope.saveState();
- };
+ // TODO: This function contains too much internal panel knowledge. Logic should be pushed to embeddable handlers.
+ const handleError = (error) => {
+ $scope.error = error.message;
- $scope.addColumn = function addColumn(columnName) {
- $scope.savedObj.searchSource.get('index').popularizeField(columnName, 1);
- columnActions.addColumn($scope.panel.columns, columnName);
- $scope.saveState(); // sync to sharing url
- };
+ // Dashboard listens for this broadcast, once for every visualization (pendingVisCount).
+ // We need to broadcast even in the event of an error or it'll never fetch the data for
+ // other visualizations.
+ $scope.$root.$broadcast('ready:vis');
- $scope.removeColumn = function removeColumn(columnName) {
- $scope.savedObj.searchSource.get('index').popularizeField(columnName, 1);
- columnActions.removeColumn($scope.panel.columns, columnName);
- $scope.saveState(); // sync to sharing url
- };
+ // If the savedObjectType matches the panel type, this means the object itself has been deleted,
+ // so we shouldn't even have an edit link. If they don't match, it means something else is wrong
+ // with the object (but the object still exists), so we link to the object editor instead.
+ const objectItselfDeleted = error.savedObjectType === $scope.panel.type;
+ if (objectItselfDeleted) return;
- $scope.moveColumn = function moveColumn(columnName, newIndex) {
- columnActions.moveColumn($scope.panel.columns, columnName, newIndex);
- $scope.saveState(); // sync to sharing url
- };
- }
+ const type = $scope.panel.type;
+ const service = services.find(service => service.type === type);
+ if (!service) return;
- $scope.filter = function (field, value, operator) {
- const index = $scope.savedObj.searchSource.get('index').id;
- $scope.onFilter(field, value, operator, index);
- };
+ $scope.editUrl = '#management/kibana/objects/' + service.name + '/' + panelId + '?notFound=' + error.savedObjectType;
+ };
+ const embeddableHandlers = Private(EmbeddableHandlersRegistryProvider);
+ const embeddableHandler = embeddableHandlers.byName[$scope.panel.type];
+ if (!embeddableHandler) {
+ handleError(new Error(`No embeddable handler for panel type ${$scope.panel.type} was found.`));
+ return;
}
-
- $scope.loadedPanel = loadSavedObject(getObjectLoadersForDashboard(), $scope.panel)
- .then(initializePanel)
- .catch(function (e) {
- $scope.error = e.message;
-
- // Dashboard listens for this broadcast, once for every visualization (pendingVisCount).
- // We need to broadcast even in the event of an error or it'll never fetch the data for
- // other visualizations.
- $scope.$root.$broadcast('ready:vis');
-
- // If the savedObjectType matches the panel type, this means the object itself has been deleted,
- // so we shouldn't even have an edit link. If they don't match, it means something else is wrong
- // with the object (but the object still exists), so we link to the object editor instead.
- const objectItselfDeleted = e.savedObjectType === $scope.panel.type;
- if (objectItselfDeleted) return;
-
- const type = $scope.panel.type;
- const id = $scope.panel.id;
- const service = _.find(services, { type: type });
- if (!service) return;
-
- $scope.editUrl = '#management/kibana/objects/' + service.name + '/' + id + '?notFound=' + e.savedObjectType;
- });
-
- /**
- * @returns {boolean} True if the user can only view, not edit.
- */
- $scope.isViewOnlyMode = () => {
- return $scope.dashboardViewMode === DashboardViewMode.VIEW || $scope.isFullScreenMode;
- };
+ embeddableHandler.getEditPath(panelId).then(path => {
+ $scope.editUrl = path;
+ });
+ embeddableHandler.getTitleFor(panelId).then(title => {
+ $scope.title = title;
+ });
+ $scope.renderPromise = embeddableHandler.render(
+ element.find('#embeddedPanel').get(0),
+ $scope.panel,
+ $scope.containerApi)
+ .catch(handleError);
}
};
});
diff --git a/src/core_plugins/kibana/public/discover/embeddable/search_embeddable_handler.js b/src/core_plugins/kibana/public/discover/embeddable/search_embeddable_handler.js
new file mode 100644
index 0000000000000..1c12be3e8cab4
--- /dev/null
+++ b/src/core_plugins/kibana/public/discover/embeddable/search_embeddable_handler.js
@@ -0,0 +1,81 @@
+import searchTemplate from './search_template.html';
+import angular from 'angular';
+import * as columnActions from 'ui/doc_table/actions/columns';
+import { getPersistedStateId } from 'plugins/kibana/dashboard/panel/panel_state';
+import { EmbeddableHandler } from 'ui/embeddable';
+
+
+export class SearchEmbeddableHandler extends EmbeddableHandler {
+
+ constructor($compile, $rootScope, searchLoader, Promise) {
+ super();
+ this.$compile = $compile;
+ this.searchLoader = searchLoader;
+ this.$rootScope = $rootScope;
+ this.name = 'search';
+ this.Promise = Promise;
+ }
+
+ getEditPath(panelId) {
+ return this.Promise.resolve(this.searchLoader.urlFor(panelId));
+ }
+
+ getTitleFor(panelId) {
+ return this.searchLoader.get(panelId).then(savedObject => savedObject.title);
+ }
+
+ render(domNode, panel, container) {
+ const searchScope = this.$rootScope.$new();
+ return this.getEditPath(panel.id)
+ .then(editPath => {
+ searchScope.editPath = editPath;
+ return this.searchLoader.get(panel.id);
+ })
+ .then(savedObject => {
+ searchScope.savedObj = savedObject;
+ searchScope.panel = panel;
+ container.registerPanelIndexPattern(panel.panelIndex, savedObject.searchSource.get('index'));
+
+ // This causes changes to a saved search to be hidden, but also allows
+ // the user to locally modify and save changes to a saved search only in a dashboard.
+ // See https://github.com/elastic/kibana/issues/9523 for more details.
+ searchScope.panel = container.updatePanel(searchScope.panel.panelIndex, {
+ columns: searchScope.panel.columns || searchScope.savedObj.columns,
+ sort: searchScope.panel.sort || searchScope.savedObj.sort
+ });
+
+ const uiState = savedObject.uiStateJSON ? JSON.parse(savedObject.uiStateJSON) : {};
+ searchScope.uiState = container.createChildUistate(getPersistedStateId(panel), uiState);
+
+ searchScope.setSortOrder = function setSortOrder(columnName, direction) {
+ searchScope.panel = container.updatePanel(searchScope.panel.panelIndex, { sort: [columnName, direction] });
+ };
+
+ searchScope.addColumn = function addColumn(columnName) {
+ savedObject.searchSource.get('index').popularizeField(columnName, 1);
+ columnActions.addColumn(searchScope.panel.columns, columnName);
+ searchScope.panel = container.updatePanel(searchScope.panel.panelIndex, { columns: searchScope.panel.columns });
+ };
+
+ searchScope.removeColumn = function removeColumn(columnName) {
+ savedObject.searchSource.get('index').popularizeField(columnName, 1);
+ columnActions.removeColumn(searchScope.panel.columns, columnName);
+ searchScope.panel = container.updatePanel(searchScope.panel.panelIndex, { columns: searchScope.panel.columns });
+ };
+
+ searchScope.moveColumn = function moveColumn(columnName, newIndex) {
+ columnActions.moveColumn(searchScope.panel.columns, columnName, newIndex);
+ searchScope.panel = container.updatePanel(searchScope.panel.panelIndex, { columns: searchScope.panel.columns });
+ };
+
+ searchScope.filter = function (field, value, operator) {
+ const index = savedObject.searchSource.get('index').id;
+ container.addFilter(field, value, operator, index);
+ };
+
+ const searchInstance = this.$compile(searchTemplate)(searchScope);
+ const rootNode = angular.element(domNode);
+ rootNode.append(searchInstance);
+ });
+ }
+}
diff --git a/src/core_plugins/kibana/public/discover/embeddable/search_embeddable_handler_provider.js b/src/core_plugins/kibana/public/discover/embeddable/search_embeddable_handler_provider.js
new file mode 100644
index 0000000000000..d7fa5dc8b1f00
--- /dev/null
+++ b/src/core_plugins/kibana/public/discover/embeddable/search_embeddable_handler_provider.js
@@ -0,0 +1,12 @@
+import { SearchEmbeddableHandler } from './search_embeddable_handler';
+import { EmbeddableHandlersRegistryProvider } from 'ui/embeddable/embeddable_handlers_registry';
+
+export function searchEmbeddableHandlerProvider(Private) {
+ const SearchEmbeddableHandlerProvider = ($compile, $rootScope, savedSearches, Promise) => {
+ return new SearchEmbeddableHandler($compile, $rootScope, savedSearches, Promise);
+ };
+ return Private(SearchEmbeddableHandlerProvider);
+}
+
+
+EmbeddableHandlersRegistryProvider.register(searchEmbeddableHandlerProvider);
diff --git a/src/core_plugins/kibana/public/discover/embeddable/search_template.html b/src/core_plugins/kibana/public/discover/embeddable/search_template.html
new file mode 100644
index 0000000000000..0bb48561b27df
--- /dev/null
+++ b/src/core_plugins/kibana/public/discover/embeddable/search_template.html
@@ -0,0 +1,16 @@
+
+
diff --git a/src/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_handler.js b/src/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_handler.js
new file mode 100644
index 0000000000000..8bb5c6e9ccd8c
--- /dev/null
+++ b/src/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_handler.js
@@ -0,0 +1,63 @@
+import angular from 'angular';
+
+import visualizationTemplate from './visualize_template.html';
+import { getPersistedStateId } from 'plugins/kibana/dashboard/panel/panel_state';
+import { UtilsBrushEventProvider as utilsBrushEventProvider } from 'ui/utils/brush_event';
+import { FilterBarClickHandlerProvider as filterBarClickHandlerProvider } from 'ui/filter_bar/filter_bar_click_handler';
+import { EmbeddableHandler } from 'ui/embeddable';
+import chrome from 'ui/chrome';
+
+export class VisualizeEmbeddableHandler extends EmbeddableHandler {
+ constructor($compile, $rootScope, visualizeLoader, timefilter, Notifier, Promise) {
+ super();
+ this.$compile = $compile;
+ this.visualizeLoader = visualizeLoader;
+ this.$rootScope = $rootScope;
+ this.name = 'visualization';
+ this.Promise = Promise;
+ this.brushEvent = utilsBrushEventProvider(timefilter);
+ this.filterBarClickHandler = filterBarClickHandlerProvider(Notifier);
+ }
+
+ getEditPath(panelId) {
+ return this.Promise.resolve(this.visualizeLoader.urlFor(panelId));
+ }
+
+ getTitleFor(panelId) {
+ return this.visualizeLoader.get(panelId).then(savedObject => savedObject.title);
+ }
+
+ render(domNode, panel, container) {
+ const visualizeScope = this.$rootScope.$new();
+ return this.getEditPath(panel.id)
+ .then(editPath => {
+ visualizeScope.editUrl = editPath;
+ return this.visualizeLoader.get(panel.id);
+ })
+ .then(savedObject => {
+ visualizeScope.savedObj = savedObject;
+ visualizeScope.panel = panel;
+
+ const uiState = savedObject.uiStateJSON ? JSON.parse(savedObject.uiStateJSON) : {};
+ visualizeScope.uiState = container.createChildUistate(getPersistedStateId(panel), uiState);
+
+ visualizeScope.savedObj.vis.setUiState(visualizeScope.uiState);
+
+ visualizeScope.savedObj.vis.listeners.click = this.filterBarClickHandler(container.getAppState());
+ visualizeScope.savedObj.vis.listeners.brush = this.brushEvent(container.getAppState());
+ visualizeScope.isFullScreenMode = !chrome.getVisible();
+
+ container.registerPanelIndexPattern(panel.panelIndex, visualizeScope.savedObj.vis.indexPattern);
+
+ const visualizationInstance = this.$compile(visualizationTemplate)(visualizeScope);
+ const rootNode = angular.element(domNode);
+ rootNode.append(visualizationInstance);
+
+ visualizationInstance.on('$destroy', function () {
+ visualizeScope.savedObj.destroy();
+ visualizeScope.$destroy();
+ });
+ });
+ }
+}
+
diff --git a/src/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_handler_provider.js b/src/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_handler_provider.js
new file mode 100644
index 0000000000000..1f6704f1f921f
--- /dev/null
+++ b/src/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_handler_provider.js
@@ -0,0 +1,17 @@
+import { VisualizeEmbeddableHandler } from './visualize_embeddable_handler';
+import { EmbeddableHandlersRegistryProvider } from 'ui/embeddable/embeddable_handlers_registry';
+
+export function visualizeEmbeddableHandlerProvider(Private) {
+ const VisualizeEmbeddableHandlerProvider = (
+ $compile,
+ $rootScope,
+ savedVisualizations,
+ timefilter,
+ Notifier,
+ Promise) => {
+ return new VisualizeEmbeddableHandler($compile, $rootScope, savedVisualizations, timefilter, Notifier, Promise);
+ };
+ return Private(VisualizeEmbeddableHandlerProvider);
+}
+
+EmbeddableHandlersRegistryProvider.register(visualizeEmbeddableHandlerProvider);
diff --git a/src/core_plugins/kibana/public/visualize/embeddable/visualize_template.html b/src/core_plugins/kibana/public/visualize/embeddable/visualize_template.html
new file mode 100644
index 0000000000000..4a7d8fd6481a5
--- /dev/null
+++ b/src/core_plugins/kibana/public/visualize/embeddable/visualize_template.html
@@ -0,0 +1,11 @@
+
+
diff --git a/src/ui/public/embeddable/container_api.js b/src/ui/public/embeddable/container_api.js
new file mode 100644
index 0000000000000..e1471a88d7a70
--- /dev/null
+++ b/src/ui/public/embeddable/container_api.js
@@ -0,0 +1,51 @@
+/**
+ * The ContainerAPI is an interface for embeddable objects to interact with the container they are embedded within.
+ */
+export class ContainerAPI {
+ /**
+ * Available so the embeddable object can trigger a filter action.
+ * @param field
+ * @param value
+ * @param operator
+ * @param index
+ */
+ addFilter(/*field, value, operator, index */) {
+ throw new Error('Must implement addFilter.');
+ }
+
+ /**
+ * @return {AppState}
+ */
+ getAppState() {
+ throw new Error('Must implement getAppState.');
+ }
+
+ /**
+ * Creates a new state for the panel. It's passed the ui state object to use, and is returned
+ * a PersistedState.
+ * @param path {String} - the unique path for this ui state.
+ * @param initialState {Object} - the initial state to use for the child.
+ * @returns {PersistedState}
+ */
+ createChildUistate(/* path, initialState */) {
+ throw new Error('Must implement getInitalState.');
+ }
+
+ /**
+ * Call this to tell the container that this panel uses a particular index pattern.
+ * @param {string} panelIndex - a unique id that identifies the panel to update.
+ * @param {string} indexPattern - an index pattern the panel uses
+ */
+ registerPanelIndexPattern(/* panelIndex, indexPattern */) {
+ throw new Error('Must implement registerPanelIndexPattern.');
+ }
+
+ /**
+ * @param {string} panelIndex - a unique id that identifies the panel to update.
+ * @param {Object} panelAttributes - the new panel attributes that will be applied to the panel.
+ * @return {Object} - the updated panel.
+ */
+ updatePanel(/*paneIndex, panelAttributes */) {
+ throw new Error('Must implement updatePanel.');
+ }
+}
diff --git a/src/ui/public/embeddable/embeddable_handler.js b/src/ui/public/embeddable/embeddable_handler.js
new file mode 100644
index 0000000000000..aee16ca8e6049
--- /dev/null
+++ b/src/ui/public/embeddable/embeddable_handler.js
@@ -0,0 +1,33 @@
+/**
+ * The EmbeddableHandler defines how to render and embed any object into the Dashboard, or some other
+ * container that supports EmbeddableHandlers.
+ */
+export class EmbeddableHandler {
+ /**
+ * @param {string} panelId - the id of the panel to grab the title for.
+ * @return {Promise.
} a promise that resolves with the path that dictates where the user will be navigated to
+ * when they click the edit icon.
+ */
+ getEditPath(/* panelId */) {
+ throw new Error('Must implement getEditPath.');
+ }
+
+ /**
+ * @param {string} panelId - the id of the panel to grab the title for.
+ * @return {Promise.} - Promise that resolves with the title to display for the particular panel.
+ */
+ getTitleFor(/* panelId */) {
+ throw new Error('Must implement getTitleFor.');
+ }
+
+ /**
+ * @param {Element} domNode - the dom node to mount the rendered embeddable on
+ * @param {PanelState} panel - a panel object which container information about the panel. Can also be modified to
+ * store per panel information.
+ * @property {ContainerApi} containerApi - an id to specify the object that this panel contains.
+ * @param {Promise.} A promise that resolves when the object is finished rendering.
+ */
+ render(/* domNode, panel, container */) {
+ throw new Error('Must implement render.');
+ }
+}
diff --git a/src/ui/public/embeddable/embeddable_handlers_registry.js b/src/ui/public/embeddable/embeddable_handlers_registry.js
new file mode 100644
index 0000000000000..408288c842173
--- /dev/null
+++ b/src/ui/public/embeddable/embeddable_handlers_registry.js
@@ -0,0 +1,9 @@
+import { uiRegistry } from 'ui/registry/_registry';
+
+/**
+ * Registry of functions (EmbeddableHandlerProviders) which return an EmbeddableHandler.
+ */
+export const EmbeddableHandlersRegistryProvider = uiRegistry({
+ name: 'embeddableHandlers',
+ index: ['name']
+});
diff --git a/src/ui/public/embeddable/index.js b/src/ui/public/embeddable/index.js
new file mode 100644
index 0000000000000..bdd73ce99a110
--- /dev/null
+++ b/src/ui/public/embeddable/index.js
@@ -0,0 +1,3 @@
+export { EmbeddableHandler } from './embeddable_handler';
+export { EmbeddableHandlersRegistryProvider } from './embeddable_handlers_registry';
+export { ContainerAPI } from './container_api';
diff --git a/src/ui/ui_exports.js b/src/ui/ui_exports.js
index 70606553717ca..ba8c57f8479d2 100644
--- a/src/ui/ui_exports.js
+++ b/src/ui/ui_exports.js
@@ -21,6 +21,10 @@ export default class UiExports {
visEditorTypes: [
'ui/vis/editors/default/default',
],
+ embeddableHandlers: [
+ 'plugins/kibana/visualize/embeddable/visualize_embeddable_handler_provider',
+ 'plugins/kibana/discover/embeddable/search_embeddable_handler_provider',
+ ],
};
this.urlBasePath = urlBasePath;
this.exportConsumer = _.memoize(this.exportConsumer);
@@ -105,6 +109,7 @@ export default class UiExports {
case 'visRequestHandlers':
case 'visEditorTypes':
case 'savedObjectTypes':
+ case 'embeddableHandlers':
case 'fieldFormats':
case 'fieldFormatEditors':
case 'spyModes':
diff --git a/test/functional/apps/dashboard/_dashboard.js b/test/functional/apps/dashboard/_dashboard.js
index 0c1df9a6b0ab3..48f32a81e2231 100644
--- a/test/functional/apps/dashboard/_dashboard.js
+++ b/test/functional/apps/dashboard/_dashboard.js
@@ -182,7 +182,22 @@ export default function ({ getService, getPageObjects }) {
expect(spyToggleExists).to.be(true);
});
+ // This was an actual bug that appeared, where the spy pane appeared on panels after adding them, but
+ // disappeared when a new dashboard was opened up.
+ it('shows the spy pane toggle directly after opening a dashboard', async () => {
+ await PageObjects.dashboard.saveDashboard('spy pane test');
+ await PageObjects.dashboard.gotoDashboardLandingPage();
+ await PageObjects.dashboard.loadSavedDashboard('spy pane test');
+ const panels = await PageObjects.dashboard.getDashboardPanels();
+ // Simulate hover
+ await remote.moveMouseTo(panels[0]);
+ const spyToggleExists = await PageObjects.visualize.getSpyToggleExists();
+ expect(spyToggleExists).to.be(true);
+ });
+
it('shows other panels after being minimized', async () => {
+ // Panels are all minimized on a fresh open of a dashboard, so we need to re-expand in order to then minimize.
+ await PageObjects.dashboard.toggleExpandPanel();
await PageObjects.dashboard.toggleExpandPanel();
const panels = await PageObjects.dashboard.getDashboardPanels();
const visualizations = PageObjects.dashboard.getTestVisualizations();
@@ -222,6 +237,7 @@ export default function ({ getService, getPageObjects }) {
describe('full screen mode', () => {
it('option not available in edit mode', async () => {
+ await PageObjects.dashboard.clickEdit();
const exists = await PageObjects.dashboard.fullScreenModeMenuItemExists();
expect(exists).to.be(false);
});
diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js
index 90636becb209a..818b5d50c2c5b 100644
--- a/test/functional/apps/discover/_discover.js
+++ b/test/functional/apps/discover/_discover.js
@@ -310,10 +310,12 @@ export default function ({ getService, getPageObjects }) {
description: 'A Saved Search Description'
};
- await PageObjects.discover.loadSavedSearch(expected.title);
- const { title, description } = await PageObjects.common.getSharedItemTitleAndDescription();
- expect(title).to.eql(expected.title);
- expect(description).to.eql(expected.description);
+ await retry.try(async () => {
+ await PageObjects.discover.loadSavedSearch(expected.title);
+ const { title, description } = await PageObjects.common.getSharedItemTitleAndDescription();
+ expect(title).to.eql(expected.title);
+ expect(description).to.eql(expected.description);
+ });
});
});
});