merge Refactor out state logic from dashboard directive (and a bugfix (#10267)

This commit is contained in:
Stacey Gammon 2017-02-09 11:55:35 -05:00 committed by GitHub
parent 0046164246
commit e661dd336b
17 changed files with 802 additions and 387 deletions

View file

@ -23,9 +23,9 @@ describe('dashboard panels', function () {
style="width: 600px; height: 600px;"
ng-if="!hasExpandedPanel()"
on-panel-removed="onPanelRemoved"
panels="state.panels"
get-vis-click-handler="filterBarClickHandler(state)"
get-vis-brush-handler="brushEvent(state)"
panels="panels"
get-vis-click-handler="filterBarClickHandler"
get-vis-brush-handler="brushEvent"
save-state="saveState"
toggle-expand="toggleExpandPanel"
create-child-ui-state="createChildUiState"
@ -38,7 +38,7 @@ describe('dashboard panels', function () {
}
function findPanelWithVisualizationId(id) {
return $scope.state.panels.find((panel) => { return panel.id === id; });
return $scope.panels.find((panel) => { return panel.id === id; });
}
beforeEach(() => {
@ -56,7 +56,7 @@ describe('dashboard panels', function () {
dash.init();
compile(dash);
});
expect($scope.state.panels.length).to.be(0);
expect($scope.panels.length).to.be(0);
});
it('loads one vizualization', function () {
@ -66,7 +66,7 @@ describe('dashboard panels', function () {
dash.panelsJSON = `[{"col":3,"id":"foo1","row":1,"size_x":2,"size_y":2,"type":"visualization"}]`;
compile(dash);
});
expect($scope.state.panels.length).to.be(1);
expect($scope.panels.length).to.be(1);
});
it('loads vizualizations in correct order', function () {
@ -92,7 +92,7 @@ describe('dashboard panels', function () {
{"col":1,"id":"foo17","row":3,"size_x":4,"size_y":3,"type":"visualization"}]`;
compile(dash);
});
expect($scope.state.panels.length).to.be(16);
expect($scope.panels.length).to.be(16);
const foo8Panel = findPanelWithVisualizationId('foo8');
expect(foo8Panel).to.not.be(null);
expect(foo8Panel.row).to.be(8);
@ -108,7 +108,7 @@ describe('dashboard panels', function () {
{"col":5,"id":"foo2","row":1,"size_x":5,"size_y":9,"type":"visualization"}]`;
compile(dash);
});
expect($scope.state.panels.length).to.be(2);
expect($scope.panels.length).to.be(2);
const foo1Panel = findPanelWithVisualizationId('foo1');
expect(foo1Panel).to.not.be(null);
expect(foo1Panel.size_x).to.be(DEFAULT_PANEL_WIDTH);

View file

@ -0,0 +1,95 @@
import ngMock from 'ng_mock';
import expect from 'expect.js';
import { DashboardState } from '../dashboard_state';
describe('DashboardState', function () {
let AppState;
let dashboardState;
let savedDashboard;
let SavedDashboard;
let timefilter;
let quickTimeRanges;
function initDashboardState() {
dashboardState = new DashboardState(savedDashboard, timefilter, true, quickTimeRanges, AppState);
}
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function ($injector) {
timefilter = $injector.get('timefilter');
quickTimeRanges = $injector.get('quickRanges');
AppState = $injector.get('AppState');
SavedDashboard = $injector.get('SavedDashboard');
savedDashboard = new SavedDashboard();
}));
describe('timefilter', function () {
describe('when timeRestore is true', function () {
it('syncs quick time', function () {
savedDashboard.timeRestore = true;
savedDashboard.timeFrom = 'now/w';
savedDashboard.timeTo = 'now/w';
timefilter.time.from = '2015-09-19 06:31:44.000';
timefilter.time.to = '2015-09-29 06:31:44.000';
timefilter.time.mode = 'absolute';
initDashboardState();
expect(timefilter.time.mode).to.equal('quick');
expect(timefilter.time.to).to.equal('now/w');
expect(timefilter.time.from).to.equal('now/w');
});
it('syncs relative time', function () {
savedDashboard.timeRestore = true;
savedDashboard.timeFrom = 'now-13d';
savedDashboard.timeTo = 'now';
timefilter.time.from = '2015-09-19 06:31:44.000';
timefilter.time.to = '2015-09-29 06:31:44.000';
timefilter.time.mode = 'absolute';
initDashboardState();
expect(timefilter.time.mode).to.equal('relative');
expect(timefilter.time.to).to.equal('now');
expect(timefilter.time.from).to.equal('now-13d');
});
it('syncs absolute time', function () {
savedDashboard.timeRestore = true;
savedDashboard.timeFrom = '2015-09-19 06:31:44.000';
savedDashboard.timeTo = '2015-09-29 06:31:44.000';
timefilter.time.from = 'now/w';
timefilter.time.to = 'now/w';
timefilter.time.mode = 'quick';
initDashboardState();
expect(timefilter.time.mode).to.equal('absolute');
expect(timefilter.time.to).to.equal(savedDashboard.timeTo);
expect(timefilter.time.from).to.equal(savedDashboard.timeFrom);
});
});
it('is not synced when timeRestore is false', function () {
savedDashboard.timeRestore = false;
savedDashboard.timeFrom = 'now/w';
savedDashboard.timeTo = 'now/w';
timefilter.time.timeFrom = '2015-09-19 06:31:44.000';
timefilter.time.timeTo = '2015-09-29 06:31:44.000';
timefilter.time.mode = 'absolute';
initDashboardState();
expect(timefilter.time.mode).to.equal('absolute');
expect(timefilter.time.timeFrom).to.equal('2015-09-19 06:31:44.000');
expect(timefilter.time.timeTo).to.equal('2015-09-29 06:31:44.000');
});
});
});

View file

@ -30,7 +30,7 @@
parse-query
input-focus
kbn-typeahead-input
ng-model="state.query"
ng-model="model.query"
placeholder="Filter..."
aria-label="Filter input"
type="text"
@ -63,9 +63,9 @@
<dashboard-grid
ng-show="!hasExpandedPanel()"
on-panel-removed="onPanelRemoved"
panels="state.panels"
get-vis-click-handler="filterBarClickHandler(state)"
get-vis-brush-handler="brushEvent(state)"
panels="panels"
get-vis-click-handler="getFilterBarClickHandler"
get-vis-brush-handler="getBrushEvent"
save-state="saveState"
toggle-expand="toggleExpandPanel"
create-child-ui-state="createChildUiState"
@ -76,8 +76,8 @@
panel="expandedPanel"
is-full-screen-mode="!chrome.getVisible()"
is-expanded="true"
get-vis-click-handler="filterBarClickHandler(state)"
get-vis-brush-handler="brushEvent(state)"
get-vis-click-handler="getFilterBarClickHandler"
get-vis-brush-handler="getBrushEvent"
save-state="saveState"
create-child-ui-state="createChildUiState"
toggle-expand="toggleExpandPanel(expandedPanel.panelIndex)">

View file

@ -10,14 +10,11 @@ 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';
import { FilterUtils } from './filter_utils';
import { getPersistedStateId } from 'plugins/kibana/dashboard/panel/panel_state';
import { DashboardState } from './dashboard_state';
const app = uiModules.get('app/dashboard', [
'elasticsearch',
@ -52,7 +49,7 @@ uiRoutes
}
});
app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter, kbnUrl, Private) {
app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter, quickRanges, kbnUrl, Private) {
const brushEvent = Private(UtilsBrushEventProvider);
const filterBarClickHandler = Private(FilterBarFilterBarClickHandlerProvider);
@ -60,117 +57,76 @@ app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter,
restrict: 'E',
controllerAs: 'dashboardApp',
controller: function ($scope, $rootScope, $route, $routeParams, $location, Private, getAppState) {
const queryFilter = Private(FilterBarQueryFilterProvider);
const notify = new Notifier({
location: 'Dashboard'
});
const docTitle = Private(DocTitleProvider);
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;
}
if (dash.id) {
docTitle.change(dash.title);
}
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: FilterUtils.getQueryFilterForDashboard(dash),
filters: FilterUtils.getFilterBarsForDashboard(dash),
};
const dashboardState = new DashboardState(
dash,
timefilter,
!getAppState.previouslyStored(),
quickRanges,
AppState);
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');
dashboardState.updateFilters(queryFilter);
let pendingVisCount = _.size(dashboardState.getPanels());
timefilter.enabled = true;
$scope.timefilter = timefilter;
$scope.$listen(timefilter, 'fetch', $scope.refresh);
courier.setRootSearchSource(dash.searchSource);
const docTitle = Private(DocTitleProvider);
// Following the "best practice" of always have a '.' in your ng-models
// https://github.com/angular/angular.js/wiki/Understanding-Scopes
$scope.model = { query: dashboardState.getQuery() };
function init() {
updateQueryOnRootSource();
$scope.panels = dashboardState.getPanels();
$scope.topNavMenu = getTopNavConfig(kbnUrl);
$scope.refresh = _.bindKey(courier, 'fetch');
$scope.timefilter = timefilter;
$scope.expandedPanel = null;
if (dash.id) {
docTitle.change(dash.title);
}
$scope.getBrushEvent = () => brushEvent(dashboardState.getAppState());
$scope.getFilterBarClickHandler = () => filterBarClickHandler(dashboardState.getAppState());
$scope.hasExpandedPanel = () => $scope.expandedPanel !== null;
$scope.getDashTitle = () => {
return dashboardState.dashboard.lastSavedTitle || `${dashboardState.dashboard.title} (unsaved)`;
};
$scope.newDashboard = () => { kbnUrl.change(DashboardConstants.CREATE_NEW_DASHBOARD_URL, {}); };
$scope.saveState = () => dashboardState.saveState();
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
}]));
$scope.toggleExpandPanel = (panelIndex) => {
if ($scope.expandedPanel && $scope.expandedPanel.panelIndex === panelIndex) {
$scope.expandedPanel = null;
} else {
dash.searchSource.set('filter', filters);
$scope.expandedPanel =
dashboardState.getPanels().find((panel) => panel.panelIndex === panelIndex);
}
}
};
function setDarkTheme(enabled) {
const theme = Boolean(enabled) ? 'theme-dark' : 'theme-light';
chrome.removeApplicationClass(['theme-dark', 'theme-light']);
chrome.addApplicationClass(theme);
}
$scope.filterResults = function () {
dashboardState.setQuery($scope.model.query);
dashboardState.updateFilters(queryFilter);
$scope.refresh();
};
// called by the saved-object-finder when a user clicks a vis
$scope.addVis = function (hit) {
pendingVisCount++;
dashboardState.addNewPanel(hit.id, 'visualization');
};
$scope.addSearch = function (hit) {
pendingVisCount++;
dashboardState.addNewPanel(hit.id, 'search');
};
$scope.showEditHelpText = () => {
return !dashboardState.getPanels().length;
};
/**
* Creates a child ui state for the panel. It's passed the ui state to use, but needs to
@ -180,104 +136,74 @@ app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter,
* @returns {Object}
*/
$scope.createChildUiState = function createChildUiState(path, uiState) {
return $scope.uiState.createChild(path, uiState, true);
return dashboardState.uiState.createChild(path, uiState, true);
};
$scope.saveState = function saveState() {
$state.save();
};
$scope.onPanelRemoved = (panelIndex) => dashboardState.removePanel(panelIndex);
$scope.onPanelRemoved = (panelIndex) => {
_.remove($scope.state.panels, function (panel) {
if (panel.panelIndex === panelIndex) {
$scope.uiState.removeChild(getPersistedStateId(panel));
return true;
} else {
return false;
$scope.save = function () {
return dashboardState.saveDashboard(angular.toJson).then(function (id) {
$scope.kbnTopNav.close('save');
if (id) {
notify.info(`Saved Dashboard as "${dash.title}"`);
if (dash.id !== $routeParams.id) {
kbnUrl.change(
`${DashboardConstants.EXISTING_DASHBOARD_URL}`,
{ id: dash.id });
} else {
docTitle.change(dash.lastSavedTitle);
}
}
});
$state.save();
}).catch(notify.fatal);
};
$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);
}
};
$scope.$watchCollection(() => dashboardState.getOptions(), () => dashboardState.saveState());
$scope.$watch(() => dashboardState.getOptions().darkTheme, updateTheme);
$scope.$watch('model.query', function () {
dashboardState.setQuery($scope.model.query);
});
$scope.$listen(timefilter, 'fetch', $scope.refresh);
// update root source when filters update
$scope.$listen(queryFilter, 'update', function () {
updateQueryOnRootSource();
$state.save();
dashboardState.updateFilters(queryFilter);
});
// update data when filters fire fetch event
$scope.$listen(queryFilter, 'fetch', $scope.refresh);
$scope.getDashTitle = function () {
return dash.lastSavedTitle || `${dash.title} (unsaved)`;
};
$scope.$on('$destroy', () => {
dashboardState.destroy();
$scope.newDashboard = function () {
kbnUrl.change('/dashboard', {});
};
// Remove dark theme to keep it from affecting the appearance of other apps.
setLightTheme();
});
$scope.filterResults = function () {
updateQueryOnRootSource();
$state.save();
$scope.refresh();
};
function updateTheme() {
const useDarkTheme = dashboardState.getOptions().darkTheme;
useDarkTheme ? setDarkTheme() : setLightTheme();
}
$scope.save = function () {
$state.save();
function setDarkTheme() {
chrome.removeApplicationClass(['theme-light']);
chrome.addApplicationClass('theme-dark');
}
const timeRestoreObj = _.pick(timefilter.refreshInterval, ['display', 'pause', 'section', 'value']);
function setLightTheme() {
chrome.removeApplicationClass(['theme-dark']);
chrome.addApplicationClass('theme-light');
}
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();
if (pendingVisCount > 0) pendingVisCount--;
if (pendingVisCount === 0) {
dashboardState.saveState();
$scope.refresh();
}
});
// 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);
@ -288,15 +214,10 @@ app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter,
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,
ui: dashboardState.getOptions(),
save: $scope.save,
addVis: $scope.addVis,
addNewVis,
@ -304,11 +225,7 @@ app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter,
timefilter: $scope.timefilter
};
init();
$scope.showEditHelpText = () => {
return !$scope.state.panels.length;
};
$scope.$emit('application.load');
}
};
});

View file

@ -2,5 +2,7 @@
export const DashboardConstants = {
ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM: 'addToDashboard',
NEW_VISUALIZATION_ID_PARAM: 'addVisualization',
LANDING_PAGE_PATH: '/dashboard'
LANDING_PAGE_PATH: '/dashboard',
CREATE_NEW_DASHBOARD_URL: '/dashboard/create',
EXISTING_DASHBOARD_URL: '/dashboard/{{id}}'
};

View file

@ -0,0 +1,175 @@
import _ from 'lodash';
import { FilterUtils } from './filter_utils';
import { PanelUtils } from './panel/panel_utils';
import moment from 'moment';
import stateMonitorFactory from 'ui/state_management/state_monitor_factory';
import { createPanelState } from 'plugins/kibana/dashboard/panel/panel_state';
import { getPersistedStateId } from 'plugins/kibana/dashboard/panel/panel_state';
function getStateDefaults(dashboard) {
return {
title: dashboard.title,
panels: dashboard.panelsJSON ? JSON.parse(dashboard.panelsJSON) : [],
options: dashboard.optionsJSON ? JSON.parse(dashboard.optionsJSON) : {},
uiState: dashboard.uiStateJSON ? JSON.parse(dashboard.uiStateJSON) : {},
query: FilterUtils.getQueryFilterForDashboard(dashboard),
filters: FilterUtils.getFilterBarsForDashboard(dashboard)
};
}
export class DashboardState {
/**
* @param dashboard {SavedDashboard}
* @param timefilter {Object}
* @param timeFilterPreviouslyStored {boolean} - I'm honestly not sure what this value
* means but preserving original logic after a refactor.
* @param quickTimeRanges {Array<Object>} An array of default time ranges that should be
* classified as 'quick' mode.
* @param AppState {Object} A class that will be used to instantiate the appState.
*/
constructor(dashboard, timefilter, timeFilterPreviouslyStored, quickTimeRanges, AppState) {
this.stateDefaults = getStateDefaults(dashboard);
this.appState = new AppState(this.stateDefaults);
this.uiState = this.appState.makeStateful('uiState');
this.timefilter = timefilter;
this.dashboard = dashboard;
this.stateMonitor = stateMonitorFactory.create(this.appState, this.stateDefaults);
if (this.getShouldSyncTimefilterWithDashboard() && timeFilterPreviouslyStored) {
this.syncTimefilterWithDashboard(quickTimeRanges);
}
PanelUtils.initPanelIndexes(this.getPanels());
}
getAppState() {
return this.appState;
}
getQuery() {
return this.appState.query;
}
getOptions() {
return this.appState.options;
}
/**
* Returns true if the timefilter should match the time stored with the dashboard.
* @returns {boolean}
*/
getShouldSyncTimefilterWithDashboard() {
return this.dashboard.timeRestore && this.dashboard.timeTo && this.dashboard.timeFrom;
}
getPanels() {
return this.appState.panels;
}
/**
* Creates and initializes a basic panel, adding it to the state.
* @param {number} id
* @param {string} type
*/
addNewPanel(id, type) {
const maxPanelIndex = PanelUtils.getMaxPanelIndex(this.getPanels());
this.getPanels().push(createPanelState(id, type, maxPanelIndex));
}
removePanel(panelIndex) {
_.remove(this.getPanels(), (panel) => {
if (panel.panelIndex === panelIndex) {
this.uiState.removeChild(getPersistedStateId(panel));
return true;
} else {
return false;
}
});
this.saveState();
}
/**
* Updates the time filter to match the values stored in the dashboard.
* @param {Array<Object>} quickTimeRanges - An array of often used default relative time ranges.
* Used to determine whether a relative query should be classified as a "quick" time mode or
* simply a "relative" time mode.
*/
syncTimefilterWithDashboard(quickTimeRanges) {
this.timefilter.time.to = this.dashboard.timeTo;
this.timefilter.time.from = this.dashboard.timeFrom;
const isMoment = moment(this.dashboard.timeTo).isValid();
if (isMoment) {
this.timefilter.time.mode = 'absolute';
} else {
const quickTime = _.find(
quickTimeRanges,
(timeRange) => timeRange.from === this.dashboard.timeFrom && timeRange.to === this.dashboard.timeTo);
this.timefilter.time.mode = quickTime ? 'quick' : 'relative';
}
if (this.dashboard.refreshInterval) {
this.timefilter.refreshInterval = this.dashboard.refreshInterval;
}
}
setQuery(newQuery) {
this.appState.query = newQuery;
}
saveState() {
this.appState.save();
}
/**
* Saves the dashboard.
* @param toJson {function} A custom toJson function. Used because the previous code used
* the angularized toJson version, and it was unclear whether there was a reason not to use
* JSON.stringify
* @returns {Promise<string>} A promise that if resolved, will contain the id of the newly saved
* dashboard.
*/
saveDashboard(toJson) {
this.saveState();
const timeRestoreObj = _.pick(this.timefilter.refreshInterval, ['display', 'pause', 'section', 'value']);
this.dashboard.panelsJSON = toJson(this.appState.panels);
this.dashboard.uiStateJSON = toJson(this.uiState.getChanges());
this.dashboard.timeFrom = this.dashboard.timeRestore ? this.timefilter.time.from : undefined;
this.dashboard.timeTo = this.dashboard.timeRestore ? this.timefilter.time.to : undefined;
this.dashboard.refreshInterval = this.dashboard.timeRestore ? timeRestoreObj : undefined;
this.dashboard.optionsJSON = toJson(this.appState.options);
return this.dashboard.save()
.then((id) => {
this.stateMonitor.setInitialState(this.appState.toJSON());
return id;
});
}
/**
* Stores the given filter with the dashboard and to the state.
* @param filter
*/
updateFilters(filter) {
const filters = filter.getFilters();
if (this.appState.query) {
this.dashboard.searchSource.set('filter', _.union(filters, [{
query: this.appState.query
}]));
} else {
this.dashboard.searchSource.set('filter', filters);
}
this.saveState();
}
destroy() {
if (this.stateMonitor) {
this.stateMonitor.destroy();
}
this.dashboard.destroy();
}
}

View file

@ -31,12 +31,12 @@ app.directive('dashboardGrid', function ($compile, Notifier) {
* Returns a click handler for a visualization.
* @type {function}
*/
getVisClickHandler: '&',
getVisClickHandler: '=',
/**
* Returns a brush event handler for a visualization.
* @type {function}
*/
getVisBrushHandler: '&',
getVisBrushHandler: '=',
/**
* Call when changes should be propagated to the url and thus saved in state.
* @type {function}
@ -173,8 +173,8 @@ app.directive('dashboardGrid', function ($compile, Notifier) {
panel="findPanelByPanelIndex(${panel.panelIndex}, panels)"
is-full-screen-mode="isFullScreenMode"
is-expanded="false"
get-vis-click-handler="getVisClickHandler(state)"
get-vis-brush-handler="getVisBrushHandler(state)"
get-vis-click-handler="getVisClickHandler"
get-vis-brush-handler="getVisBrushHandler"
save-state="saveState"
toggle-expand="toggleExpand(${panel.panelIndex})"
create-child-ui-state="createChildUiState">

View file

@ -59,12 +59,12 @@ uiModules
* Returns a click handler for a visualization.
* @type {function}
*/
getVisClickHandler: '&',
getVisClickHandler: '=',
/**
* Returns a brush event handler for a visualization.
* @type {function}
*/
getVisBrushHandler: '&',
getVisBrushHandler: '=',
/**
* Call when changes should be propagated to the url and thus saved in state.
* @type {function}

View file

@ -44,4 +44,23 @@ export class PanelUtils {
static findPanelByPanelIndex(panelIndex, panels) {
return _.find(panels, (panel) => panel.panelIndex === panelIndex);
}
static initPanelIndexes(panels) {
// find the largest panelIndex in all the panels
let maxIndex = this.getMaxPanelIndex(panels);
// ensure that all panels have a panelIndex
panels.forEach(function (panel) {
if (!panel.panelIndex) {
panel.panelIndex = maxIndex++;
}
});
}
static getMaxPanelIndex(panels) {
let maxId = panels.reduce(function (id, panel) {
return Math.max(id, panel.panelIndex || id);
}, 0);
return ++maxId;
}
}

View file

@ -2,7 +2,12 @@
<div class="kuiLocalDropdownTitle">Options</div>
<div class="input-group">
<label>
<input type="checkbox" ng-model="opts.ui.darkTheme" ng-checked="opts.ui.darkTheme">
<input
type="checkbox"
ng-model="opts.ui.darkTheme"
ng-checked="opts.ui.darkTheme"
data-test-subj="dashboardDarkThemeCheckbox"
>
Use dark theme
</label>
</div>

View file

@ -16,7 +16,12 @@
<div class="form-group">
<label>
<input type="checkbox" ng-model="opts.dashboard.timeRestore" ng-checked="opts.dashboard.timeRestore">
<input
type="checkbox"
ng-model="opts.dashboard.timeRestore"
ng-checked="opts.dashboard.timeRestore"
data-test-subj="storeTimeWithDashboard"
>
Store time with {{opts.dashboard.getDisplayName()}}
<kbn-info placement="bottom" info="Change the time filter to the currently selected time each time this dashboard is loaded"></kbn-info>
</label>

View file

@ -14,128 +14,97 @@ import {
import PageObjects from '../../../support/page_objects';
bdd.describe('dashboard tab', function describeIndexTests() {
bdd.before(function () {
PageObjects.common.debug('Starting dashboard before method');
const logstash = scenarioManager.loadIfEmpty('logstashFunctional');
// delete .kibana index and update configDoc
return esClient.deleteAndUpdateConfigDoc({ 'dateFormat:tz':'UTC', 'defaultIndex':'logstash-*' })
// and load a set of makelogs data
.then(function loadkibanaVisualizations() {
PageObjects.common.debug('load kibana index with visualizations');
return elasticDump.elasticLoad('dashboard','.kibana');
})
.then(function () {
PageObjects.common.debug('navigateToApp dashboard');
return PageObjects.common.navigateToApp('dashboard');
})
// wait for the logstash data load to finish if it hasn't already
.then(function () {
return logstash;
});
bdd.before(async function () {
return PageObjects.dashboard.initTests();
});
bdd.describe('add visualizations to dashboard', function dashboardTest() {
const visualizations = ['Visualization漢字 AreaChart',
'Visualization☺漢字 DataTable',
'Visualization漢字 LineChart',
'Visualization PieChart',
'Visualization TileMap',
'Visualization☺ VerticalBarChart',
'Visualization MetricChart'
];
bdd.it('should be able to add visualizations to dashboard', async function addVisualizations() {
PageObjects.common.saveScreenshot('Dashboard-no-visualizations');
bdd.it('should be able to add visualizations to dashboard', function addVisualizations() {
PageObjects.common.saveScreenshot('Dashboard-no-visualizations');
// This flip between apps fixes the url so state is preserved when switching apps in test mode.
// Without this flip the url in test mode looks something like
// "http://localhost:5620/app/kibana?_t=1486069030837#/dashboard?_g=...."
// after the initial flip, the url will look like this: "http://localhost:5620/app/kibana#/dashboard?_g=...."
await PageObjects.header.clickVisualize();
await PageObjects.header.clickDashboard();
function addVisualizations(arr) {
return arr.reduce(function (promise, vizName) {
return promise
.then(function () {
return PageObjects.dashboard.addVisualization(vizName);
});
}, Promise.resolve());
}
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.dashboard.addVisualizations(PageObjects.dashboard.getTestVisualizationNames());
return PageObjects.dashboard.clickNewDashboard()
.then(() => addVisualizations(visualizations))
.then(function () {
PageObjects.common.debug('done adding visualizations');
PageObjects.common.saveScreenshot('Dashboard-add-visualizations');
});
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', async function setTimepicker() {
await PageObjects.dashboard.setTimepickerInDataRange();
});
bdd.it('should save and load dashboard', async function saveAndLoadDashboard() {
const dashboardName = 'Dashboard Test 1';
await PageObjects.dashboard.saveDashboard(dashboardName);
await PageObjects.dashboard.gotoDashboardLandingPage();
await PageObjects.common.try(function () {
PageObjects.common.debug('now re-load previously saved dashboard');
return PageObjects.dashboard.loadSavedDashboard(dashboardName);
});
await PageObjects.common.saveScreenshot('Dashboard-load-saved');
});
bdd.it('set the timepicker time to that which contains our test data', function setTimepicker() {
const fromTime = '2015-09-19 06:31:44.000';
const toTime = '2015-09-23 18:31:44.000';
// .then(function () {
PageObjects.common.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"');
return PageObjects.header.setAbsoluteRange(fromTime, toTime)
.then(function () {
return PageObjects.header.isGlobalLoadingIndicatorHidden();
})
.then(function takeScreenshot() {
PageObjects.common.saveScreenshot('Dashboard-set-timepicker');
});
});
bdd.it('should save and load dashboard', function saveAndLoadDashboard() {
const dashboardName = 'Dashboard Test 1';
// TODO: save time on the dashboard and test it
return PageObjects.dashboard.saveDashboard(dashboardName)
.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');
});
});
bdd.it('should have all the expected visualizations', function checkVisualizations() {
return PageObjects.common.tryForTime(10000, function () {
return PageObjects.dashboard.getPanelTitles()
bdd.it('should have all the expected visualizations', function checkVisualizations() {
return PageObjects.common.tryForTime(10000, function () {
return PageObjects.dashboard.getPanelTitles()
.then(function (panelTitles) {
PageObjects.common.log('visualization titles = ' + panelTitles);
expect(panelTitles).to.eql(visualizations);
expect(panelTitles).to.eql(PageObjects.dashboard.getTestVisualizationNames());
});
})
})
.then(function () {
PageObjects.common.saveScreenshot('Dashboard-has-visualizations');
});
});
});
bdd.it('should have all the expected initial sizes', function checkVisualizationSizes() {
const width = DEFAULT_PANEL_WIDTH;
const height = DEFAULT_PANEL_HEIGHT;
const visObjects = [
{ dataCol: '1', dataRow: '1', dataSizeX: width, dataSizeY: height, title: 'Visualization漢字 AreaChart' },
{ dataCol: width + 1, dataRow: '1', dataSizeX: width, dataSizeY: height, title: 'Visualization☺漢字 DataTable' },
{ dataCol: '1', dataRow: height + 1, dataSizeX: width, dataSizeY: height, title: 'Visualization漢字 LineChart' },
{ dataCol: width + 1, dataRow: height + 1, dataSizeX: width, dataSizeY: height, title: 'Visualization PieChart' },
{ dataCol: '1', dataRow: (height * 2) + 1, dataSizeX: width, dataSizeY: height, title: 'Visualization TileMap' },
{ dataCol: width + 1, dataRow: (height * 2) + 1, dataSizeX: width, dataSizeY: height, title: 'Visualization☺ VerticalBarChart' },
{ dataCol: '1', dataRow: (height * 3) + 1, dataSizeX: width, dataSizeY: height, title: 'Visualization MetricChart' }
];
return PageObjects.common.tryForTime(10000, function () {
return PageObjects.dashboard.getPanelData()
bdd.it('should have all the expected initial sizes', function checkVisualizationSizes() {
const width = DEFAULT_PANEL_WIDTH;
const height = DEFAULT_PANEL_HEIGHT;
const titles = PageObjects.dashboard.getTestVisualizationNames();
const visObjects = [
{ dataCol: '1', dataRow: '1', dataSizeX: width, dataSizeY: height, title: titles[0] },
{ dataCol: width + 1, dataRow: '1', dataSizeX: width, dataSizeY: height, title: titles[1] },
{ dataCol: '1', dataRow: height + 1, dataSizeX: width, dataSizeY: height, title: titles[2] },
{ dataCol: width + 1, dataRow: height + 1, dataSizeX: width, dataSizeY: height, title: titles[3] },
{ dataCol: '1', dataRow: (height * 2) + 1, dataSizeX: width, dataSizeY: height, title: titles[4] },
{ dataCol: width + 1, dataRow: (height * 2) + 1, dataSizeX: width, dataSizeY: height, title: titles[5] },
{ dataCol: '1', dataRow: (height * 3) + 1, dataSizeX: width, dataSizeY: height, title: titles[6] }
];
return PageObjects.common.tryForTime(10000, function () {
return PageObjects.dashboard.getPanelData()
.then(function (panelTitles) {
PageObjects.common.log('visualization titles = ' + panelTitles);
PageObjects.common.saveScreenshot('Dashboard-visualization-sizes');
expect(panelTitles).to.eql(visObjects);
});
});
})
.then(function () {
PageObjects.common.saveScreenshot('Dashboard-has-visualizations');
});
});
bdd.it('filters when a pie chart slice is clicked', async function () {
let descriptions = await PageObjects.dashboard.getFilterDescriptions(1000);
expect(descriptions.length).to.equal(0);
await PageObjects.dashboard.filterOnPieSlice();
descriptions = await PageObjects.dashboard.getFilterDescriptions();
expect(descriptions.length).to.equal(1);
});
bdd.it('retains dark theme in state', async function () {
await PageObjects.dashboard.useDarkTheme(true);
await PageObjects.header.clickVisualize();
await PageObjects.header.clickDashboard();
const isDarkThemeOn = await PageObjects.dashboard.isDarkThemeOn();
expect(isDarkThemeOn).to.equal(true);
});
});

View file

@ -0,0 +1,67 @@
import expect from 'expect.js';
import { bdd } from '../../../support';
import PageObjects from '../../../support/page_objects';
const dashboardName = 'Dashboard Test Time';
const fromTime = '2015-09-19 06:31:44.000';
const toTime = '2015-09-23 18:31:44.000';
bdd.describe('dashboard time', function dashboardSaveWithTime() {
bdd.before(async function () {
await PageObjects.dashboard.initTests();
});
bdd.describe('dashboard without stored timed', async function () {
bdd.it('is saved', async function () {
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.dashboard.addVisualizations([PageObjects.dashboard.getTestVisualizationNames()[0]]);
await PageObjects.dashboard.saveDashboard(dashboardName, false);
});
bdd.it('Does not set the time picker on open', async function () {
await PageObjects.header.setAbsoluteRange(fromTime, toTime);
await PageObjects.dashboard.loadSavedDashboard(dashboardName);
const fromTimeNext = await PageObjects.header.getFromTime();
const toTimeNext = await PageObjects.header.getToTime();
expect(fromTimeNext).to.equal(fromTime);
expect(toTimeNext).to.equal(toTime);
});
});
bdd.describe('dashboard with stored timed', async function () {
bdd.it('is saved with quick time', async function () {
await PageObjects.header.setQuickTime('Today');
await PageObjects.dashboard.saveDashboard(dashboardName, true);
});
bdd.it('sets quick time on open', async function () {
await PageObjects.header.setAbsoluteRange(fromTime, toTime);
await PageObjects.dashboard.loadSavedDashboard(dashboardName);
const prettyPrint = await PageObjects.header.getPrettyDuration();
expect(prettyPrint).to.equal('Today');
});
bdd.it('is saved with absolute time', async function () {
await PageObjects.header.setAbsoluteRange(fromTime, toTime);
await PageObjects.dashboard.saveDashboard(dashboardName, true);
});
bdd.it('sets absolute time on open', async function () {
await PageObjects.header.setQuickTime('Today');
await PageObjects.dashboard.loadSavedDashboard(dashboardName);
const fromTimeNext = await PageObjects.header.getFromTime();
const toTimeNext = await PageObjects.header.getToTime();
expect(fromTimeNext).to.equal(fromTime);
expect(toTimeNext).to.equal(toTime);
});
});
});

View file

@ -14,4 +14,5 @@ bdd.describe('dashboard app', function () {
});
require('./_dashboard');
require('./_dashboard_time');
});

View file

@ -6,14 +6,12 @@ import bluebird, {
import fs from 'fs';
import _ from 'lodash';
import mkdirp from 'mkdirp';
import moment from 'moment';
import path from 'path';
import testSubjSelector from '@spalger/test-subj-selector';
import {
format,
parse
} from 'url';
import util from 'util';
import getUrl from '../../utils/get_url';
@ -272,6 +270,35 @@ export default class Common {
}
}
async doesCssSelectorExist(selector) {
PageObjects.common.debug(`doesCssSelectorExist ${selector}`);
const exists = await this.remote
.setFindTimeout(1000)
.findByCssSelector(selector)
.then(() => true)
.catch(() => false);
this.remote.setFindTimeout(defaultFindTimeout);
PageObjects.common.debug(`exists? ${exists}`);
return exists;
}
findByCssSelector(selector) {
PageObjects.common.debug(`findByCssSelector ${selector}`);
return this.remote.setFindTimeout(defaultFindTimeout).findByCssSelector(selector);
}
async doesTestSubjectExist(selector) {
PageObjects.common.debug(`doesTestSubjectExist ${selector}`);
const exists = await this.remote
.setFindTimeout(1000)
.findDisplayedByCssSelector(testSubjSelector(selector))
.then(() => true)
.catch(() => false);
this.remote.setFindTimeout(defaultFindTimeout);
return exists;
}
findTestSubject(selector) {
this.debug('in findTestSubject: ' + testSubjSelector(selector));
return this.remote
@ -281,9 +308,17 @@ export default class Common {
async findAllTestSubjects(selector) {
this.debug('in findAllTestSubjects: ' + testSubjSelector(selector));
const remote = this.remote.setFindTimeout(defaultFindTimeout);
const all = await remote.findAllByCssSelector(testSubjSelector(selector));
const all = await this.findAllByCssSelector(testSubjSelector(selector));
return await filterAsync(all, el => el.isDisplayed());
}
async findAllByCssSelector(selector, timeout = defaultFindTimeout) {
this.debug('in findAllByCssSelector: ' + selector);
const remote = this.remote.setFindTimeout(timeout);
let elements = await remote.findAllByCssSelector(selector);
this.remote.setFindTimeout(defaultFindTimeout);
if (!elements) elements = [];
this.debug(`Found ${elements.length} for selector ${selector}`);
return elements;
}
}

View file

@ -1,8 +1,11 @@
import _ from 'lodash';
import { defaultFindTimeout } from '../';
import {
defaultFindTimeout,
scenarioManager,
esClient,
elasticDump
} from '../';
import PageObjects from './';
export default class DashboardPage {
@ -12,20 +15,74 @@ export default class DashboardPage {
this.findTimeout = this.remote.setFindTimeout(defaultFindTimeout);
}
gotoDashboardLandingPage() {
return this.findTimeout
.findByCssSelector('a[href="#/dashboard"]')
.click();
async initTests() {
const logstash = scenarioManager.loadIfEmpty('logstashFunctional');
await esClient.deleteAndUpdateConfigDoc({ 'dateFormat:tz':'UTC', 'defaultIndex':'logstash-*' });
PageObjects.common.debug('load kibana index with visualizations');
await elasticDump.elasticLoad('dashboard','.kibana');
await PageObjects.common.navigateToApp('dashboard');
return logstash;
}
/**
* Returns true if already on the dashboard landing page (that page doesn't have a link to itself).
* @returns {Promise<boolean>}
*/
async onDashboardLandingPage() {
PageObjects.common.debug(`onDashboardLandingPage`);
const exists = await PageObjects.common.doesCssSelectorExist('a[href="#/dashboard"]');
return !exists;
}
async gotoDashboardLandingPage() {
PageObjects.common.debug('Go to dashboard landing page');
const onPage = await this.onDashboardLandingPage();
if (!onPage) {
return PageObjects.common.findByCssSelector('a[href="#/dashboard"]').click();
}
}
clickNewDashboard() {
return PageObjects.common.findTestSubject('newDashboardLink')
.click();
return PageObjects.common.findTestSubject('newDashboardLink').click();
}
clickAddVisualization() {
return PageObjects.common.findTestSubject('dashboardAddPanelButton')
.click();
return PageObjects.common.findTestSubject('dashboardAddPanelButton').click();
}
clickOptions() {
return PageObjects.common.findTestSubject('dashboardOptionsButton').click();
}
isOptionsOpen() {
PageObjects.common.debug('isOptionsOpen');
return PageObjects.common.doesTestSubjectExist('dashboardDarkThemeCheckbox');
}
async openOptions() {
PageObjects.common.debug('openOptions');
const isOpen = await this.isOptionsOpen();
if (!isOpen) {
return PageObjects.common.findTestSubject('dashboardOptionsButton').click();
}
}
async isDarkThemeOn() {
PageObjects.common.debug('isDarkThemeOn');
await this.openOptions();
const darkThemeCheckbox = await PageObjects.common.findTestSubject('dashboardDarkThemeCheckbox');
return await darkThemeCheckbox.getProperty('checked');
}
async useDarkTheme(on) {
await this.openOptions();
const isDarkThemeOn = await this.isDarkThemeOn();
if (isDarkThemeOn !== on) {
return PageObjects.common.findTestSubject('dashboardDarkThemeCheckbox').click();
}
}
filterVizNames(vizName) {
@ -42,7 +99,7 @@ export default class DashboardPage {
}
closeAddVizualizationPanel() {
PageObjects.common.debug('-------------close panel');
PageObjects.common.debug('closeAddVizualizationPanel');
return this.findTimeout
.findByCssSelector('i.fa fa-chevron-up')
.click();
@ -73,48 +130,36 @@ export default class DashboardPage {
});
}
saveDashboard(dashName) {
return PageObjects.common.findTestSubject('dashboardSaveButton')
.click()
.then(() => {
return PageObjects.header.isGlobalLoadingIndicatorHidden();
})
.then(() => {
return PageObjects.common.sleep(1000);
})
.then(() => {
PageObjects.common.debug('saveButton button clicked');
return this.findTimeout
.findById('dashboardTitle')
.type(dashName);
})
.then(() => {
return PageObjects.header.isGlobalLoadingIndicatorHidden();
})
.then(() => {
return PageObjects.common.sleep(1000);
})
// click save button
.then(() => {
return PageObjects.common.try(() => {
PageObjects.common.debug('clicking final Save button for named dashboard');
return this.findTimeout
.findByCssSelector('.btn-primary')
.click();
});
})
.then(() => {
return PageObjects.header.isGlobalLoadingIndicatorHidden();
})
async saveDashboard(dashName, storeTimeWithDash) {
await PageObjects.common.findTestSubject('dashboardSaveButton').click();
await PageObjects.header.isGlobalLoadingIndicatorHidden();
await PageObjects.common.sleep(1000);
PageObjects.common.debug('entering new title');
await this.findTimeout.findById('dashboardTitle').type(dashName);
if (storeTimeWithDash !== undefined) {
await this.storeTimeWithDashboard(storeTimeWithDash);
}
await PageObjects.header.isGlobalLoadingIndicatorHidden();
await PageObjects.common.sleep(1000);
await PageObjects.common.try(() => {
PageObjects.common.debug('clicking final Save button for named dashboard');
return this.findTimeout.findByCssSelector('.btn-primary').click();
});
await PageObjects.header.isGlobalLoadingIndicatorHidden();
// verify that green message at the top of the page.
// it's only there for about 5 seconds
.then(() => {
return PageObjects.common.try(() => {
PageObjects.common.debug('verify toast-message for saved dashboard');
return this.findTimeout
await PageObjects.common.try(() => {
PageObjects.common.debug('verify toast-message for saved dashboard');
return this.findTimeout
.findByCssSelector('kbn-truncated.toast-message.ng-isolate-scope')
.getVisibleText();
});
});
}
@ -126,28 +171,18 @@ 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) {
async loadSavedDashboard(dashName) {
PageObjects.common.debug(`Load Saved Dashboard ${dashName}`);
const self = this;
return this.gotoDashboardLandingPage()
.then(function filterDashboard() {
PageObjects.common.debug('Load Saved Dashboard button clicked');
return PageObjects.common.findTestSubject('searchFilter')
.click()
.type(dashName.replace('-',' '));
})
.then(() => {
return PageObjects.header.isGlobalLoadingIndicatorHidden();
})
.then(() => {
return PageObjects.common.sleep(1000);
})
.then(function clickDashboardByLinkedText() {
return self
.clickDashboardByLinkText(dashName);
})
.then(() => {
return PageObjects.header.isGlobalLoadingIndicatorHidden();
});
await this.gotoDashboardLandingPage();
const searchBox = await PageObjects.common.findTestSubject('searchFilter');
await searchBox.click();
await searchBox.type(dashName.replace('-',' '));
await PageObjects.header.isGlobalLoadingIndicatorHidden();
await PageObjects.common.sleep(1000);
await this.clickDashboardByLinkText(dashName);
return PageObjects.header.isGlobalLoadingIndicatorHidden();
}
getPanelTitles() {
@ -216,4 +251,54 @@ export default class DashboardPage {
});
}
getTestVisualizationNames() {
return [
'Visualization PieChart',
'Visualization☺ VerticalBarChart',
'Visualization漢字 AreaChart',
'Visualization☺漢字 DataTable',
'Visualization漢字 LineChart',
'Visualization TileMap',
'Visualization MetricChart'
];
}
addVisualizations(visualizations) {
return visualizations.reduce(function (promise, vizName) {
return promise
.then(() => PageObjects.dashboard.addVisualization(vizName));
}, Promise.resolve());
}
async setTimepickerInDataRange() {
const fromTime = '2015-09-19 06:31:44.000';
const toTime = '2015-09-23 18:31:44.000';
await PageObjects.header.setAbsoluteRange(fromTime, toTime);
}
async storeTimeWithDashboard(on) {
PageObjects.common.debug('Storing time with dashboard: ' + on);
const storeTimeCheckbox = await PageObjects.common.findTestSubject('storeTimeWithDashboard');
const checked = await storeTimeCheckbox.getProperty('checked');
if (checked === true && on === false ||
checked === false && on === true) {
PageObjects.common.debug('Flipping store time checkbox');
await storeTimeCheckbox.click();
}
}
async getFilterDescriptions(timeout = defaultFindTimeout) {
const filters = await PageObjects.common.findAllByCssSelector(
'.filter-bar > .filter > .filter-description',
timeout);
return _.map(filters, async (filter) => await filter.getVisibleText());
}
async filterOnPieSlice() {
PageObjects.common.debug('Filtering on a pie slice');
const slices = await PageObjects.common.findAllByCssSelector('svg > g > path.slice');
PageObjects.common.debug('Slices found:' + slices.length);
return slices[0].click();
}
}

View file

@ -12,28 +12,29 @@ export default class HeaderPage {
}
clickSelector(selector) {
return this.try(() => {
return this.remote.setFindTimeout(defaultFindTimeout)
return this.remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector(selector)
.then(tab => {
return tab.click();
});
});
}
clickDiscover() {
PageObjects.common.debug('click Discover tab');
this.clickSelector('a[href*=\'discover\']');
return PageObjects.common.sleep(3000);
}
clickVisualize() {
PageObjects.common.debug('click Visualize tab');
this.clickSelector('a[href*=\'visualize\']');
return PageObjects.common.sleep(3000);
}
clickDashboard() {
PageObjects.common.debug('click Dashboard tab');
this.clickSelector('a[href*=\'dashboard\']');
return PageObjects.common.sleep(3000);
}
clickSettings() {
@ -48,6 +49,11 @@ export default class HeaderPage {
});
}
clickQuickButton() {
return this.remote.setFindTimeout(defaultFindTimeout)
.findByLinkText('Quick').click();
}
isTimepickerOpen() {
return this.remote.setFindTimeout(2000)
.findDisplayedByCssSelector('.kbn-timepicker')
@ -60,6 +66,20 @@ export default class HeaderPage {
.findByLinkText('Absolute').click();
}
async getFromTime() {
await this.ensureTimePickerIsOpen();
return this.remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('input[ng-model=\'absolute.from\']')
.getProperty('value');
}
async getToTime() {
await this.ensureTimePickerIsOpen();
return this.remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('input[ng-model=\'absolute.to\']')
.getProperty('value');
}
setFromTime(timeString) {
return this.remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('input[ng-model=\'absolute.from\']')
@ -129,4 +149,24 @@ export default class HeaderPage {
.findByCssSelector('[data-test-subj="globalLoadingIndicator"].ng-hide');
}
async ensureTimePickerIsOpen() {
const isOpen = await PageObjects.header.isTimepickerOpen();
PageObjects.common.debug(`time picker open: ${isOpen}`);
if (!isOpen) {
PageObjects.common.debug('--Opening time picker');
await PageObjects.header.clickTimepicker();
}
}
async setQuickTime(quickTime) {
await this.ensureTimePickerIsOpen();
PageObjects.common.debug('--Clicking Quick button');
await this.clickQuickButton();
await this.remote.setFindTimeout(defaultFindTimeout)
.findByLinkText(quickTime).click();
}
async getPrettyDuration() {
return await PageObjects.common.findTestSubject('globalTimepickerRange').getVisibleText();
}
}