Update dashboard based on url changes (#12123)

* Update dashboard based on url changes

* make clickSelector a retry in case it fails the first time
This commit is contained in:
Stacey Gammon 2017-06-02 12:38:47 -04:00 committed by GitHub
parent 829e0bd64d
commit 40bc831d80
6 changed files with 83 additions and 22 deletions

View file

@ -105,12 +105,26 @@ app.directive('dashboardApp', function ($injector) {
dashboardState.syncTimefilterWithDashboard(timefilter, quickRanges);
}
const updateState = () => {
// 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(),
darkTheme: dashboardState.getDarkTheme(),
timeRestore: dashboardState.getTimeRestore(),
title: dashboardState.getTitle(),
description: dashboardState.getDescription(),
};
$scope.panels = dashboardState.getPanels();
};
// Part of the exposed plugin API - do not remove without careful consideration.
this.appStatus = {
dirty: !dash.id
};
dashboardState.stateMonitor.onChange(status => {
this.appStatus.dirty = status.dirty || !dash.id;
updateState();
});
dashboardState.applyFilters(dashboardState.getQuery(), filterBar.getFilters());
@ -121,17 +135,8 @@ app.directive('dashboardApp', function ($injector) {
dash.searchSource.version(true);
courier.setRootSearchSource(dash.searchSource);
// 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(),
darkTheme: dashboardState.getDarkTheme(),
timeRestore: dashboardState.getTimeRestore(),
title: dashboardState.getTitle(),
description: dashboardState.getDescription(),
};
updateState();
$scope.panels = dashboardState.getPanels();
$scope.refresh = (...args) => {
$rootScope.$broadcast('fetch');
courier.fetch(...args);

View file

@ -135,11 +135,27 @@ app.directive('dashboardGrid', function ($compile, Notifier) {
$scope.$watchCollection('panels', function (panels) {
const currentPanels = gridster.$widgets.toArray().map(
el => PanelUtils.findPanelByPanelIndex(el.panelIndex, $scope.panels)
el => {
const panel = PanelUtils.findPanelByPanelIndex(el.panelIndex, $scope.panels);
if (panel) {
// A panel may have had its state updated, refresh gridster with the latest values.
const panelElement = panelElementMapping[panel.panelIndex];
PanelUtils.refreshElementSizeAndPosition(panel, panelElement);
return panel;
} else {
return { panelIndex: el.panelIndex };
}
}
);
// panels that have been added
// Panels in the grid that are missing from the panels array. This can happen if the url is modified, and a
// panel is manually removed.
const removed = _.difference(currentPanels, panels);
// Panels that have been added.
const added = _.difference(panels, currentPanels);
removed.forEach(panel => $scope.removePanel(panel.panelIndex));
if (added.length) {
// See issue https://github.com/elastic/kibana/issues/2138 and the
// subsequent fix for why we need to sort here. Short story is that
@ -157,9 +173,10 @@ app.directive('dashboardGrid', function ($compile, Notifier) {
added.forEach(addPanel);
}
if (added.length) {
if (added.length || removed.length) {
$scope.saveState();
}
layout();
});
$scope.$on('$destroy', function () {

View file

@ -35,6 +35,19 @@ export class PanelUtils {
panel.row = data.row;
}
/**
* Ensures that the grid element matches the latest size/pos info in the panel element.
* @param {PanelState} panel
* @param {Element} panelElement - jQuery element representing the element in the UI
*/
static refreshElementSizeAndPosition(panel, panelElement) {
const data = panelElement.coords().grid;
data.size_x = panel.size_x;
data.size_y = panel.size_y;
data.col = panel.col;
data.row = panel.row;
}
/**
* Returns the panel with the given panelIndex from the panels array (*NOT* the panel at the given index).
* @param panelIndex {number} - Note this is *NOT* the index of the panel in the panels array.

View file

@ -8,6 +8,7 @@ import {
export default function ({ getService, getPageObjects }) {
const retry = getService('retry');
const log = getService('log');
const remote = getService('remote');
const screenshots = getService('screenshots');
const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'visualize']);
@ -117,7 +118,7 @@ export default function ({ getService, getPageObjects }) {
});
});
it('should have panels with expected data-shared-item title and description', function checkTitles() {
it('should have panels with expected data-shared-item title and description', function () {
const visualizations = PageObjects.dashboard.getTestVisualizations();
return retry.tryForTime(10000, function () {
return PageObjects.dashboard.getPanelSharedItemData()
@ -128,7 +129,7 @@ export default function ({ getService, getPageObjects }) {
});
});
it('add new visualization link', async function checkTitles() {
it('add new visualization link', async function () {
await PageObjects.dashboard.clickAddVisualization();
await PageObjects.dashboard.clickAddNewVisualizationLink();
await PageObjects.visualize.clickAreaChart();
@ -144,5 +145,27 @@ export default function ({ getService, getPageObjects }) {
expect(panelTitles.length).to.eql(visualizations.length + 1);
});
});
describe('Directly modifying url updates dashboard state', () => {
it('for query parameter', async function () {
const currentQuery = await PageObjects.dashboard.getQuery();
expect(currentQuery).to.equal('');
const currentUrl = await remote.getCurrentUrl();
const newUrl = currentUrl.replace('query:%27*%27', 'query:%27hi%27');
// Don't add the timestamp to the url or it will cause a hard refresh and we want to test a
// soft refresh.
await remote.get(newUrl.toString(), false);
const newQuery = await PageObjects.dashboard.getQuery();
expect(newQuery).to.equal('hi');
});
it('for panel size parameters', async function () {
const currentUrl = await remote.getCurrentUrl();
const newUrl = currentUrl.replace(`size_x:${DEFAULT_PANEL_WIDTH}`, `size_x:${DEFAULT_PANEL_WIDTH * 2}`);
await remote.get(newUrl.toString(), false);
const allPanelInfo = await PageObjects.dashboard.getPanelSizeData();
expect(allPanelInfo[0].dataSizeX).to.equal(`${DEFAULT_PANEL_WIDTH * 2}`);
});
});
});
}

View file

@ -12,8 +12,8 @@ export function HeaderPageProvider({ getService, getPageObjects }) {
class HeaderPage {
async clickSelector(selector) {
remote.setFindTimeout(defaultFindTimeout);
await remote.findByCssSelector(selector).click();
log.debug(`clickSelector(${selector})`);
await retry.try(async () => await remote.findByCssSelector(selector).click());
}
async clickDiscover() {

View file

@ -2,12 +2,15 @@ import { modifyUrl } from '../../../../src/utils';
export const createRemoteInterceptors = remote => ({
// inject _t=Date query param on navigation
async get(url) {
const urlWithTime = modifyUrl(url, parsed => {
parsed.query._t = Date.now();
});
async get(url, insertTimestamp = true) {
if (insertTimestamp) {
const urlWithTime = modifyUrl(url, parsed => {
parsed.query._t = Date.now();
});
return await remote.get(urlWithTime);
return await remote.get(urlWithTime);
}
return await remote.get(url);
},
// strip _t=Date query param when url is read