Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make panel scope isolate, and some cleanup #9335

Merged
merged 10 commits into from
Dec 7, 2016
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -19,12 +20,16 @@ describe('dashboard panels', function () {
$el = angular.element(`
<dashboard-app>
<dashboard-grid style="width: 600px; height: 600px;"></dashboard-grid>
</<dashboard-app>`);
</dashboard-app>`);
$compile($el)($scope);
$scope.$digest();
});
};

function findPanelWithVisualizationId(id) {
return $scope.state.panels.find((panel) => { return panel.id === id; });
}

beforeEach(() => {
ngMock.module('kibana');
});
Expand Down Expand Up @@ -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);
});
});
Original file line number Diff line number Diff line change
@@ -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
};
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
{{::savedObj.title}}
</span>
<div class="btn-group">
<a aria-label="Edit" ng-show="chrome.getVisible() && editUrl" ng-href="{{::editUrl}}">
<a aria-label="Edit" ng-show="!isFullScreenMode && editUrl" ng-href="{{::editUrl}}">
<i aria-hidden="true" class="fa fa-pencil"></i>
</a>
<a aria-label="Move" ng-show="chrome.getVisible()" class="panel-move">
<a aria-label="Move" ng-show="!isFullScreenMode" class="panel-move">
<i aria-hidden="true" class="fa fa-arrows"></i>
</a>
<a aria-label="Remove" ng-show="chrome.getVisible()" ng-click="remove()">
<a aria-label="Remove" ng-show="!isFullScreenMode" ng-click="remove()">
<i aria-hidden="true" class="fa fa-times"></i>
</a>
</div>
Expand All @@ -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">
Expand Down
85 changes: 0 additions & 85 deletions src/core_plugins/kibana/public/dashboard/components/panel/panel.js

This file was deleted.

104 changes: 104 additions & 0 deletions src/core_plugins/kibana/public/dashboard/directives/dashboard_panel.js
Original file line number Diff line number Diff line change
@@ -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;
});
}
};
});
Loading