Commit 5210a8f2 by Ryan McKinley Committed by GitHub

Dashboards: add panel inspector (#20052)

parent 4424c2f4
...@@ -13,6 +13,7 @@ export interface BuildInfo { ...@@ -13,6 +13,7 @@ export interface BuildInfo {
interface FeatureToggles { interface FeatureToggles {
transformations: boolean; transformations: boolean;
inspect: boolean;
expressions: boolean; expressions: boolean;
} }
export class GrafanaBootConfig { export class GrafanaBootConfig {
...@@ -48,6 +49,7 @@ export class GrafanaBootConfig { ...@@ -48,6 +49,7 @@ export class GrafanaBootConfig {
pluginsToPreload: string[] = []; pluginsToPreload: string[] = [];
featureToggles: FeatureToggles = { featureToggles: FeatureToggles = {
transformations: false, transformations: false,
inspect: false,
expressions: false, expressions: false,
}; };
......
...@@ -13,6 +13,7 @@ import 'mousetrap-global-bind'; ...@@ -13,6 +13,7 @@ import 'mousetrap-global-bind';
import { ContextSrv } from './context_srv'; import { ContextSrv } from './context_srv';
import { ILocationService, ITimeoutService, IRootScopeService } from 'angular'; import { ILocationService, ITimeoutService, IRootScopeService } from 'angular';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl'; import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
import { getLocationSrv } from '@grafana/runtime';
export class KeybindingSrv { export class KeybindingSrv {
helpModal: boolean; helpModal: boolean;
...@@ -264,6 +265,13 @@ export class KeybindingSrv { ...@@ -264,6 +265,13 @@ export class KeybindingSrv {
} }
}); });
// inspect panel
this.bind('p i', () => {
if (dashboard.meta.focusPanelId) {
getLocationSrv().update({ partial: true, query: { inspect: dashboard.meta.focusPanelId } });
}
});
// toggle panel legend // toggle panel legend
this.bind('p l', () => { this.bind('p l', () => {
if (dashboard.meta.focusPanelId) { if (dashboard.meta.focusPanelId) {
......
// Libraries
import React, { PureComponent } from 'react';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { JSONFormatter, Modal } from '@grafana/ui';
import { css } from 'emotion';
import { getLocationSrv } from '@grafana/runtime';
interface Props {
dashboard: DashboardModel;
panel: PanelModel;
}
interface State {}
export class PanelInspector extends PureComponent<Props, State> {
constructor(props: Props) {
super(props);
}
onDismiss = () => {
getLocationSrv().update({
query: { inspect: null },
partial: true,
});
};
render() {
const { panel } = this.props;
if (!panel) {
this.onDismiss(); // Try to close the component
return null;
}
const bodyStyle = css`
max-height: 70vh;
overflow-y: scroll;
`;
// TODO? should we get the result with an observable once?
const data = (panel.getQueryRunner() as any).lastResult;
return (
<Modal
title={
<div className="modal-header-title">
<i className="fa fa-info-circle" />
<span className="p-l-1">{panel.title ? panel.title : 'Panel'}</span>
</div>
}
onDismiss={this.onDismiss}
isOpen={true}
>
<div className={bodyStyle}>
<JSONFormatter json={data} open={2} />
</div>
</Modal>
);
}
}
...@@ -31,6 +31,7 @@ import { ...@@ -31,6 +31,7 @@ import {
AppNotificationSeverity, AppNotificationSeverity,
} from 'app/types'; } from 'app/types';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { PanelInspector } from '../components/Inspector/PanelInspector';
export interface Props { export interface Props {
urlUid?: string; urlUid?: string;
urlSlug?: string; urlSlug?: string;
...@@ -38,6 +39,7 @@ export interface Props { ...@@ -38,6 +39,7 @@ export interface Props {
editview?: string; editview?: string;
urlPanelId?: string; urlPanelId?: string;
urlFolderId?: string; urlFolderId?: string;
inspectPanelId?: string;
$scope: any; $scope: any;
$injector: any; $injector: any;
routeInfo: DashboardRouteInfo; routeInfo: DashboardRouteInfo;
...@@ -250,7 +252,7 @@ export class DashboardPage extends PureComponent<Props, State> { ...@@ -250,7 +252,7 @@ export class DashboardPage extends PureComponent<Props, State> {
} }
render() { render() {
const { dashboard, editview, $injector, isInitSlow, initError } = this.props; const { dashboard, editview, $injector, isInitSlow, initError, inspectPanelId } = this.props;
const { isSettingsOpening, isEditing, isFullscreen, scrollTop, updateScrollTop } = this.state; const { isSettingsOpening, isEditing, isFullscreen, scrollTop, updateScrollTop } = this.state;
if (!dashboard) { if (!dashboard) {
...@@ -270,6 +272,9 @@ export class DashboardPage extends PureComponent<Props, State> { ...@@ -270,6 +272,9 @@ export class DashboardPage extends PureComponent<Props, State> {
'dashboard-container--has-submenu': dashboard.meta.submenuEnabled, 'dashboard-container--has-submenu': dashboard.meta.submenuEnabled,
}); });
// Find the panel to inspect
const inspectPanel = inspectPanelId ? dashboard.getPanelById(parseInt(inspectPanelId, 10)) : null;
// Only trigger render when the scroll has moved by 25 // Only trigger render when the scroll has moved by 25
const approximateScrollTop = Math.round(scrollTop / 25) * 25; const approximateScrollTop = Math.round(scrollTop / 25) * 25;
...@@ -306,6 +311,8 @@ export class DashboardPage extends PureComponent<Props, State> { ...@@ -306,6 +311,8 @@ export class DashboardPage extends PureComponent<Props, State> {
</div> </div>
</CustomScrollbar> </CustomScrollbar>
</div> </div>
{inspectPanel && <PanelInspector dashboard={dashboard} panel={inspectPanel} />}
</div> </div>
); );
} }
...@@ -320,6 +327,7 @@ export const mapStateToProps = (state: StoreState) => ({ ...@@ -320,6 +327,7 @@ export const mapStateToProps = (state: StoreState) => ({
urlFolderId: state.location.query.folderId, urlFolderId: state.location.query.folderId,
urlFullscreen: !!state.location.query.fullscreen, urlFullscreen: !!state.location.query.fullscreen,
urlEdit: !!state.location.query.edit, urlEdit: !!state.location.query.edit,
inspectPanelId: state.location.query.inspect,
initPhase: state.dashboard.initPhase, initPhase: state.dashboard.initPhase,
isInitSlow: state.dashboard.isInitSlow, isInitSlow: state.dashboard.isInitSlow,
initError: state.dashboard.initError, initError: state.dashboard.initError,
......
import { updateLocation } from 'app/core/actions'; import { updateLocation } from 'app/core/actions';
import { store } from 'app/store/store'; import { store } from 'app/store/store';
import config from 'app/core/config';
import { removePanel, duplicatePanel, copyPanel, editPanelJson, sharePanel } from 'app/features/dashboard/utils/panel'; import { removePanel, duplicatePanel, copyPanel, editPanelJson, sharePanel } from 'app/features/dashboard/utils/panel';
import { PanelModel } from 'app/features/dashboard/state/PanelModel'; import { PanelModel } from 'app/features/dashboard/state/PanelModel';
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
import { getLocationSrv } from '@grafana/runtime';
import { PanelMenuItem } from '@grafana/data'; import { PanelMenuItem } from '@grafana/data';
export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => { export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => {
...@@ -37,6 +39,15 @@ export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => { ...@@ -37,6 +39,15 @@ export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => {
sharePanel(dashboard, panel); sharePanel(dashboard, panel);
}; };
const onInspectPanel = () => {
getLocationSrv().update({
partial: true,
query: {
inspect: panel.id,
},
});
};
const onDuplicatePanel = () => { const onDuplicatePanel = () => {
duplicatePanel(dashboard, panel); duplicatePanel(dashboard, panel);
}; };
...@@ -78,6 +89,15 @@ export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => { ...@@ -78,6 +89,15 @@ export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => {
shortcut: 'p s', shortcut: 'p s',
}); });
if (config.featureToggles.inspect) {
menu.push({
text: 'Inspect',
iconClassName: 'fa fa-fw fa-info-circle',
onClick: onInspectPanel,
shortcut: 'p i',
});
}
const subMenu: PanelMenuItem[] = []; const subMenu: PanelMenuItem[] = [];
if (!panel.fullscreen && dashboard.meta.canEdit) { if (!panel.fullscreen && dashboard.meta.canEdit) {
......
...@@ -18,6 +18,7 @@ import { auto } from 'angular'; ...@@ -18,6 +18,7 @@ import { auto } from 'angular';
import { TemplateSrv } from '../templating/template_srv'; import { TemplateSrv } from '../templating/template_srv';
import { getPanelLinksSupplier } from './panellinks/linkSuppliers'; import { getPanelLinksSupplier } from './panellinks/linkSuppliers';
import { renderMarkdown, AppEvent, PanelEvents, PanelPluginMeta } from '@grafana/data'; import { renderMarkdown, AppEvent, PanelEvents, PanelPluginMeta } from '@grafana/data';
import { getLocationSrv } from '@grafana/runtime';
export class PanelCtrl { export class PanelCtrl {
panel: any; panel: any;
...@@ -145,6 +146,15 @@ export class PanelCtrl { ...@@ -145,6 +146,15 @@ export class PanelCtrl {
shortcut: 'p s', shortcut: 'p s',
}); });
if (config.featureToggles.inspect) {
menu.push({
text: 'Inspect',
icon: 'fa fa-fw fa-info-circle',
click: 'ctrl.inspectPanel();',
shortcut: 'p i',
});
}
// Additional items from sub-class // Additional items from sub-class
menu.push(...(await this.getAdditionalMenuItems())); menu.push(...(await this.getAdditionalMenuItems()));
...@@ -234,6 +244,15 @@ export class PanelCtrl { ...@@ -234,6 +244,15 @@ export class PanelCtrl {
sharePanelUtil(this.dashboard, this.panel); sharePanelUtil(this.dashboard, this.panel);
} }
inspectPanel() {
getLocationSrv().update({
query: {
inspect: this.panel.id,
},
partial: true,
});
}
getInfoMode() { getInfoMode() {
if (this.error) { if (this.error) {
return 'error'; return 'error';
......
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