From e7b5a8918343be4b2cfdd322b126ba865c1e7c75 Mon Sep 17 00:00:00 2001 From: Elastic Jasper Date: Wed, 7 Dec 2016 16:04:34 -0500 Subject: [PATCH] Make panel scope isolate, and some cleanup Backports PR #9335 **Commit 1:** make scope isolate * Original sha: c54eb3f32729dd188a3fdb7326cedb9970adca8b * Authored by Stacey Gammon on 2016-11-30T20:55:28Z **Commit 2:** Make panel scope isolate Also clean up and simplify scope destroying Fix sorting on scripted date and boolean fields (#9261) The elasticsearch API only [supports][1][2] sort scripts of type `number` and `string`. Since dates need to be returned as millis since the epoch for visualizations to work anyway, we can simply add a condition to send dates as type number in the sort API. ES will cast booleans if we tell them its a string, so we can add a similar condition there as well. [1]: https://www.elastic.co/guide/en/elasticsearch/reference/5.0/search-request-sort.html#_script_based_sorting [2]: https://github.com/elastic/elasticsearch/blob/aeb97ff41298e26b107a733837dfe17f123c0c9b/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java#L359 Fixes: https://github.com/elastic/kibana/issues/9257 Add docs on all available console server config options (#9288) settings: do not query ES for settings in non-green status (#9308) If the ui settings status is not green, that means there is at least one dependency (so elasticsearch at the moment) that is not met in order for it to function correctly, so we shouldn't attempt to determine user settings at all. This ensures that when something like the version check fails in the elasticsearch plugin, Kibana correctly behaves by not attempting requests to elasticsearch, which prevents 500 errors and allows users to see the error status on the status page. We also now periodically check for compatible elasticsearch versions so that Kibana can automatically recover if the elasticsearch node is upgraded to the appropriate version. Change loading screen background to white to make it less distracting when navigating between apps. (#9313) more refactoring Fix sorting on scripted date and boolean fields (#9261) The elasticsearch API only [supports][1][2] sort scripts of type `number` and `string`. Since dates need to be returned as millis since the epoch for visualizations to work anyway, we can simply add a condition to send dates as type number in the sort API. ES will cast booleans if we tell them its a string, so we can add a similar condition there as well. [1]: https://www.elastic.co/guide/en/elasticsearch/reference/5.0/search-request-sort.html#_script_based_sorting [2]: https://github.com/elastic/elasticsearch/blob/aeb97ff41298e26b107a733837dfe17f123c0c9b/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java#L359 Fixes: https://github.com/elastic/kibana/issues/9257 Add docs on all available console server config options (#9288) settings: do not query ES for settings in non-green status (#9308) If the ui settings status is not green, that means there is at least one dependency (so elasticsearch at the moment) that is not met in order for it to function correctly, so we shouldn't attempt to determine user settings at all. This ensures that when something like the version check fails in the elasticsearch plugin, Kibana correctly behaves by not attempting requests to elasticsearch, which prevents 500 errors and allows users to see the error status on the status page. We also now periodically check for compatible elasticsearch versions so that Kibana can automatically recover if the elasticsearch node is upgraded to the appropriate version. Change loading screen background to white to make it less distracting when navigating between apps. (#9313) Skip assertion when the test blips. (#9251) Tests fails intermittently, and for identical reasons. When this occurs, skip the test. This only occurs in CI and cannot be reproduced locally. Additional changes: - Use async/await to improve legibility. - Move async behaviour to beforeEach and keep test stubs synchronous to improve labeling of tests. [git] ignore extra files in the root config/ directory (#9296) upgrade makelogs (#9295) build: remove deepModules hackery (#9327) The deepModules hacks in the build system were added to support the long paths that resulted from npm2, but npm3 fundamentally addresses that problem, so deepModules is no longer necessary. In practical terms, npm3 shouldn't ever cause path lengths to become so long that they trigger path length problems on certain operating systems. more cleanup and tests Save/rename flow fix (#9087) * Save/rename flow fix - Use auto generated ids instead of coupling the id to the title which creates problems. - Adjust UI to make the save flow more understandable. - Remove confirmation on overwrite since we will now be creating duplicate objects even if they have the same title. * use undefined instead of null * Change titleChanged function name * address code review comments * Add isSaving flag to avoid checkbox flicker, fix regression bug from refactor. Added tests and fixed a couple bugs Updated info message * Update doc and nav title with new name on rename don't hardcode Dashboard * Original sha: e76f638a65ebd63e64bac35773f8df5d485bc32e * Authored by Stacey Gammon on 2016-12-01T18:54:50Z **Commit 3:** Merge branch 'master' of https://github.com/elastic/kibana into panel-refactor-cleanup * Original sha: 82323bee77a3385d1c1eac9457d5cd74d3c9b9d2 * Authored by Stacey Gammon on 2016-12-02T15:23:56Z **Commit 4:** namespace panel factory cleanup * Original sha: 3a09d660f0807d9c28404a014a4a6c7b7a615da5 * Authored by Stacey Gammon on 2016-12-02T15:31:09Z **Commit 5:** Fix parameter name in html * Original sha: 93afafa81098e5248844b046051035fc0c822b5f * Authored by Stacey Gammon on 2016-12-06T14:25:51Z **Commit 6:** Address comments - Get rid of factory function and Panel class. - Rename panel.js to panel_state.js - Rename dashboard_panel_directive to dashboard_panel * Original sha: aa8d6c8056e80cce8cec572f1dd707cb77e7852c * Authored by Stacey Gammon on 2016-12-06T15:27:58Z **Commit 7:** Merge branch 'master' of https://github.com/elastic/kibana into panel-refactor-cleanup * Original sha: cfd610a6ea445f012a24ad22fdc3de546977b4b1 * Authored by Stacey Gammon on 2016-12-06T16:04:56Z **Commit 8:** Fix file path reference in tests. * Original sha: 317e76eee53da48cd88566e3fe6ba29f9a966457 * Authored by Stacey Gammon on 2016-12-06T18:11:42Z **Commit 9:** Merge branch 'master' of https://github.com/elastic/kibana into panel-refactor-cleanup * Original sha: a6288ddeeca77b721e1461b992aab0fb6bd65c33 * Authored by Stacey Gammon on 2016-12-07T14:22:36Z **Commit 10:** Panel => PanelState * Original sha: c87f45b192f1060c924a0fb3045f0c308e06f8f5 * Authored by Stacey Gammon on 2016-12-07T14:32:08Z --- .../dashboard/__tests__/dashboard_panels.js | 33 +++++- .../components/panel/lib/panel_state.js | 35 ++++++ .../components/panel/lib/panel_utils.js | 45 ++++++++ .../dashboard/components/panel/panel.html | 8 +- .../dashboard/components/panel/panel.js | 85 -------------- .../dashboard/directives/dashboard_panel.js | 104 ++++++++++++++++++ .../public/dashboard/directives/grid.js | 93 +++++++--------- .../kibana/public/dashboard/index.js | 30 ++--- 8 files changed, 269 insertions(+), 164 deletions(-) create mode 100644 src/core_plugins/kibana/public/dashboard/components/panel/lib/panel_state.js create mode 100644 src/core_plugins/kibana/public/dashboard/components/panel/lib/panel_utils.js delete mode 100644 src/core_plugins/kibana/public/dashboard/components/panel/panel.js create mode 100644 src/core_plugins/kibana/public/dashboard/directives/dashboard_panel.js diff --git a/src/core_plugins/kibana/public/dashboard/__tests__/dashboard_panels.js b/src/core_plugins/kibana/public/dashboard/__tests__/dashboard_panels.js index 0611c8eba587a..fd7d982cd7194 100644 --- a/src/core_plugins/kibana/public/dashboard/__tests__/dashboard_panels.js +++ b/src/core_plugins/kibana/public/dashboard/__tests__/dashboard_panels.js @@ -2,12 +2,13 @@ import angular from 'angular'; import expect from 'expect.js'; import ngMock from 'ng_mock'; import 'plugins/kibana/dashboard/services/_saved_dashboard'; +import { DEFAULT_PANEL_WIDTH, DEFAULT_PANEL_HEIGHT } from '../components/panel/lib/panel_state'; describe('dashboard panels', function () { let $scope; let $el; - const compile = (dashboard) => { + function compile(dashboard) { ngMock.inject(($rootScope, $controller, $compile, $route) => { $scope = $rootScope.$new(); $route.current = { @@ -19,12 +20,16 @@ describe('dashboard panels', function () { $el = angular.element(` - `); + `); $compile($el)($scope); $scope.$digest(); }); }; + function findPanelWithVisualizationId(id) { + return $scope.state.panels.find((panel) => { return panel.id === id; }); + } + beforeEach(() => { ngMock.module('kibana'); }); @@ -77,10 +82,30 @@ describe('dashboard panels', function () { compile(dash); }); expect($scope.state.panels.length).to.be(16); - const foo8Panel = $scope.state.panels.find( - (panel) => { return panel.id === 'foo8'; }); + const foo8Panel = findPanelWithVisualizationId('foo8'); expect(foo8Panel).to.not.be(null); expect(foo8Panel.row).to.be(8); expect(foo8Panel.col).to.be(1); }); + + it('initializes visualizations with the default size', function () { + ngMock.inject((SavedDashboard) => { + let dash = new SavedDashboard(); + dash.init(); + dash.panelsJSON = `[ + {"col":3,"id":"foo1","row":1,"type":"visualization"}, + {"col":5,"id":"foo2","row":1,"size_x":5,"size_y":9,"type":"visualization"}]`; + compile(dash); + }); + expect($scope.state.panels.length).to.be(2); + const foo1Panel = findPanelWithVisualizationId('foo1'); + expect(foo1Panel).to.not.be(null); + expect(foo1Panel.size_x).to.be(DEFAULT_PANEL_WIDTH); + expect(foo1Panel.size_y).to.be(DEFAULT_PANEL_HEIGHT); + + const foo2Panel = findPanelWithVisualizationId('foo2'); + expect(foo2Panel).to.not.be(null); + expect(foo2Panel.size_x).to.be(5); + expect(foo2Panel.size_y).to.be(9); + }); }); diff --git a/src/core_plugins/kibana/public/dashboard/components/panel/lib/panel_state.js b/src/core_plugins/kibana/public/dashboard/components/panel/lib/panel_state.js new file mode 100644 index 0000000000000..55d58c88d7e21 --- /dev/null +++ b/src/core_plugins/kibana/public/dashboard/components/panel/lib/panel_state.js @@ -0,0 +1,35 @@ +export const DEFAULT_PANEL_WIDTH = 3; +export const DEFAULT_PANEL_HEIGHT = 2; + +/** + * Represents a panel on a grid. Keeps track of position in the grid and what visualization it + * contains. + * + * @typedef {Object} PanelState + * @property {number} id - Id of the visualization contained in the panel. + * @property {Element} $el - A reference to the gridster widget holding this panel. Used to + * update the size and column attributes. TODO: move out of panel state as this couples state to ui. + * @property {string} type - Type of the visualization in the panel. + * @property {number} panelId - Unique id to represent this panel in the grid. + * @property {number} size_x - Width of the panel. + * @property {number} size_y - Height of the panel. + * @property {number} col - Column index in the grid. + * @property {number} row - Row index in the grid. + */ + +/** + * Creates and initializes a basic panel state. + * @param {number} id + * @param {string} type + * @param {number} panelId + * @return {PanelState} + */ +export function createPanelState(id, type, panelId) { + return { + size_x: DEFAULT_PANEL_WIDTH, + size_y: DEFAULT_PANEL_HEIGHT, + panelId: panelId, + type: type, + id: id + }; +} diff --git a/src/core_plugins/kibana/public/dashboard/components/panel/lib/panel_utils.js b/src/core_plugins/kibana/public/dashboard/components/panel/lib/panel_utils.js new file mode 100644 index 0000000000000..5856d71f884f6 --- /dev/null +++ b/src/core_plugins/kibana/public/dashboard/components/panel/lib/panel_utils.js @@ -0,0 +1,45 @@ +import { DEFAULT_PANEL_WIDTH, DEFAULT_PANEL_HEIGHT } from 'plugins/kibana/dashboard/components/panel/lib/panel_state'; + +export class PanelUtils { + /** + * Fills in default parameters where not specified. + * @param {PanelState} panel + */ + static initializeDefaults(panel) { + panel.size_x = panel.size_x || DEFAULT_PANEL_WIDTH; + panel.size_y = panel.size_y || DEFAULT_PANEL_HEIGHT; + + if (!panel.id) { + // In the interest of backwards comparability + if (panel.visId) { + panel.id = panel.visId; + panel.type = 'visualization'; + delete panel.visId; + } else { + throw new Error('Missing object id on panel'); + } + } + } + + /** + * Ensures that the panel object has the latest size/pos info. + * @param {PanelState} panel + */ + static refreshSizeAndPosition(panel) { + const data = panel.$el.coords().grid; + panel.size_x = data.size_x; + panel.size_y = data.size_y; + panel.col = data.col; + panel.row = data.row; + } + + /** + * $el is a circular structure because it contains a reference to it's parent panel, + * so it needs to be removed before it can be serialized (we also don't + * want it to show up in the url). + * @param {PanelState} panel + */ + static makeSerializeable(panel) { + delete panel.$el; + } +} diff --git a/src/core_plugins/kibana/public/dashboard/components/panel/panel.html b/src/core_plugins/kibana/public/dashboard/components/panel/panel.html index 874a62b75522a..1cba6ed1d7ca5 100644 --- a/src/core_plugins/kibana/public/dashboard/components/panel/panel.html +++ b/src/core_plugins/kibana/public/dashboard/components/panel/panel.html @@ -4,13 +4,13 @@ {{::savedObj.title}} @@ -26,7 +26,7 @@ ng-switch-when="visualization" vis="savedObj.vis" search-source="savedObj.searchSource" - show-spy-panel="chrome.getVisible()" + show-spy-panel="!isFullScreenMode" ui-state="uiState" render-counter class="panel-content"> diff --git a/src/core_plugins/kibana/public/dashboard/components/panel/panel.js b/src/core_plugins/kibana/public/dashboard/components/panel/panel.js deleted file mode 100644 index b386335eb096d..0000000000000 --- a/src/core_plugins/kibana/public/dashboard/components/panel/panel.js +++ /dev/null @@ -1,85 +0,0 @@ -import _ from 'lodash'; -import 'ui/visualize'; -import 'ui/doc_table'; -import { loadPanelProvider } from 'plugins/kibana/dashboard/components/panel/lib/load_panel'; -import FilterManagerProvider from 'ui/filter_manager'; -import uiModules from 'ui/modules'; -import panelTemplate from 'plugins/kibana/dashboard/components/panel/panel.html'; - -uiModules -.get('app/dashboard') -.directive('dashboardPanel', function (savedVisualizations, savedSearches, Notifier, Private, $injector) { - const loadPanel = Private(loadPanelProvider); - const filterManager = Private(FilterManagerProvider); - - const services = require('plugins/kibana/management/saved_object_registry').all().map(function (serviceObj) { - const service = $injector.get(serviceObj.service); - return { - type: service.type, - name: serviceObj.service - }; - }); - - const getPanelId = function (panel) { - return ['P', panel.panelIndex].join('-'); - }; - - return { - restrict: 'E', - template: panelTemplate, - link: function ($scope) { - // using $scope inheritance, panels are available in AppState - const $state = $scope.state; - - // receives $scope.panel from the dashboard grid directive, seems like should be isolate? - $scope.$watch('id', function () { - if (!$scope.panel.id || !$scope.panel.type) return; - - loadPanel($scope.panel, $scope) - .then(function (panelConfig) { - // These could be done in loadPanel, putting them here to make them more explicit - $scope.savedObj = panelConfig.savedObj; - $scope.editUrl = panelConfig.editUrl; - $scope.$on('$destroy', function () { - panelConfig.savedObj.destroy(); - $scope.parentUiState.removeChild(getPanelId(panelConfig.panel)); - }); - - // create child ui state from the savedObj - const uiState = panelConfig.uiState || {}; - $scope.uiState = $scope.parentUiState.createChild(getPanelId(panelConfig.panel), uiState, true); - const panelSavedVis = _.get(panelConfig, 'savedObj.vis'); // Sometimes this will be a search, and undef - if (panelSavedVis) { - panelSavedVis.setUiState($scope.uiState); - } - - $scope.filter = function (field, value, operator) { - const index = $scope.savedObj.searchSource.get('index').id; - filterManager.add(field, value, operator, index); - }; - }) - .catch(function (e) { - $scope.error = e.message; - - // 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; - }); - - }); - - $scope.remove = function () { - _.pull($state.panels, $scope.panel); - }; - } - }; -}); diff --git a/src/core_plugins/kibana/public/dashboard/directives/dashboard_panel.js b/src/core_plugins/kibana/public/dashboard/directives/dashboard_panel.js new file mode 100644 index 0000000000000..ed1736349991d --- /dev/null +++ b/src/core_plugins/kibana/public/dashboard/directives/dashboard_panel.js @@ -0,0 +1,104 @@ +import _ from 'lodash'; +import 'ui/visualize'; +import 'ui/doc_table'; +import { loadPanelProvider } from 'plugins/kibana/dashboard/components/panel/lib/load_panel'; +import FilterManagerProvider from 'ui/filter_manager'; +import uiModules from 'ui/modules'; +import panelTemplate from 'plugins/kibana/dashboard/components/panel/panel.html'; + +uiModules +.get('app/dashboard') +.directive('dashboardPanel', function (savedVisualizations, savedSearches, Notifier, Private, $injector) { + const loadPanel = Private(loadPanelProvider); + const filterManager = Private(FilterManagerProvider); + + const services = require('plugins/kibana/management/saved_object_registry').all().map(function (serviceObj) { + const service = $injector.get(serviceObj.service); + return { + type: service.type, + name: serviceObj.service + }; + }); + + /** + * Returns a unique id for storing the panel state in the persistent ui. + * @param {PanelState} panel + * @returns {string} + */ + const getPersistedStateId = function (panel) { + return `P-${panel.panelId}`; + }; + + return { + restrict: 'E', + template: panelTemplate, + scope: { + /** + * Whether or not the dashboard this panel is contained on is in 'full screen mode'. + * @type {boolean} + */ + isFullScreenMode: '=', + /** + * The parent's persisted state is used to create a child persisted state for the + * panel. + * @type {PersistedState} + */ + parentUiState: '=', + /** + * Contains information about this panel. + * @type {PanelState} + */ + panel: '=', + /** + * Handles removing this panel from the grid. + * @type {() => void} + */ + remove: '&' + }, + link: function ($scope, element) { + if (!$scope.panel.id || !$scope.panel.type) return; + + loadPanel($scope.panel, $scope) + .then(function (panelConfig) { + // These could be done in loadPanel, putting them here to make them more explicit + $scope.savedObj = panelConfig.savedObj; + $scope.editUrl = panelConfig.editUrl; + + element.on('$destroy', function () { + panelConfig.savedObj.destroy(); + $scope.parentUiState.removeChild(getPersistedStateId(panelConfig.panel)); + $scope.$destroy(); + }); + + // create child ui state from the savedObj + const uiState = panelConfig.uiState || {}; + $scope.uiState = $scope.parentUiState.createChild(getPersistedStateId(panelConfig.panel), uiState, true); + const panelSavedVis = _.get(panelConfig, 'savedObj.vis'); // Sometimes this will be a search, and undef + if (panelSavedVis) { + panelSavedVis.setUiState($scope.uiState); + } + + $scope.filter = function (field, value, operator) { + const index = $scope.savedObj.searchSource.get('index').id; + filterManager.add(field, value, operator, index); + }; + }) + .catch(function (e) { + $scope.error = e.message; + + // 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; + }); + } + }; +}); diff --git a/src/core_plugins/kibana/public/dashboard/directives/grid.js b/src/core_plugins/kibana/public/dashboard/directives/grid.js index 2204ff6d2779e..2dc6822ef0751 100644 --- a/src/core_plugins/kibana/public/dashboard/directives/grid.js +++ b/src/core_plugins/kibana/public/dashboard/directives/grid.js @@ -3,6 +3,7 @@ import $ from 'jquery'; import Binder from 'ui/binder'; import 'gridster'; import uiModules from 'ui/modules'; +import { PanelUtils } from 'plugins/kibana/dashboard/components/panel/lib/panel_utils'; const app = uiModules.get('app/dashboard'); @@ -33,6 +34,24 @@ app.directive('dashboardGrid', function ($compile, Notifier) { // debounced layout function is safe to call as much as possible const safeLayout = _.debounce(layout, 200); + $scope.removePanelFromState = (panelId) => { + _.remove($scope.state.panels, function (panel) { + return panel.panelId === panelId; + }); + }; + + /** + * Removes the panel with the given id from the $scope.state.panels array. Does not + * remove the ui element from gridster - that is triggered by a watcher that is + * triggered on changes made to $scope.state.panels. + * @param panelId {number} + */ + $scope.getPanelByPanelId = (panelId) => { + return _.find($scope.state.panels, function (panel) { + return panel.panelId === panelId; + }); + }; + function init() { $el.addClass('gridster'); @@ -90,7 +109,7 @@ app.directive('dashboardGrid', function ($compile, Notifier) { }; // ensure that every panel can be serialized now that we are done - $state.panels.forEach(makePanelSerializeable); + $state.panels.forEach(PanelUtils.makeSerializeable); // alert interested parties that we have finished processing changes to the panels // TODO: change this from event based to calling a method on dashboardApp @@ -108,7 +127,7 @@ app.directive('dashboardGrid', function ($compile, Notifier) { panel.$el.stop(); removePanel(panel, true); // not that we will, but lets be safe - makePanelSerializeable(panel); + PanelUtils.makeSerializeable(panel); }); }); @@ -121,81 +140,44 @@ app.directive('dashboardGrid', function ($compile, Notifier) { // return the panel object for an element. // // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // ALWAYS CALL makePanelSerializeable AFTER YOU ARE DONE WITH IT + // ALWAYS CALL PanelUtils.makeSerializeable AFTER YOU ARE DONE WITH IT // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! function getPanelFor(el) { const $panel = el.jquery ? el : $(el); const panel = $panel.data('panel'); - panel.$el = $panel; - panel.$scope = $panel.data('$scope'); - return panel; } - // since the $el and $scope are circular structures, they need to be - // removed from panel before it can be serialized (we also wouldn't - // want them to show up in the url) - function makePanelSerializeable(panel) { - delete panel.$el; - delete panel.$scope; - } - // tell gridster to remove the panel, and cleanup our metadata function removePanel(panel, silent) { // remove from grister 'silently' (don't reorganize after) gridster.remove_widget(panel.$el, silent); - - // destroy the scope - panel.$scope.$destroy(); - panel.$el.removeData('panel'); - panel.$el.removeData('$scope'); } // tell gridster to add the panel, and create additional meatadata like $scope function addPanel(panel) { - _.defaults(panel, { - size_x: 3, - size_y: 2 - }); - - // ignore panels that don't have vis id's - if (!panel.id) { - // In the interest of backwards compat - if (panel.visId) { - panel.id = panel.visId; - panel.type = 'visualization'; - delete panel.visId; - } else { - throw new Error('missing object id on panel'); - } - } + PanelUtils.initializeDefaults(panel); - panel.$scope = $scope.$new(); - panel.$scope.panel = panel; - panel.$scope.parentUiState = $scope.uiState; - - panel.$el = $compile('
  • ')(panel.$scope); + const panelHtml = ` +
  • + +
  • `; + panel.$el = $compile(panelHtml)($scope); // tell gridster to use the widget gridster.add_widget(panel.$el, panel.size_x, panel.size_y, panel.col, panel.row); - // update size/col/etc. - refreshPanelStats(panel); + // Gridster may change the position of the widget when adding it, make sure the panel + // contains the latest info. + PanelUtils.refreshSizeAndPosition(panel); - // stash the panel and it's scope in the element's data + // stash the panel in the element's data panel.$el.data('panel', panel); - panel.$el.data('$scope', panel.$scope); - } - - // ensure that the panel object has the latest size/pos info - function refreshPanelStats(panel) { - const data = panel.$el.coords().grid; - panel.size_x = data.size_x; - panel.size_y = data.size_y; - panel.col = data.col; - panel.row = data.row; } // when gridster tell us it made a change, update each of the panel objects @@ -203,9 +185,8 @@ app.directive('dashboardGrid', function ($compile, Notifier) { // ensure that our panel objects keep their size in sync gridster.$widgets.each(function (i, el) { const panel = getPanelFor(el); - refreshPanelStats(panel); - panel.$scope.$broadcast('resize'); - makePanelSerializeable(panel); + PanelUtils.refreshSizeAndPosition(panel); + PanelUtils.makeSerializeable(panel); $scope.$root.$broadcast('change:vis'); }); } diff --git a/src/core_plugins/kibana/public/dashboard/index.js b/src/core_plugins/kibana/public/dashboard/index.js index 05bdabc73ba43..607633f747aff 100644 --- a/src/core_plugins/kibana/public/dashboard/index.js +++ b/src/core_plugins/kibana/public/dashboard/index.js @@ -7,7 +7,7 @@ import 'ui/notify'; import 'ui/typeahead'; import 'ui/share'; import 'plugins/kibana/dashboard/directives/grid'; -import 'plugins/kibana/dashboard/components/panel/panel'; +import 'plugins/kibana/dashboard/directives/dashboard_panel'; import 'plugins/kibana/dashboard/services/saved_dashboards'; import 'plugins/kibana/dashboard/styles/main.less'; import FilterBarQueryFilterProvider from 'ui/filter_bar/query_filter'; @@ -17,6 +17,7 @@ import uiRoutes from 'ui/routes'; import uiModules from 'ui/modules'; import indexTemplate from 'plugins/kibana/dashboard/index.html'; import { savedDashboardRegister } from 'plugins/kibana/dashboard/services/saved_dashboard_register'; +import { createPanelState } from 'plugins/kibana/dashboard/components/panel/lib/panel_state'; require('ui/saved_objects/saved_object_registry').register(savedDashboardRegister); const app = uiModules.get('app/dashboard', [ @@ -152,7 +153,7 @@ app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter, docTitle.change(dash.title); } - initPanelIndices(); + initPanelIds(); // watch for state changes and update the appStatus.dirty value stateMonitor = stateMonitorFactory.create($state, stateDefaults); @@ -171,24 +172,23 @@ app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter, $scope.$emit('application.load'); } - function initPanelIndices() { - // find the largest panelIndex in all the panels - let maxIndex = getMaxPanelIndex(); + function initPanelIds() { + // find the largest panelId in all the panels + let maxIndex = getMaxPanelId(); - // ensure that all panels have a panelIndex + // ensure that all panels have a panelId $scope.state.panels.forEach(function (panel) { - if (!panel.panelIndex) { - panel.panelIndex = maxIndex++; + if (!panel.panelId) { + panel.panelId = maxIndex++; } }); } - function getMaxPanelIndex() { - let index = $scope.state.panels.reduce(function (idx, panel) { - // if panel is missing an index, add one and increment the index - return Math.max(idx, panel.panelIndex || idx); + function getMaxPanelId() { + let maxId = $scope.state.panels.reduce(function (id, panel) { + return Math.max(id, panel.panelId || id); }, 0); - return ++index; + return ++maxId; } function updateQueryOnRootSource() { @@ -272,12 +272,12 @@ app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter, // called by the saved-object-finder when a user clicks a vis $scope.addVis = function (hit) { pendingVis++; - $state.panels.push({ id: hit.id, type: 'visualization', panelIndex: getMaxPanelIndex() }); + $state.panels.push(createPanelState(hit.id, 'visualization', getMaxPanelId())); }; $scope.addSearch = function (hit) { pendingVis++; - $state.panels.push({ id: hit.id, type: 'search', panelIndex: getMaxPanelIndex() }); + $state.panels.push(createPanelState(hit.id, 'search', getMaxPanelId())); }; // Setup configurable values for config directive, after objects are initialized