2022-04-22 08:33:13 -05:00
|
|
|
import React, { PureComponent } from 'react';
|
2020-04-27 13:50:33 -05:00
|
|
|
import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux';
|
2022-04-22 08:33:13 -05:00
|
|
|
import { Subscription } from 'rxjs';
|
|
|
|
|
2021-08-19 01:47:37 -05:00
|
|
|
import { getDefaultTimeRange, LoadingState, PanelData, PanelPlugin } from '@grafana/data';
|
2023-09-12 03:54:41 -05:00
|
|
|
import { AngularComponent, getAngularLoader } from '@grafana/runtime';
|
2023-04-25 11:16:46 -05:00
|
|
|
import { PanelChrome } from '@grafana/ui';
|
2020-03-16 03:48:44 -05:00
|
|
|
import config from 'app/core/config';
|
|
|
|
import { PANEL_BORDER } from 'app/core/constants';
|
2022-04-22 08:33:13 -05:00
|
|
|
import { setPanelAngularComponent } from 'app/features/panel/state/reducers';
|
2021-10-13 01:53:36 -05:00
|
|
|
import { getPanelStateForModel } from 'app/features/panel/state/selectors';
|
2022-04-22 08:33:13 -05:00
|
|
|
import { StoreState } from 'app/types';
|
|
|
|
|
|
|
|
import { getTimeSrv, TimeSrv } from '../services/TimeSrv';
|
|
|
|
import { DashboardModel, PanelModel } from '../state';
|
2023-04-25 11:16:46 -05:00
|
|
|
import { getPanelChromeProps } from '../utils/getPanelChromeProps';
|
2022-04-22 08:33:13 -05:00
|
|
|
|
2023-09-12 03:54:41 -05:00
|
|
|
import { PanelHeaderMenuWrapper } from './PanelHeader/PanelHeaderMenuWrapper';
|
2020-02-09 03:53:34 -06:00
|
|
|
|
2020-02-28 04:04:40 -06:00
|
|
|
interface OwnProps {
|
2020-02-09 03:53:34 -06:00
|
|
|
panel: PanelModel;
|
|
|
|
dashboard: DashboardModel;
|
|
|
|
plugin: PanelPlugin;
|
2020-04-10 09:37:26 -05:00
|
|
|
isViewing: boolean;
|
|
|
|
isEditing: boolean;
|
2020-02-09 03:53:34 -06:00
|
|
|
isInView: boolean;
|
2023-04-26 07:06:38 -05:00
|
|
|
isDraggable?: boolean;
|
2020-02-09 03:53:34 -06:00
|
|
|
width: number;
|
|
|
|
height: number;
|
2023-04-25 11:16:46 -05:00
|
|
|
hideMenu?: boolean;
|
2020-02-09 03:53:34 -06:00
|
|
|
}
|
|
|
|
|
2020-02-28 04:04:40 -06:00
|
|
|
interface ConnectedProps {
|
2021-10-13 01:53:36 -05:00
|
|
|
angularComponent?: AngularComponent;
|
2020-02-28 04:04:40 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
interface DispatchProps {
|
|
|
|
setPanelAngularComponent: typeof setPanelAngularComponent;
|
|
|
|
}
|
|
|
|
|
|
|
|
export type Props = OwnProps & ConnectedProps & DispatchProps;
|
|
|
|
|
2020-02-09 03:53:34 -06:00
|
|
|
export interface State {
|
|
|
|
data: PanelData;
|
|
|
|
errorMessage?: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface AngularScopeProps {
|
|
|
|
panel: PanelModel;
|
|
|
|
dashboard: DashboardModel;
|
|
|
|
size: {
|
|
|
|
height: number;
|
|
|
|
width: number;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-02-28 04:04:40 -06:00
|
|
|
export class PanelChromeAngularUnconnected extends PureComponent<Props, State> {
|
2020-02-17 00:25:27 -06:00
|
|
|
element: HTMLElement | null = null;
|
2020-02-09 03:53:34 -06:00
|
|
|
timeSrv: TimeSrv = getTimeSrv();
|
|
|
|
scopeProps?: AngularScopeProps;
|
2020-12-23 03:45:31 -06:00
|
|
|
subs = new Subscription();
|
2020-02-09 03:53:34 -06:00
|
|
|
constructor(props: Props) {
|
|
|
|
super(props);
|
|
|
|
this.state = {
|
|
|
|
data: {
|
|
|
|
state: LoadingState.NotStarted,
|
|
|
|
series: [],
|
2021-01-07 04:26:56 -06:00
|
|
|
timeRange: getDefaultTimeRange(),
|
2020-02-09 03:53:34 -06:00
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
const { panel } = this.props;
|
|
|
|
this.loadAngularPanel();
|
|
|
|
|
|
|
|
// subscribe to data events
|
|
|
|
const queryRunner = panel.getQueryRunner();
|
2020-05-25 07:05:43 -05:00
|
|
|
|
|
|
|
// we are not displaying any of this data so no need for transforms or field config
|
2020-12-23 03:45:31 -06:00
|
|
|
this.subs.add(
|
|
|
|
queryRunner.getData({ withTransforms: false, withFieldConfig: false }).subscribe({
|
|
|
|
next: (data: PanelData) => this.onPanelDataUpdate(data),
|
|
|
|
})
|
|
|
|
);
|
2020-02-09 03:53:34 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
onPanelDataUpdate(data: PanelData) {
|
2020-02-17 00:25:27 -06:00
|
|
|
let errorMessage: string | undefined;
|
2020-02-09 03:53:34 -06:00
|
|
|
|
|
|
|
if (data.state === LoadingState.Error) {
|
|
|
|
const { error } = data;
|
|
|
|
if (error) {
|
|
|
|
if (errorMessage !== error.message) {
|
|
|
|
errorMessage = error.message;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.setState({ data, errorMessage });
|
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
2020-12-23 03:45:31 -06:00
|
|
|
this.subs.unsubscribe();
|
2022-11-08 09:26:02 -06:00
|
|
|
if (this.props.angularComponent) {
|
|
|
|
this.props.angularComponent?.destroy();
|
|
|
|
}
|
2020-02-09 03:53:34 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
componentDidUpdate(prevProps: Props, prevState: State) {
|
2020-02-17 00:28:53 -06:00
|
|
|
const { plugin, height, width, panel } = this.props;
|
|
|
|
|
|
|
|
if (prevProps.plugin !== plugin) {
|
2020-02-09 03:53:34 -06:00
|
|
|
this.loadAngularPanel();
|
|
|
|
}
|
2020-02-17 00:28:53 -06:00
|
|
|
|
|
|
|
if (prevProps.width !== width || prevProps.height !== height) {
|
|
|
|
if (this.scopeProps) {
|
2020-03-16 03:48:44 -05:00
|
|
|
this.scopeProps.size.height = this.getInnerPanelHeight();
|
|
|
|
this.scopeProps.size.width = this.getInnerPanelWidth();
|
2020-12-23 03:45:31 -06:00
|
|
|
panel.render();
|
2020-02-17 00:28:53 -06:00
|
|
|
}
|
|
|
|
}
|
2020-02-09 03:53:34 -06:00
|
|
|
}
|
|
|
|
|
2020-03-16 03:48:44 -05:00
|
|
|
getInnerPanelHeight() {
|
|
|
|
const { plugin, height } = this.props;
|
|
|
|
const { theme } = config;
|
|
|
|
|
|
|
|
const headerHeight = this.hasOverlayHeader() ? 0 : theme.panelHeaderHeight;
|
|
|
|
const chromePadding = plugin.noPadding ? 0 : theme.panelPadding;
|
|
|
|
return height - headerHeight - chromePadding * 2 - PANEL_BORDER;
|
|
|
|
}
|
|
|
|
|
|
|
|
getInnerPanelWidth() {
|
|
|
|
const { plugin, width } = this.props;
|
|
|
|
const { theme } = config;
|
|
|
|
|
|
|
|
const chromePadding = plugin.noPadding ? 0 : theme.panelPadding;
|
|
|
|
return width - chromePadding * 2 - PANEL_BORDER;
|
|
|
|
}
|
|
|
|
|
2020-02-09 03:53:34 -06:00
|
|
|
loadAngularPanel() {
|
2020-03-16 03:48:44 -05:00
|
|
|
const { panel, dashboard, setPanelAngularComponent } = this.props;
|
2020-02-09 03:53:34 -06:00
|
|
|
|
|
|
|
// if we have no element or already have loaded the panel return
|
2020-02-28 04:04:40 -06:00
|
|
|
if (!this.element) {
|
2020-02-09 03:53:34 -06:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const loader = getAngularLoader();
|
|
|
|
const template = '<plugin-component type="panel" class="panel-height-helper"></plugin-component>';
|
|
|
|
|
|
|
|
this.scopeProps = {
|
|
|
|
panel: panel,
|
|
|
|
dashboard: dashboard,
|
2020-03-16 03:48:44 -05:00
|
|
|
size: { width: this.getInnerPanelWidth(), height: this.getInnerPanelHeight() },
|
2020-02-09 03:53:34 -06:00
|
|
|
};
|
|
|
|
|
2020-02-28 04:04:40 -06:00
|
|
|
setPanelAngularComponent({
|
2021-10-13 01:53:36 -05:00
|
|
|
key: panel.key,
|
2020-02-28 04:04:40 -06:00
|
|
|
angularComponent: loader.load(this.element, this.scopeProps, template),
|
|
|
|
});
|
2020-02-09 03:53:34 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
hasOverlayHeader() {
|
|
|
|
const { panel } = this.props;
|
2021-07-26 12:19:07 -05:00
|
|
|
const { data } = this.state;
|
2020-02-09 03:53:34 -06:00
|
|
|
|
|
|
|
// always show normal header if we have time override
|
|
|
|
if (data.request && data.request.timeInfo) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return !panel.hasTitle();
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
2023-09-12 03:54:41 -05:00
|
|
|
const { dashboard, panel } = this.props;
|
2021-05-03 01:52:05 -05:00
|
|
|
const { errorMessage, data } = this.state;
|
2020-02-09 03:53:34 -06:00
|
|
|
const { transparent } = panel;
|
2021-05-03 01:52:05 -05:00
|
|
|
|
2023-04-25 11:16:46 -05:00
|
|
|
const panelChromeProps = getPanelChromeProps({ ...this.props, data });
|
|
|
|
|
2023-09-12 03:54:41 -05:00
|
|
|
// Shift the hover menu down if it's on the top row so it doesn't get clipped by topnav
|
|
|
|
const hoverHeaderOffset = (panel.gridPos?.y ?? 0) === 0 ? -16 : undefined;
|
2020-02-09 03:53:34 -06:00
|
|
|
|
2023-09-12 03:54:41 -05:00
|
|
|
const menu = (
|
|
|
|
<div data-testid="panel-dropdown">
|
|
|
|
<PanelHeaderMenuWrapper panel={panel} dashboard={dashboard} loadingState={data.state} />
|
|
|
|
</div>
|
|
|
|
);
|
2020-02-09 03:53:34 -06:00
|
|
|
|
2023-09-12 03:54:41 -05:00
|
|
|
return (
|
|
|
|
<PanelChrome
|
|
|
|
width={this.props.width}
|
|
|
|
height={this.props.height}
|
|
|
|
title={panelChromeProps.title}
|
|
|
|
loadingState={data.state}
|
|
|
|
statusMessage={errorMessage}
|
|
|
|
statusMessageOnClick={panelChromeProps.onOpenErrorInspect}
|
|
|
|
description={panelChromeProps.description}
|
|
|
|
titleItems={panelChromeProps.titleItems}
|
|
|
|
menu={this.props.hideMenu ? undefined : menu}
|
|
|
|
dragClass={panelChromeProps.dragClass}
|
|
|
|
dragClassCancel="grid-drag-cancel"
|
|
|
|
padding={panelChromeProps.padding}
|
|
|
|
hoverHeaderOffset={hoverHeaderOffset}
|
|
|
|
hoverHeader={panelChromeProps.hasOverlayHeader()}
|
|
|
|
displayMode={transparent ? 'transparent' : 'default'}
|
|
|
|
onCancelQuery={panelChromeProps.onCancelQuery}
|
|
|
|
onOpenMenu={panelChromeProps.onOpenMenu}
|
|
|
|
>
|
|
|
|
{() => <div ref={(element) => (this.element = element)} className="panel-height-helper" />}
|
|
|
|
</PanelChrome>
|
|
|
|
);
|
2020-02-09 03:53:34 -06:00
|
|
|
}
|
|
|
|
}
|
2020-02-28 04:04:40 -06:00
|
|
|
|
|
|
|
const mapStateToProps: MapStateToProps<ConnectedProps, OwnProps, StoreState> = (state, props) => {
|
|
|
|
return {
|
2021-10-13 01:53:36 -05:00
|
|
|
angularComponent: getPanelStateForModel(state, props.panel)?.angularComponent,
|
2020-02-28 04:04:40 -06:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2021-02-08 23:05:34 -06:00
|
|
|
const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = { setPanelAngularComponent };
|
2020-02-28 04:04:40 -06:00
|
|
|
|
|
|
|
export const PanelChromeAngular = connect(mapStateToProps, mapDispatchToProps)(PanelChromeAngularUnconnected);
|