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

Push panel rendering logic out of dashboard #11618

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/core_plugins/kibana/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ module.exports = function (kibana) {
'navbarExtensions',
'managementSections',
'devTools',
'docViews'
'docViews',
'embeddableHandlers',
],
injectVars,
},
Expand Down
16 changes: 13 additions & 3 deletions src/core_plugins/kibana/public/dashboard/__tests__/panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,17 @@ describe('dashboard panel', function () {
function init(mockDocResponse) {
ngMock.module('kibana');
ngMock.inject(($rootScope, $compile, esAdmin) => {
sinon.stub(esAdmin, 'mget').returns(Promise.resolve({ docs: [ mockDocResponse ] }));
sinon.stub(esAdmin, 'mget', function (request) {
const response = {
docs: []
};
request.body.docs.forEach(() => {
response.docs.push(mockDocResponse);
});

return Promise.resolve(response);
});

sinon.stub(esAdmin.indices, 'getFieldMapping').returns(Promise.resolve({
'.kibana': {
mappings: {
Expand Down Expand Up @@ -63,7 +73,7 @@ describe('dashboard panel', function () {
expect($scope.error).to.be('Could not locate that visualization (id: foo1)');
parentScope.$digest();
const content = $el.find('.panel-content');
expect(content).to.have.length(0);
expect(content.children().length).to.be(0);
});
});

Expand All @@ -73,7 +83,7 @@ describe('dashboard panel', function () {
expect($scope.error).not.to.be.ok();
parentScope.$digest();
const content = $el.find('.panel-content');
expect(content).to.have.length(1);
expect(content.children().length).to.be.greaterThan(0);
});
});
});

This file was deleted.

This file was deleted.

40 changes: 6 additions & 34 deletions src/core_plugins/kibana/public/dashboard/panel/panel.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<div class="panel panel-default" ng-class="{'panel--edit-mode': !isViewOnlyMode()}" ng-switch on="panel.type" ng-if="savedObj || error">
<div class="panel panel-default" ng-class="{'panel--edit-mode': !isViewOnlyMode()}" ng-switch on="panel.type">
<div class="panel-heading">
<span
data-test-subj="dashboardPanelTitle"
class="panel-title"
aria-label="{{:: 'Dashboard panel: ' + savedObj.title }}"
aria-label="{{:: 'Dashboard panel: ' + title }}"
>
{{::savedObj.title}}
{{::title}}
</span>
<div class="kuiMicroButtonGroup">
<a
Expand Down Expand Up @@ -76,36 +76,8 @@
<span ng-bind="error"></span>
</div>

<visualize
ng-if="!error"
ng-switch-when="visualization"
vis="savedObj.vis"
search-source="savedObj.searchSource"
show-spy-panel="!isFullScreenMode"
ui-state="uiState"
data-shared-item
data-title="{{savedObj.title}}"
data-description="{{savedObj.description}}"
render-counter
class="panel-content">
</visualize>

<doc-table
ng-if="!error"
ng-switch-when="search"
search-source="savedObj.searchSource"
sorting="panel.sort"
columns="panel.columns"
data-shared-item
data-title="{{savedObj.title}}"
data-description="{{savedObj.description}}"
render-counter
<div
id="renderPanel"
class="panel-content"
filter="filter"
on-add-column="addColumn"
on-change-sort-order="setSortOrder"
on-move-column="moveColumn"
on-remove-column="removeColumn"
>
</doc-table>
></div>
</div>
132 changes: 45 additions & 87 deletions src/core_plugins/kibana/public/dashboard/panel/panel.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
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 { FilterManagerProvider } from 'ui/filter_manager';
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/registry/embeddable_handlers';

uiModules
.get('app/dashboard')
.directive('dashboardPanel', function (savedVisualizations, savedSearches, Notifier, Private, $injector, getObjectLoadersForDashboard) {
const filterManager = Private(FilterManagerProvider);

.directive('dashboardPanel', function (Notifier, Private, $injector) {
const services = savedObjectManagementRegistry.all().map(function (serviceObj) {
const service = $injector.get(serviceObj.service);
return {
Expand Down Expand Up @@ -84,94 +78,58 @@ uiModules
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();
});
const saveState = (panel) => {
$scope.panel = Object.assign($scope.panel, panel);
$scope.saveState();
};

// 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();
} else if ($scope.panel.type === savedSearches.type) {
// 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;
// TODO: vis actions should be generalized for use by all panel renderers, e.g. updateFilters, updateTimeRange.
const actions = {
getVisClickHandler: $scope.getVisClickHandler,
getVisBrushHandler: $scope.getVisBrushHandler,
saveState,
getIsViewOnlyMode: $scope.isViewOnlyMode,
createChildUiState: $scope.createChildUiState
};

$scope.setSortOrder = function setSortOrder(columnName, direction) {
$scope.panel.sort = [columnName, direction];
$scope.saveState();
};
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 id = $scope.panel.id;
const service = _.find(services, { type: type });
if (!service) return;

$scope.filter = function (field, value, operator) {
const index = $scope.savedObj.searchSource.get('index').id;
filterManager.add(field, value, operator, index);
};
$scope.editUrl = '#management/kibana/objects/' + service.name + '/' + id + '?notFound=' + error.savedObjectType;
};

const embeddableHandlers = Private(EmbeddableHandlersRegistryProvider);
const embeddableHandler = embeddableHandlers.byName[$scope.panel.type];
if (!embeddableHandler) {
handleError(`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;
};
$scope.editUrl = embeddableHandler.getEditPath($scope.panel);
embeddableHandler.getTitleFor($scope.panel).then(title => {
$scope.title = title;
});
$scope.loadedPanel =
embeddableHandler.renderAt(element.find('.panel-content').get(0), $scope.panel, actions).catch(handleError);
}
};
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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 { FilterManagerProvider } from 'ui/filter_manager';

export class SearchEmbeddableHandler {
constructor($compile, $rootScope, searchLoader, Private) {
this.$compile = $compile;
this.searchLoader = searchLoader;
this.filterManager = Private(FilterManagerProvider);
this.$rootScope = $rootScope;
this.name = 'search';
this.title = 'Saved Searches';
}

getEditPath(panel) {
return this.searchLoader.urlFor(panel.id);
}

canRenderType(type) {
return type === 'search';
}

getTitleFor(panel) {
return this.searchLoader.get(panel.id).then(savedObject => savedObject.title);
}

renderAt(domNode, panel, actions) {
return this.searchLoader.get(panel.id).then((savedObject) => {
const editUrl = this.searchLoader.urlFor(panel.id);
const searchScope = this.$rootScope.$new();
searchScope.editUrl = editUrl;
searchScope.savedObj = savedObject;
searchScope.panel = panel;

// 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.
actions.saveState({
columns: searchScope.panel.columns || searchScope.savedObj.columns,
sort: searchScope.panel.sort || searchScope.savedObj.sort
});

const uiState = savedObject.uiStateJSON ? JSON.parse(savedObject.uiStateJSON) : {};
searchScope.uiState = actions.createChildUiState(getPersistedStateId(panel), uiState);

searchScope.setSortOrder = function setSortOrder(columnName, direction) {
actions.saveState({ sort: [columnName, direction] });
};

searchScope.addColumn = function addColumn(columnName) {
savedObject.searchSource.get('index').popularizeField(columnName, 1);
columnActions.addColumn(searchScope.panel.columns, columnName);
actions.saveState({}); // sync to sharing url
};

searchScope.removeColumn = function removeColumn(columnName) {
savedObject.searchSource.get('index').popularizeField(columnName, 1);
columnActions.removeColumn(searchScope.panel.columns, columnName);
actions.saveState({}); // sync to sharing url
};

searchScope.moveColumn = function moveColumn(columnName, newIndex) {
columnActions.moveColumn(searchScope.panel.columns, columnName, newIndex);
actions.saveState({}); // sync to sharing url
};

searchScope.filter = function (field, value, operator) {
const index = savedObject.searchSource.get('index').id;
this.filterManager.add(field, value, operator, index);
};

const searchInstance = this.$compile(searchTemplate)(searchScope);
const rootNode = angular.element(domNode);
rootNode.append(searchInstance);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { SearchEmbeddableHandler } from './search_embeddable_handler';

export function searchEmbeddableHandlerProvider(Private) {
const SearchEmbeddableHandlerProvider = ($compile, $rootScope, savedSearches, Private) => {
return new SearchEmbeddableHandler($compile, $rootScope, savedSearches, Private);
};
return Private(SearchEmbeddableHandlerProvider);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<doc-table
search-source="savedObj.searchSource"
sorting="panel.sort"
columns="panel.columns"
data-shared-item
data-title="{{savedObj.title}}"
data-description="{{savedObj.description}}"
render-counter
class="panel-content"
filter="filter"
on-add-column="addColumn"
on-change-sort-order="setSortOrder"
on-move-column="moveColumn"
on-remove-column="removeColumn"
>
</doc-table>
Loading