Commit aeaac748 by Torkel Ödegaard

New solo panel route working in all scenarios I can test

parent c4f55fec
import $ from 'jquery';
import angular from 'angular';
export class Profiler {
panelsRendered: number;
enabled: boolean;
panelsInitCount: any;
timings: any;
digestCounter: any;
$rootScope: any;
scopeCount: any;
window: any;
init(config, $rootScope) {
this.enabled = config.buildInfo.env === 'development';
this.timings = {};
this.timings.appStart = { loadStart: new Date().getTime() };
this.$rootScope = $rootScope;
this.window = window;
if (!this.enabled) {
return;
}
$rootScope.$watch(
() => {
this.digestCounter++;
return false;
},
() => {}
);
$rootScope.onAppEvent('refresh', this.refresh.bind(this), $rootScope);
$rootScope.onAppEvent('dashboard-fetch-end', this.dashboardFetched.bind(this), $rootScope);
$rootScope.onAppEvent('dashboard-initialized', this.dashboardInitialized.bind(this), $rootScope);
$rootScope.onAppEvent('panel-initialized', this.panelInitialized.bind(this), $rootScope);
}
refresh() {
this.timings.query = 0;
this.timings.render = 0;
setTimeout(() => {
console.log('panel count: ' + this.panelsInitCount);
console.log('total query: ' + this.timings.query);
console.log('total render: ' + this.timings.render);
console.log('avg render: ' + this.timings.render / this.panelsInitCount);
}, 5000);
}
dashboardFetched() {
this.timings.dashboardLoadStart = new Date().getTime();
this.panelsInitCount = 0;
this.digestCounter = 0;
this.panelsInitCount = 0;
this.panelsRendered = 0;
this.timings.query = 0;
this.timings.render = 0;
}
dashboardInitialized() {
setTimeout(() => {
console.log('Dashboard::Performance Total Digests: ' + this.digestCounter);
console.log('Dashboard::Performance Total Watchers: ' + this.getTotalWatcherCount());
console.log('Dashboard::Performance Total ScopeCount: ' + this.scopeCount);
const timeTaken = this.timings.lastPanelInitializedAt - this.timings.dashboardLoadStart;
console.log('Dashboard::Performance All panels initialized in ' + timeTaken + ' ms');
// measure digest performance
const rootDigestStart = window.performance.now();
for (let i = 0; i < 30; i++) {
this.$rootScope.$apply();
}
console.log('Dashboard::Performance Root Digest ' + (window.performance.now() - rootDigestStart) / 30);
}, 3000);
}
getTotalWatcherCount() {
let count = 0;
let scopes = 0;
const root = $(document.getElementsByTagName('body'));
const f = element => {
if (element.data().hasOwnProperty('$scope')) {
scopes++;
angular.forEach(element.data().$scope.$$watchers, () => {
count++;
});
}
angular.forEach(element.children(), childElement => {
f($(childElement));
});
};
f(root);
this.scopeCount = scopes;
return count;
}
renderingCompleted(panelId, panelTimings) {
renderingCompleted(panelId) {
// add render counter to root scope
// used by phantomjs render.js to know when panel has rendered
this.panelsRendered = (this.panelsRendered || 0) + 1;
......@@ -108,21 +22,6 @@ export class Profiler {
// this window variable is used by backend rendering tools to know
// all panels have completed rendering
this.window.panelsRendered = this.panelsRendered;
if (this.enabled) {
panelTimings.renderEnd = new Date().getTime();
this.timings.query += panelTimings.queryEnd - panelTimings.queryStart;
this.timings.render += panelTimings.renderEnd - panelTimings.renderStart;
}
}
panelInitialized() {
if (!this.enabled) {
return;
}
this.panelsInitCount++;
this.timings.lastPanelInitializedAt = new Date().getTime();
}
}
......
......@@ -5,21 +5,27 @@ import { connect } from 'react-redux';
// Utils & Services
import appEvents from 'app/core/app_events';
import locationUtil from 'app/core/utils/location_util';
import { getBackendSrv } from 'app/core/services/backend_srv';
// Components
import { DashboardPanel } from '../dashgrid/DashboardPanel';
// Redux
import { updateLocation } from 'app/core/actions';
// Types
import { StoreState } from 'app/types';
import { PanelModel, DashboardModel } from 'app/features/dashboard/state';
interface Props {
panelId: string;
uid?: string;
slug?: string;
type?: string;
urlUid?: string;
urlSlug?: string;
urlType?: string;
$scope: any;
$injector: any;
updateLocation: typeof updateLocation;
}
interface State {
......@@ -37,19 +43,34 @@ export class SoloPanelPage extends Component<Props, State> {
};
componentDidMount() {
const { $injector, $scope, uid } = this.props;
const { $injector, $scope, urlUid, urlType, urlSlug } = this.props;
// handle old urls with no uid
if (!urlUid && !(urlType === 'script' || urlType === 'snapshot')) {
this.redirectToNewUrl();
return;
}
const dashboardLoaderSrv = $injector.get('dashboardLoaderSrv');
// subscribe to event to know when dashboard controller is done with inititalization
appEvents.on('dashboard-initialized', this.onDashoardInitialized);
dashboardLoaderSrv.loadDashboard('', '', uid).then(result => {
dashboardLoaderSrv.loadDashboard(urlType, urlSlug, urlUid).then(result => {
result.meta.soloMode = true;
$scope.initDashboard(result, $scope);
});
}
redirectToNewUrl() {
getBackendSrv().getDashboardBySlug(this.props.urlSlug).then(res => {
if (res) {
const url = locationUtil.stripBaseFromUrl(res.meta.url.replace('/d/', '/d-solo/'));
this.props.updateLocation(url);
}
});
}
onDashoardInitialized = () => {
const { $scope, panelId } = this.props;
......@@ -89,13 +110,14 @@ export class SoloPanelPage extends Component<Props, State> {
}
const mapStateToProps = (state: StoreState) => ({
uid: state.location.routeParams.uid,
slug: state.location.routeParams.slug,
type: state.location.routeParams.type,
urlUid: state.location.routeParams.uid,
urlSlug: state.location.routeParams.slug,
urlType: state.location.routeParams.type,
panelId: state.location.query.panelId
});
const mapDispatchToProps = {
updateLocation
};
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(SoloPanelPage));
......@@ -135,11 +135,8 @@ export class DataPanel extends Component<Props, State> {
cacheTimeout: null,
};
console.log('Issuing DataPanel query', queryOptions);
const resp = await ds.query(queryOptions);
console.log('Issuing DataPanel query Resp', resp);
if (this.isUnmounted) {
return;
}
......
......@@ -12,11 +12,12 @@ import { DataPanel } from './DataPanel';
// Utils
import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel';
import { PANEL_HEADER_HEIGHT } from 'app/core/constants';
import { profiler } from 'app/core/profiler';
// Types
import { DashboardModel, PanelModel } from '../state';
import { PanelPlugin } from 'app/types';
import { TimeRange } from '@grafana/ui';
import { TimeRange, LoadingState } from '@grafana/ui';
import variables from 'sass/_variables.scss';
import templateSrv from 'app/features/templating/template_srv';
......@@ -98,6 +99,12 @@ export class PanelChrome extends PureComponent<Props, State> {
const { timeRange, renderCounter } = this.state;
const PanelComponent = plugin.exports.Panel;
// This is only done to increase a counter that is used by backend
// image rendering (phantomjs/headless chrome) to know when to capture image
if (loading === LoadingState.Done) {
profiler.renderingCompleted(panel.id);
}
return (
<div className="panel-content">
<PanelComponent
......
import './panel_header';
import './panel_directive';
import './solo_panel_ctrl';
import './query_ctrl';
import './panel_editor_tab';
import './query_editor_row';
......
......@@ -16,7 +16,6 @@ class MetricsPanelCtrl extends PanelCtrl {
datasourceSrv: any;
timeSrv: any;
templateSrv: any;
timing: any;
range: any;
interval: any;
intervalMs: any;
......@@ -81,7 +80,6 @@ class MetricsPanelCtrl extends PanelCtrl {
this.loading = true;
// load datasource service
this.setTimeQueryStart();
this.datasourceSrv
.get(this.panel.datasource)
.then(this.updateTimeRange.bind(this))
......@@ -112,14 +110,6 @@ class MetricsPanelCtrl extends PanelCtrl {
});
}
setTimeQueryStart() {
this.timing.queryStart = new Date().getTime();
}
setTimeQueryEnd() {
this.timing.queryEnd = new Date().getTime();
}
updateTimeRange(datasource?) {
this.datasource = datasource || this.datasource;
this.range = this.timeSrv.timeRange();
......@@ -181,7 +171,6 @@ class MetricsPanelCtrl extends PanelCtrl {
}
handleQueryResult(result) {
this.setTimeQueryEnd();
this.loading = false;
// check for if data source returns subject
......
import _ from 'lodash';
import $ from 'jquery';
import Remarkable from 'remarkable';
import config from 'app/core/config';
......@@ -13,7 +12,7 @@ import {
sharePanel as sharePanelUtil,
} from 'app/features/dashboard/utils/panel';
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT, PANEL_HEADER_HEIGHT, PANEL_BORDER } from 'app/core/constants';
import { GRID_COLUMN_COUNT, PANEL_HEADER_HEIGHT, PANEL_BORDER } from 'app/core/constants';
export class PanelCtrl {
panel: any;
......@@ -31,8 +30,8 @@ export class PanelCtrl {
height: any;
containerHeight: any;
events: Emitter;
timing: any;
loading: boolean;
timing: any;
maxPanelsPerRowOptions: number[];
constructor($scope, $injector) {
......@@ -42,7 +41,7 @@ export class PanelCtrl {
this.$timeout = $injector.get('$timeout');
this.editorTabs = [];
this.events = this.panel.events;
this.timing = {};
this.timing = {}; // not used but here to not break plugins
const plugin = config.panels[this.panel.type];
if (plugin) {
......@@ -59,7 +58,7 @@ export class PanelCtrl {
}
renderingCompleted() {
profiler.renderingCompleted(this.panel.id, this.timing);
profiler.renderingCompleted(this.panel.id);
}
refresh() {
......@@ -200,24 +199,12 @@ export class PanelCtrl {
return this.dashboard.meta.fullscreen && !this.panel.fullscreen;
}
calculatePanelHeight() {
if (this.panel.isEditing) {
this.containerHeight = $('.panel-wrapper--edit').height();
} else if (this.panel.fullscreen) {
this.containerHeight = $('.panel-wrapper--view').height();
} else {
this.containerHeight = this.panel.gridPos.h * GRID_CELL_HEIGHT + (this.panel.gridPos.h - 1) * GRID_CELL_VMARGIN;
}
if (this.panel.soloMode) {
this.containerHeight = $(window).height();
}
calculatePanelHeight(containerHeight) {
this.containerHeight = containerHeight;
this.height = this.containerHeight - (PANEL_BORDER + PANEL_HEADER_HEIGHT);
}
render(payload?) {
this.timing.renderStart = new Date().getTime();
this.events.emit('render', payload);
}
......
......@@ -101,7 +101,7 @@ module.directive('grafanaPanel', ($rootScope, $document, $timeout) => {
});
ctrl.events.on('panel-size-changed', () => {
ctrl.calculatePanelHeight();
ctrl.calculatePanelHeight(panelContainer[0].offsetHeight);
$timeout(() => {
resizeScrollableContent();
ctrl.render();
......@@ -112,19 +112,21 @@ module.directive('grafanaPanel', ($rootScope, $document, $timeout) => {
// first wait one pass for dashboard fullscreen view mode to take effect (classses being applied)
setTimeout(() => {
// then recalc style
ctrl.calculatePanelHeight();
ctrl.calculatePanelHeight(panelContainer[0].offsetHeight);
// then wait another cycle (this might not be needed)
$timeout(() => {
ctrl.render();
resizeScrollableContent();
});
});
}, 10);
});
ctrl.events.on('render', () => {
// set initial height
ctrl.calculatePanelHeight();
if (!ctrl.height) {
ctrl.calculatePanelHeight(panelContainer[0].offsetHeight);
}
ctrl.events.on('render', () => {
if (transparentLastState !== ctrl.panel.transparent) {
panelContainer.toggleClass('panel-transparent', ctrl.panel.transparent === true);
transparentLastState = ctrl.panel.transparent;
......
<div class="panel-solo" ng-if="panel">
<plugin-component type="panel">
</plugin-component>
</div>
import angular from 'angular';
import locationUtil from 'app/core/utils/location_util';
import appEvents from 'app/core/app_events';
export class SoloPanelCtrl {
/** @ngInject */
constructor($scope, $routeParams, $location, dashboardLoaderSrv, contextSrv, backendSrv) {
let panelId;
$scope.init = () => {
contextSrv.sidemenu = false;
appEvents.emit('toggle-sidemenu-hidden');
const params = $location.search();
panelId = parseInt(params.panelId, 10);
appEvents.on('dashboard-initialized', $scope.initPanelScope);
// if no uid, redirect to new route based on slug
if (!($routeParams.type === 'script' || $routeParams.type === 'snapshot') && !$routeParams.uid) {
backendSrv.getDashboardBySlug($routeParams.slug).then(res => {
if (res) {
const url = locationUtil.stripBaseFromUrl(res.meta.url.replace('/d/', '/d-solo/'));
$location.path(url).replace();
}
});
return;
}
dashboardLoaderSrv.loadDashboard($routeParams.type, $routeParams.slug, $routeParams.uid).then(result => {
result.meta.soloMode = true;
$scope.initDashboard(result, $scope);
});
};
$scope.initPanelScope = () => {
const panelInfo = $scope.dashboard.getPanelInfoById(panelId);
// fake row ctrl scope
$scope.ctrl = {
dashboard: $scope.dashboard,
};
$scope.panel = panelInfo.panel;
$scope.panel.soloMode = true;
$scope.$index = 0;
if (!$scope.panel) {
$scope.appEvent('alert-error', ['Panel not found', '']);
return;
}
};
$scope.init();
}
}
angular.module('grafana.routes').controller('SoloPanelCtrl', SoloPanelCtrl);
......@@ -80,7 +80,6 @@ class TablePanelCtrl extends MetricsPanelCtrl {
this.pageIndex = 0;
if (this.panel.transform === 'annotations') {
this.setTimeQueryStart();
return this.annotationsSrv
.getAnnotations({
dashboard: this.dashboard,
......
......@@ -59,10 +59,11 @@ export function setupAngularRoutes($routeProvider, $locationProvider) {
},
})
.when('/dashboard-solo/:type/:slug', {
templateUrl: 'public/app/features/panel/partials/soloPanel.html',
controller: 'SoloPanelCtrl',
reloadOnSearch: false,
pageClass: 'page-dashboard',
template: '<react-container />',
pageClass: 'dashboard-solo',
resolve: {
component: () => SoloPanelPage,
},
})
.when('/dashboard/new', {
templateUrl: 'public/app/partials/dashboard.html',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment