mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
* Introduce dashboard landing page Still TODO: - Add clickable breadcrumbs - Remove New and Open top nav options * use clickable breadcrumbs instead of 'New' and 'Open' nav items * code cleanup improve tests * Add new empty dashboard styling * Fix selenium tests * Address code review comments - rename bread_crumb_url => bread_crumb_urls - fix reporting link in dash (separate PR) - Use NoItems instead of PromptForItems when searching - Style fixes * Use a constant for the landing page url * Fix tests fix tests
This commit is contained in:
parent
b9d99d30b9
commit
3e6e8a7bb5
14 changed files with 705 additions and 375 deletions
|
@ -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. -->
|
311
src/core_plugins/kibana/public/dashboard/dashboard.js
Normal file
311
src/core_plugins/kibana/public/dashboard/dashboard.js
Normal file
|
@ -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;
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
|
@ -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'
|
||||
};
|
||||
|
|
|
@ -1,319 +1,21 @@
|
|||
import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
import chrome from 'ui/chrome';
|
||||
import 'ui/courier';
|
||||
import 'ui/config';
|
||||
import 'ui/notify';
|
||||
import 'ui/typeahead';
|
||||
import 'ui/share';
|
||||
import 'plugins/kibana/dashboard/grid';
|
||||
import 'plugins/kibana/dashboard/panel/panel';
|
||||
import 'plugins/kibana/dashboard/dashboard';
|
||||
import 'plugins/kibana/dashboard/saved_dashboard/saved_dashboards';
|
||||
import 'plugins/kibana/dashboard/styles/index.less';
|
||||
import FilterBarQueryFilterProvider from 'ui/filter_bar/query_filter';
|
||||
import DocTitleProvider from 'ui/doc_title';
|
||||
import stateMonitorFactory from 'ui/state_management/state_monitor_factory';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import uiModules from 'ui/modules';
|
||||
import indexTemplate from 'plugins/kibana/dashboard/index.html';
|
||||
import { savedDashboardRegister } from 'plugins/kibana/dashboard/saved_dashboard/saved_dashboard_register';
|
||||
import { getTopNavConfig } from './top_nav/get_top_nav_config';
|
||||
import { createPanelState } from 'plugins/kibana/dashboard/panel/panel_state';
|
||||
|
||||
import dashboardListingTemplate from './listing/dashboard_listing.html';
|
||||
import { DashboardListingController } from './listing/dashboard_listing';
|
||||
import { DashboardConstants } from './dashboard_constants';
|
||||
import UtilsBrushEventProvider from 'ui/utils/brush_event';
|
||||
import FilterBarFilterBarClickHandlerProvider from 'ui/filter_bar/filter_bar_click_handler';
|
||||
|
||||
require('ui/saved_objects/saved_object_registry').register(savedDashboardRegister);
|
||||
|
||||
const app = uiModules.get('app/dashboard', [
|
||||
'elasticsearch',
|
||||
'ngRoute',
|
||||
'kibana/courier',
|
||||
'kibana/config',
|
||||
'kibana/notify',
|
||||
'kibana/typeahead'
|
||||
]);
|
||||
|
||||
uiRoutes
|
||||
.defaults(/dashboard/, {
|
||||
requireDefaultIndex: true
|
||||
})
|
||||
.when('/dashboard', {
|
||||
template: indexTemplate,
|
||||
resolve: {
|
||||
dash: function (savedDashboards, config) {
|
||||
return savedDashboards.get();
|
||||
}
|
||||
}
|
||||
})
|
||||
.when('/dashboard/:id', {
|
||||
template: indexTemplate,
|
||||
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;
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
.defaults(/dashboard/, {
|
||||
requireDefaultIndex: true
|
||||
})
|
||||
.when(DashboardConstants.LANDING_PAGE_URL, {
|
||||
template: dashboardListingTemplate,
|
||||
controller: DashboardListingController,
|
||||
controllerAs: 'listingController'
|
||||
});
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
<!-- Local nav. -->
|
||||
<kbn-top-nav name="dashboard">
|
||||
<!-- Transcluded elements. -->
|
||||
<div data-transclude-slots>
|
||||
<!-- Title. -->
|
||||
<div
|
||||
data-transclude-slot="topLeftCorner"
|
||||
class="kuiLocalTitle"
|
||||
>
|
||||
Dashboard
|
||||
</div>
|
||||
</div>
|
||||
</kbn-top-nav>
|
||||
|
||||
<div class="kuiViewContent kuiViewContent--constrainedWidth">
|
||||
<!-- ControlledTable -->
|
||||
<div class="kuiViewContentItem kuiControlledTable kuiVerticalRhythm">
|
||||
<!-- ToolBar -->
|
||||
<div class="kuiToolBar">
|
||||
<div class="kuiToolBarSearch">
|
||||
<div class="kuiToolBarSearchBox">
|
||||
<div class="kuiToolBarSearchBox__icon kuiIcon fa-search"></div>
|
||||
<input
|
||||
class="kuiToolBarSearchBox__input"
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
aria-label="Filter"
|
||||
data-test-subj="searchFilter"
|
||||
ng-model="listingController.filter"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kuiToolBarSection">
|
||||
<!-- We need an empty section for the buttons to be positioned consistently. -->
|
||||
</div>
|
||||
|
||||
<div class="kuiToolBarSection">
|
||||
<!-- Bulk delete button -->
|
||||
<button
|
||||
class="kuiButton kuiButton--danger"
|
||||
ng-click="listingController.deleteSelectedItems()"
|
||||
aria-label="Delete selected objects"
|
||||
ng-hide="listingController.getSelectedItemsCount() === 0"
|
||||
tooltip="Delete selected dashboards"
|
||||
>
|
||||
<span aria-hidden="true" class="kuiButton__icon kuiIcon fa-trash"></span>
|
||||
</button>
|
||||
|
||||
<!-- Create dashboard button -->
|
||||
<a
|
||||
class="kuiButton kuiButton--primary"
|
||||
href="#/dashboard/create"
|
||||
aria-label="Create new dashboard"
|
||||
data-test-subj="newDashboardLink"
|
||||
ng-hide="listingController.getSelectedItemsCount() > 0"
|
||||
tooltip="Create new dashboard"
|
||||
>
|
||||
<span aria-hidden="true" class="kuiButton__icon kuiIcon fa-plus"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- NoResults -->
|
||||
<div ng-if="!listingController.items.length && listingController.filter" class="kuiPanel kuiPanel--centered">
|
||||
<div class="kuiNoItems">
|
||||
No dashboards matched your search.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PromptForItems -->
|
||||
<div class="kuiPanel kuiPanel--centered" ng-if="!listingController.items.length && !listingController.filter">
|
||||
<div class="kuiPromptForItems">
|
||||
<div class="kuiPromptForItems__message">
|
||||
Looks like you don’t have any dashboards. Let’s add some!
|
||||
</div>
|
||||
|
||||
<div class="kuiPromptForItems__actions">
|
||||
<a
|
||||
class="kuiButton kuiButton--primary kuiButton--iconText"
|
||||
href="#/dashboard/create"
|
||||
>
|
||||
<span class="kuiButton__icon kuiIcon fa-plus"></span>
|
||||
Add a dashboard
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<table class="kuiTable" ng-if="listingController.items.length">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="kuiTableHeaderCell kuiTableHeaderCell--checkBox">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="kuiCheckBox"
|
||||
ng-checked="listingController.areAllItemsChecked()"
|
||||
ng-click="listingController.toggleAll()"
|
||||
>
|
||||
</th>
|
||||
|
||||
<th class="kuiTableHeaderCell" ng-click="listingController.sortHits()">
|
||||
Name
|
||||
<span
|
||||
class="fa"
|
||||
ng-class="listingController.isAscending ? 'fa-caret-up' : 'fa-caret-down'">
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
ng-repeat="item in listingController.items track by item.id | orderBy:'title'"
|
||||
class="kuiTableRow"
|
||||
>
|
||||
<td class="kuiTableRowCell kuiTableRowCell--checkBox">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="kuiCheckBox"
|
||||
ng-click="listingController.toggleItem(item)"
|
||||
ng-checked="listingController.isItemChecked(item)"
|
||||
>
|
||||
</td>
|
||||
|
||||
<td class="kuiTableRowCell">
|
||||
<div class="kuiTableRowCell__liner">
|
||||
<a class="kuiLink" ng-click="listingController.open(item)">
|
||||
{{ item.title }}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- ToolBarFooter -->
|
||||
<div class="kuiToolBarFooter">
|
||||
<div class="kuiToolBarFooterSection">
|
||||
<div class="kuiToolBarText" ng-hide="listingController.getSelectedItemsCount() === 0">
|
||||
{{ listingController.getSelectedItemsCount() }} selected
|
||||
</div>
|
||||
</div>
|
||||
<div class="kuiToolBarFooterSection">
|
||||
<!-- We need an empty section for the buttons to be positioned consistently. -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,104 @@
|
|||
import SavedObjectRegistryProvider from 'ui/saved_objects/saved_object_registry';
|
||||
import { DashboardConstants } from '../dashboard_constants';
|
||||
import _ from 'lodash';
|
||||
|
||||
export function DashboardListingController(
|
||||
$scope,
|
||||
kbnUrl,
|
||||
Notifier,
|
||||
Private,
|
||||
timefilter,
|
||||
confirmModal
|
||||
) {
|
||||
timefilter.enabled = false;
|
||||
|
||||
// TODO: Extract this into an external service.
|
||||
const services = Private(SavedObjectRegistryProvider).byLoaderPropertiesName;
|
||||
const dashboardService = services.dashboards;
|
||||
const notify = new Notifier({ location: 'Dashboard' });
|
||||
|
||||
let selectedItems = [];
|
||||
|
||||
const fetchObjects = () => {
|
||||
dashboardService.find(this.filter)
|
||||
.then(result => {
|
||||
this.items = result.hits;
|
||||
});
|
||||
};
|
||||
|
||||
this.items = [];
|
||||
this.filter = '';
|
||||
|
||||
/**
|
||||
* Boolean that keeps track of whether hits are sorted ascending (true)
|
||||
* or descending (false) by title
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.isAscending = true;
|
||||
|
||||
/**
|
||||
* Sorts hits either ascending or descending
|
||||
* @param {Array} hits Array of saved finder object hits
|
||||
* @return {Array} Array sorted either ascending or descending
|
||||
*/
|
||||
this.sortHits = function () {
|
||||
this.isAscending = !this.isAscending;
|
||||
this.items = this.isAscending ? _.sortBy(this.items, 'title') : _.sortBy(this.items, 'title').reverse();
|
||||
};
|
||||
|
||||
this.toggleAll = function toggleAll() {
|
||||
if (this.areAllItemsChecked()) {
|
||||
selectedItems = [];
|
||||
} else {
|
||||
selectedItems = this.items.slice(0);
|
||||
}
|
||||
};
|
||||
|
||||
this.toggleItem = function toggleItem(item) {
|
||||
if (this.isItemChecked(item)) {
|
||||
const index = selectedItems.indexOf(item);
|
||||
selectedItems.splice(index, 1);
|
||||
} else {
|
||||
selectedItems.push(item);
|
||||
}
|
||||
};
|
||||
|
||||
this.isItemChecked = function isItemChecked(item) {
|
||||
return selectedItems.indexOf(item) !== -1;
|
||||
};
|
||||
|
||||
this.areAllItemsChecked = function areAllItemsChecked() {
|
||||
return this.getSelectedItemsCount() === this.items.length;
|
||||
};
|
||||
|
||||
this.getSelectedItemsCount = function getSelectedItemsCount() {
|
||||
return selectedItems.length;
|
||||
};
|
||||
|
||||
this.deleteSelectedItems = function deleteSelectedItems() {
|
||||
const doDelete = () => {
|
||||
const selectedIds = selectedItems.map(item => item.id);
|
||||
|
||||
dashboardService.delete(selectedIds)
|
||||
.then(fetchObjects)
|
||||
.then(() => {
|
||||
selectedItems = [];
|
||||
})
|
||||
.catch(error => notify.error(error));
|
||||
};
|
||||
confirmModal(
|
||||
'Are you sure you want to delete the selected dashboards? This action is irreversible!',
|
||||
{
|
||||
confirmButtonText: 'Delete',
|
||||
onConfirm: doDelete
|
||||
});
|
||||
};
|
||||
|
||||
this.open = function open(item) {
|
||||
kbnUrl.change(item.url.substr(1));
|
||||
};
|
||||
|
||||
$scope.$watch(() => this.filter, () => {
|
||||
fetchObjects();
|
||||
});
|
||||
}
|
|
@ -6,28 +6,12 @@
|
|||
*/
|
||||
export function getTopNavConfig(kbnUrl) {
|
||||
return [
|
||||
getNewConfig(kbnUrl),
|
||||
getAddConfig(),
|
||||
getSaveConfig(),
|
||||
getOpenConfig(),
|
||||
getShareConfig(),
|
||||
getOptionsConfig()];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param kbnUrl
|
||||
* @returns {kbnTopNavConfig}
|
||||
*/
|
||||
function getNewConfig(kbnUrl) {
|
||||
return {
|
||||
key: 'new',
|
||||
description: 'New Dashboard',
|
||||
testId: 'dashboardNewButton',
|
||||
run: () => { kbnUrl.change('/dashboard', {}); }
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {kbnTopNavConfig}
|
||||
*/
|
||||
|
@ -52,18 +36,6 @@ function getSaveConfig() {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {kbnTopNavConfig}
|
||||
*/
|
||||
function getOpenConfig() {
|
||||
return {
|
||||
key: 'open',
|
||||
description: 'Open Saved Dashboard',
|
||||
testId: 'dashboardOpenButton',
|
||||
template: require('plugins/kibana/dashboard/top_nav/open.html')
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {kbnTopNavConfig}
|
||||
*/
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
<div class="kuiLocalDropdownTitle">Open Dashboard</div>
|
||||
<saved-object-finder type="dashboards"></saved-object-finder>
|
40
src/ui/public/kbn_top_nav/__tests__/bread_crumb_urls.js
Normal file
40
src/ui/public/kbn_top_nav/__tests__/bread_crumb_urls.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
import expect from 'expect.js';
|
||||
|
||||
import { getBreadCrumbUrls } from '../bread_crumbs/bread_crumb_urls';
|
||||
|
||||
describe('getBreadCrumbUrls', function () {
|
||||
|
||||
it('returns urls for the breadcrumbs', function () {
|
||||
const breadCrumbUrls = getBreadCrumbUrls(
|
||||
['path1', 'path2', 'a', 'longlonglonglong'],
|
||||
'http://test.com/path1/path2/a/longlonglonglong');
|
||||
expect(breadCrumbUrls.length).to.equal(4);
|
||||
expect(breadCrumbUrls[0].url).to.equal('http://test.com/path1');
|
||||
expect(breadCrumbUrls[0].breadcrumb).to.equal('path1');
|
||||
|
||||
expect(breadCrumbUrls[1].url).to.equal('http://test.com/path1/path2');
|
||||
expect(breadCrumbUrls[1].breadcrumb).to.equal('path2');
|
||||
|
||||
expect(breadCrumbUrls[2].url).to.equal('http://test.com/path1/path2/a');
|
||||
expect(breadCrumbUrls[2].breadcrumb).to.equal('a');
|
||||
|
||||
expect(breadCrumbUrls[3].url).to.equal('http://test.com/path1/path2/a/longlonglonglong');
|
||||
expect(breadCrumbUrls[3].breadcrumb).to.equal('longlonglonglong');
|
||||
});
|
||||
|
||||
it('is case insensitive', function () {
|
||||
const breadCrumbUrls = getBreadCrumbUrls(['Path1', 'Path2'], 'http://TEST.com/paTh1/path2');
|
||||
expect(breadCrumbUrls.length).to.equal(2);
|
||||
expect(breadCrumbUrls[0].url).to.equal('http://TEST.com/paTh1');
|
||||
expect(breadCrumbUrls[0].breadcrumb).to.equal('Path1');
|
||||
|
||||
expect(breadCrumbUrls[1].url).to.equal('http://TEST.com/paTh1/path2');
|
||||
expect(breadCrumbUrls[1].breadcrumb).to.equal('Path2');
|
||||
});
|
||||
|
||||
it('handles no breadcrumbs case', function () {
|
||||
const breadCrumbUrls = getBreadCrumbUrls([], 'http://test.com');
|
||||
expect(breadCrumbUrls.length).to.equal(0);
|
||||
});
|
||||
|
||||
});
|
23
src/ui/public/kbn_top_nav/bread_crumbs/bread_crumb_urls.js
Normal file
23
src/ui/public/kbn_top_nav/bread_crumbs/bread_crumb_urls.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* @typedef BreadCrumbUrl {Object}
|
||||
* @property breadcrumb {String} a breadcrumb
|
||||
* @property url {String} a url for the breadcrumb
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Array.<String>} breadcrumbs An array of breadcrumbs for the given url.
|
||||
* @param {String} url The current url that the breadcrumbs have been generated for
|
||||
* @returns {Array.<BreadCrumbUrl> An array comprised of objects that
|
||||
* will contain both the url for the given breadcrumb, as well as the breadcrumb the url
|
||||
* was generated for.
|
||||
*/
|
||||
export function getBreadCrumbUrls(breadcrumbs, url) {
|
||||
return breadcrumbs.map(breadcrumb => {
|
||||
const breadCrumbStartIndex = url.toLowerCase().lastIndexOf(breadcrumb.toLowerCase());
|
||||
return {
|
||||
breadcrumb,
|
||||
url: url.substring(0, breadCrumbStartIndex + breadcrumb.length)
|
||||
};
|
||||
});
|
||||
}
|
|
@ -1,5 +1,11 @@
|
|||
<div class="kuiLocalBreadcrumbs">
|
||||
<div class="kuiLocalBreadcrumb" ng-repeat="breadcrumb in breadcrumbs">
|
||||
<div class="kuiLocalBreadcrumb" ng-if="useLinks" ng-repeat="breadCrumbUrl in breadCrumbUrls">
|
||||
<a class=kuiLocalBreadcrumb__link" href="{{breadCrumbUrl.url}}">{{breadCrumbUrl.breadcrumb}}</a>
|
||||
</div>
|
||||
<div class="kuiLocalBreadcrumb" ng-if="!useLinks" ng-repeat="breadcrumb in breadcrumbs">
|
||||
{{breadcrumb}}
|
||||
</div>
|
||||
<div class="kuiLocalBreadcrumb" ng-if="title">
|
||||
{{title}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,14 +1,25 @@
|
|||
import _ from 'lodash';
|
||||
import chrome from 'ui/chrome/chrome';
|
||||
import breadCrumbsTemplate from './bread_crumbs.html';
|
||||
import { getBreadCrumbUrls } from './bread_crumb_urls';
|
||||
import uiModules from 'ui/modules';
|
||||
let module = uiModules.get('kibana');
|
||||
|
||||
module.directive('breadCrumbs', function () {
|
||||
module.directive('breadCrumbs', function ($location) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
omitCurrentPage: '='
|
||||
omitCurrentPage: '=',
|
||||
/**
|
||||
* Optional title to append at the end of the breadcrumbs
|
||||
* @type {String}
|
||||
*/
|
||||
title: '=',
|
||||
/**
|
||||
* If true, makes each breadcrumb a clickable link.
|
||||
* @type {String}
|
||||
*/
|
||||
useLinks: '='
|
||||
},
|
||||
template: breadCrumbsTemplate,
|
||||
controller: function ($scope) {
|
||||
|
@ -18,6 +29,11 @@ module.directive('breadCrumbs', function () {
|
|||
if ($scope.omitCurrentPage === true) {
|
||||
$scope.breadcrumbs.pop();
|
||||
}
|
||||
|
||||
if ($scope.useLinks) {
|
||||
const url = '#' + $location.path();
|
||||
$scope.breadCrumbUrls = getBreadCrumbUrls($scope.breadcrumbs, url);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -56,11 +56,12 @@ bdd.describe('dashboard tab', function describeIndexTests() {
|
|||
}, Promise.resolve());
|
||||
}
|
||||
|
||||
return addVisualizations(visualizations)
|
||||
.then(function () {
|
||||
PageObjects.common.debug('done adding visualizations');
|
||||
PageObjects.common.saveScreenshot('Dashboard-add-visualizations');
|
||||
});
|
||||
return PageObjects.dashboard.clickNewDashboard()
|
||||
.then(() => addVisualizations(visualizations))
|
||||
.then(function () {
|
||||
PageObjects.common.debug('done adding visualizations');
|
||||
PageObjects.common.saveScreenshot('Dashboard-add-visualizations');
|
||||
});
|
||||
});
|
||||
|
||||
bdd.it('set the timepicker time to that which contains our test data', function setTimepicker() {
|
||||
|
@ -82,22 +83,23 @@ bdd.describe('dashboard tab', function describeIndexTests() {
|
|||
const dashboardName = 'Dashboard Test 1';
|
||||
// TODO: save time on the dashboard and test it
|
||||
return PageObjects.dashboard.saveDashboard(dashboardName)
|
||||
// click New Dashboard just to clear the one we just created
|
||||
.then(function () {
|
||||
return PageObjects.common.try(function () {
|
||||
PageObjects.common.debug('saved Dashboard, now click New Dashboard');
|
||||
return PageObjects.dashboard.clickNewDashboard();
|
||||
.then(() => PageObjects.dashboard.gotoDashboardLandingPage())
|
||||
// click New Dashboard just to clear the one we just created
|
||||
.then(function () {
|
||||
return PageObjects.common.try(function () {
|
||||
PageObjects.common.debug('saved Dashboard, now click New Dashboard');
|
||||
return PageObjects.dashboard.clickNewDashboard();
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
return PageObjects.common.try(function () {
|
||||
PageObjects.common.debug('now re-load previously saved dashboard');
|
||||
return PageObjects.dashboard.loadSavedDashboard(dashboardName);
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
PageObjects.common.saveScreenshot('Dashboard-load-saved');
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
return PageObjects.common.try(function () {
|
||||
PageObjects.common.debug('now re-load previously saved dashboard');
|
||||
return PageObjects.dashboard.loadSavedDashboard(dashboardName);
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
PageObjects.common.saveScreenshot('Dashboard-load-saved');
|
||||
});
|
||||
});
|
||||
|
||||
bdd.it('should have all the expected visualizations', function checkVisualizations() {
|
||||
|
|
|
@ -12,9 +12,15 @@ export default class DashboardPage {
|
|||
this.findTimeout = this.remote.setFindTimeout(defaultFindTimeout);
|
||||
}
|
||||
|
||||
gotoDashboardLandingPage() {
|
||||
return this.findTimeout
|
||||
.findByCssSelector('a[href="#/dashboard"]')
|
||||
.click();
|
||||
}
|
||||
|
||||
clickNewDashboard() {
|
||||
return PageObjects.common.findTestSubject('dashboardNewButton')
|
||||
.click();
|
||||
return PageObjects.common.findTestSubject('newDashboardLink')
|
||||
.click();
|
||||
}
|
||||
|
||||
clickAddVisualization() {
|
||||
|
@ -121,15 +127,13 @@ export default class DashboardPage {
|
|||
// use the search filter box to narrow the results down to a single
|
||||
// entry, or at least to a single page of results
|
||||
loadSavedDashboard(dashName) {
|
||||
var self = this;
|
||||
return PageObjects.common.findTestSubject('dashboardOpenButton')
|
||||
.click()
|
||||
const self = this;
|
||||
return this.gotoDashboardLandingPage()
|
||||
.then(function filterDashboard() {
|
||||
PageObjects.common.debug('Load Saved Dashboard button clicked');
|
||||
return self.remote
|
||||
.findByCssSelector('input[name="filter"]')
|
||||
.click()
|
||||
.type(dashName.replace('-',' '));
|
||||
return PageObjects.common.findTestSubject('searchFilter')
|
||||
.click()
|
||||
.type(dashName.replace('-',' '));
|
||||
})
|
||||
.then(() => {
|
||||
return PageObjects.header.isGlobalLoadingIndicatorHidden();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue