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

[5.x] Dashboard landing page (#10003) #10065

Merged
merged 1 commit into from
Jan 25, 2017
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
<!-- Title. -->
<div
data-transclude-slot="topLeftCorner"
class="kuiLocalTitle"
>
<span ng-bind="getDashTitle()"></span>
<!-- Breadcrumbs. -->
<bread-crumbs title="getDashTitle()" use-links="true" omit-current-page="true"></bread-crumbs>
</div>

<!-- Search. -->
Expand Down
311 changes: 311 additions & 0 deletions src/core_plugins/kibana/public/dashboard/dashboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
import _ from 'lodash';
import angular from 'angular';
import chrome from 'ui/chrome';
import uiModules from 'ui/modules';
import uiRoutes from 'ui/routes';

import 'plugins/kibana/dashboard/grid';
import 'plugins/kibana/dashboard/panel/panel';

import dashboardTemplate from 'plugins/kibana/dashboard/dashboard.html';
import FilterBarQueryFilterProvider from 'ui/filter_bar/query_filter';
import DocTitleProvider from 'ui/doc_title';
import stateMonitorFactory from 'ui/state_management/state_monitor_factory';
import { getTopNavConfig } from './top_nav/get_top_nav_config';
import { createPanelState } from 'plugins/kibana/dashboard/panel/panel_state';
import { DashboardConstants } from './dashboard_constants';
import UtilsBrushEventProvider from 'ui/utils/brush_event';
import FilterBarFilterBarClickHandlerProvider from 'ui/filter_bar/filter_bar_click_handler';

const app = uiModules.get('app/dashboard', [
'elasticsearch',
'ngRoute',
'kibana/courier',
'kibana/config',
'kibana/notify',
'kibana/typeahead'
]);

uiRoutes
.when('/dashboard/create', {
template: dashboardTemplate,
resolve: {
dash: function (savedDashboards, courier) {
return savedDashboards.get()
.catch(courier.redirectWhenMissing({
'dashboard': '/dashboard'
}));
}
}
})
.when('/dashboard/:id', {
template: dashboardTemplate,
resolve: {
dash: function (savedDashboards, Notifier, $route, $location, courier) {
return savedDashboards.get($route.current.params.id)
.catch(courier.redirectWhenMissing({
'dashboard' : '/dashboard'
}));
}
}
});

app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter, kbnUrl, Private) {
const brushEvent = Private(UtilsBrushEventProvider);
const filterBarClickHandler = Private(FilterBarFilterBarClickHandlerProvider);

return {
restrict: 'E',
controllerAs: 'dashboardApp',
controller: function ($scope, $rootScope, $route, $routeParams, $location, Private, getAppState) {

const queryFilter = Private(FilterBarQueryFilterProvider);

const notify = new Notifier({
location: 'Dashboard'
});

const dash = $scope.dash = $route.current.locals.dash;

if (dash.timeRestore && dash.timeTo && dash.timeFrom && !getAppState.previouslyStored()) {
timefilter.time.to = dash.timeTo;
timefilter.time.from = dash.timeFrom;
if (dash.refreshInterval) {
timefilter.refreshInterval = dash.refreshInterval;
}
}

const matchQueryFilter = function (filter) {
return filter.query && filter.query.query_string && !filter.meta;
};

const extractQueryFromFilters = function (filters) {
const filter = _.find(filters, matchQueryFilter);
if (filter) return filter.query;
};

const stateDefaults = {
title: dash.title,
panels: dash.panelsJSON ? JSON.parse(dash.panelsJSON) : [],
options: dash.optionsJSON ? JSON.parse(dash.optionsJSON) : {},
uiState: dash.uiStateJSON ? JSON.parse(dash.uiStateJSON) : {},
query: extractQueryFromFilters(dash.searchSource.getOwn('filter')) || { query_string: { query: '*' } },
filters: _.reject(dash.searchSource.getOwn('filter'), matchQueryFilter),
};

let stateMonitor;
const $state = $scope.state = new AppState(stateDefaults);
const $uiState = $scope.uiState = $state.makeStateful('uiState');
const $appStatus = $scope.appStatus = this.appStatus = {};

$scope.$watchCollection('state.options', function (newVal, oldVal) {
if (!angular.equals(newVal, oldVal)) $state.save();
});

$scope.$watch('state.options.darkTheme', setDarkTheme);

$scope.topNavMenu = getTopNavConfig(kbnUrl);

$scope.refresh = _.bindKey(courier, 'fetch');

timefilter.enabled = true;
$scope.timefilter = timefilter;
$scope.$listen(timefilter, 'fetch', $scope.refresh);

courier.setRootSearchSource(dash.searchSource);

const docTitle = Private(DocTitleProvider);

function init() {
updateQueryOnRootSource();

if (dash.id) {
docTitle.change(dash.title);
}

initPanelIndexes();

// watch for state changes and update the appStatus.dirty value
stateMonitor = stateMonitorFactory.create($state, stateDefaults);
stateMonitor.onChange((status) => {
$appStatus.dirty = status.dirty;
});

$scope.$on('$destroy', () => {
stateMonitor.destroy();
dash.destroy();

// Remove dark theme to keep it from affecting the appearance of other apps.
setDarkTheme(false);
});

$scope.$emit('application.load');
}

function initPanelIndexes() {
// find the largest panelIndex in all the panels
let maxIndex = getMaxPanelIndex();

// ensure that all panels have a panelIndex
$scope.state.panels.forEach(function (panel) {
if (!panel.panelIndex) {
panel.panelIndex = maxIndex++;
}
});
}

function getMaxPanelIndex() {
let maxId = $scope.state.panels.reduce(function (id, panel) {
return Math.max(id, panel.panelIndex || id);
}, 0);
return ++maxId;
}

function updateQueryOnRootSource() {
const filters = queryFilter.getFilters();
if ($state.query) {
dash.searchSource.set('filter', _.union(filters, [{
query: $state.query
}]));
} else {
dash.searchSource.set('filter', filters);
}
}

function setDarkTheme(enabled) {
const theme = Boolean(enabled) ? 'theme-dark' : 'theme-light';
chrome.removeApplicationClass(['theme-dark', 'theme-light']);
chrome.addApplicationClass(theme);
}


/**
* Creates a child ui state for the panel. It's passed the ui state to use, but needs to
* be generated from the parent (why, I don't know yet).
* @param path {String} - the unique path for this ui state.
* @param uiState {Object} - the uiState for the child.
* @returns {Object}
*/
$scope.createChildUiState = function createChildUiState(path, uiState) {
return $scope.uiState.createChild(path, uiState, true);
};

$scope.brushEvent = brushEvent;
$scope.filterBarClickHandler = filterBarClickHandler;
$scope.expandedPanel = null;
$scope.hasExpandedPanel = () => $scope.expandedPanel !== null;
$scope.toggleExpandPanel = (panelIndex) => {
if ($scope.expandedPanel && $scope.expandedPanel.panelIndex === panelIndex) {
$scope.expandedPanel = null;
} else {
$scope.expandedPanel =
$scope.state.panels.find((panel) => panel.panelIndex === panelIndex);
}
};

// update root source when filters update
$scope.$listen(queryFilter, 'update', function () {
updateQueryOnRootSource();
$state.save();
});

// update data when filters fire fetch event
$scope.$listen(queryFilter, 'fetch', $scope.refresh);

$scope.getDashTitle = function () {
return dash.lastSavedTitle || `${dash.title} (unsaved)`;
};

$scope.newDashboard = function () {
kbnUrl.change('/dashboard', {});
};

$scope.filterResults = function () {
updateQueryOnRootSource();
$state.save();
$scope.refresh();
};

$scope.save = function () {
$state.save();

const timeRestoreObj = _.pick(timefilter.refreshInterval, ['display', 'pause', 'section', 'value']);

dash.panelsJSON = angular.toJson($state.panels);
dash.uiStateJSON = angular.toJson($uiState.getChanges());
dash.timeFrom = dash.timeRestore ? timefilter.time.from : undefined;
dash.timeTo = dash.timeRestore ? timefilter.time.to : undefined;
dash.refreshInterval = dash.timeRestore ? timeRestoreObj : undefined;
dash.optionsJSON = angular.toJson($state.options);

dash.save()
.then(function (id) {
stateMonitor.setInitialState($state.toJSON());
$scope.kbnTopNav.close('save');
if (id) {
notify.info('Saved Dashboard as "' + dash.title + '"');
if (dash.id !== $routeParams.id) {
kbnUrl.change('/dashboard/{{id}}', { id: dash.id });
} else {
docTitle.change(dash.lastSavedTitle);
}
}
})
.catch(notify.fatal);
};

let pendingVis = _.size($state.panels);
$scope.$on('ready:vis', function () {
if (pendingVis) pendingVis--;
if (pendingVis === 0) {
$state.save();
$scope.refresh();
}
});

// listen for notifications from the grid component that changes have
// been made, rather than watching the panels deeply
$scope.$on('change:vis', function () {
$state.save();
});

// called by the saved-object-finder when a user clicks a vis
$scope.addVis = function (hit) {
pendingVis++;
$state.panels.push(createPanelState(hit.id, 'visualization', getMaxPanelIndex()));
};

if ($route.current.params && $route.current.params[DashboardConstants.NEW_VISUALIZATION_ID_PARAM]) {
$scope.addVis({ id: $route.current.params[DashboardConstants.NEW_VISUALIZATION_ID_PARAM] });
kbnUrl.removeParam(DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM);
kbnUrl.removeParam(DashboardConstants.NEW_VISUALIZATION_ID_PARAM);
}

const addNewVis = function addNewVis() {
kbnUrl.change(`/visualize?${DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM}`);
};

$scope.addSearch = function (hit) {
pendingVis++;
$state.panels.push(createPanelState(hit.id, 'search', getMaxPanelIndex()));
};

// Setup configurable values for config directive, after objects are initialized
$scope.opts = {
dashboard: dash,
ui: $state.options,
save: $scope.save,
addVis: $scope.addVis,
addNewVis,
addSearch: $scope.addSearch,
timefilter: $scope.timefilter
};

init();

$scope.showEditHelpText = () => {
return !$scope.state.panels.length;
};
}
};
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

export const DashboardConstants = {
ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM: 'addToDashboard',
NEW_VISUALIZATION_ID_PARAM: 'addVisualization'
NEW_VISUALIZATION_ID_PARAM: 'addVisualization',
LANDING_PAGE_URL: '/dashboard'
};
Loading