2021-10-13 01:53:36 -05:00
|
|
|
import React, { PureComponent } from 'react';
|
2019-05-06 08:26:09 -05:00
|
|
|
import classNames from 'classnames';
|
2020-12-23 03:45:31 -06:00
|
|
|
import { Subscription } from 'rxjs';
|
2021-10-20 15:01:21 -05:00
|
|
|
import { locationService, RefreshEvent } from '@grafana/runtime';
|
2019-10-31 04:48:05 -05:00
|
|
|
import {
|
|
|
|
AbsoluteTimeRange,
|
2021-07-26 13:57:52 -05:00
|
|
|
AnnotationChangeEvent,
|
2021-07-08 03:39:03 -05:00
|
|
|
AnnotationEventUIModel,
|
2021-10-05 01:07:57 -05:00
|
|
|
CoreApp,
|
2021-05-10 07:24:23 -05:00
|
|
|
DashboardCursorSync,
|
2021-04-30 15:33:29 -05:00
|
|
|
EventFilterOptions,
|
2021-01-07 04:26:56 -06:00
|
|
|
FieldConfigSource,
|
|
|
|
getDefaultTimeRange,
|
|
|
|
LoadingState,
|
2019-10-31 04:48:05 -05:00
|
|
|
PanelData,
|
|
|
|
PanelPlugin,
|
2020-08-20 01:52:26 -05:00
|
|
|
PanelPluginMeta,
|
2021-08-20 16:48:55 -05:00
|
|
|
TimeRange,
|
2021-01-07 04:26:56 -06:00
|
|
|
toDataFrameDTO,
|
|
|
|
toUtc,
|
2019-10-31 04:48:05 -05:00
|
|
|
} from '@grafana/data';
|
2021-08-19 01:47:37 -05:00
|
|
|
import { ErrorBoundary, PanelContext, PanelContextProvider, SeriesVisibilityChangeMode } from '@grafana/ui';
|
2021-10-25 04:21:51 -05:00
|
|
|
import { VizLegendOptions } from '@grafana/schema';
|
2020-05-11 04:46:57 -05:00
|
|
|
import { selectors } from '@grafana/e2e-selectors';
|
2021-08-19 01:47:37 -05:00
|
|
|
|
|
|
|
import { PanelHeader } from './PanelHeader/PanelHeader';
|
|
|
|
import { getTimeSrv, TimeSrv } from '../services/TimeSrv';
|
|
|
|
import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel';
|
|
|
|
import { profiler } from 'app/core/profiler';
|
|
|
|
import config from 'app/core/config';
|
|
|
|
import { DashboardModel, PanelModel } from '../state';
|
|
|
|
import { PANEL_BORDER } from 'app/core/constants';
|
2020-11-17 03:25:36 -06:00
|
|
|
import { loadSnapshotData } from '../utils/loadSnapshotData';
|
2021-10-20 15:01:21 -05:00
|
|
|
import { RenderEvent } from 'app/types/events';
|
2021-05-03 10:23:17 -05:00
|
|
|
import { changeSeriesColorConfigFactory } from 'app/plugins/panel/timeseries/overrides/colorSeriesConfigFactory';
|
2021-05-06 14:22:03 -05:00
|
|
|
import { seriesVisibilityConfigFactory } from './SeriesVisibilityConfigFactory';
|
2021-07-08 03:39:03 -05:00
|
|
|
import { deleteAnnotation, saveAnnotation, updateAnnotation } from '../../annotations/api';
|
|
|
|
import { getDashboardQueryRunner } from '../../query/state/DashboardQueryRunner/DashboardQueryRunner';
|
2021-08-20 16:48:55 -05:00
|
|
|
import { liveTimer } from './liveTimer';
|
2021-08-19 01:47:37 -05:00
|
|
|
import { isSoloRoute } from '../../../routes/utils';
|
2018-06-19 07:51:57 -05:00
|
|
|
|
2019-02-12 09:06:02 -06:00
|
|
|
const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
|
2019-01-14 07:47:41 -06:00
|
|
|
|
2018-11-06 09:37:51 -06:00
|
|
|
export interface Props {
|
2018-06-19 07:51:57 -05:00
|
|
|
panel: PanelModel;
|
|
|
|
dashboard: DashboardModel;
|
2019-05-01 00:36:46 -05:00
|
|
|
plugin: PanelPlugin;
|
2020-04-10 09:37:26 -05:00
|
|
|
isViewing: boolean;
|
2020-07-09 08:16:35 -05:00
|
|
|
isEditing: boolean;
|
2019-05-03 08:35:37 -05:00
|
|
|
isInView: boolean;
|
2019-04-17 12:51:50 -05:00
|
|
|
width: number;
|
|
|
|
height: number;
|
2021-10-25 06:55:06 -05:00
|
|
|
onInstanceStateChange: (value: any) => void;
|
2018-06-19 07:51:57 -05:00
|
|
|
}
|
|
|
|
|
2018-11-06 09:37:51 -06:00
|
|
|
export interface State {
|
2019-04-17 12:51:50 -05:00
|
|
|
isFirstLoad: boolean;
|
2018-11-05 10:46:09 -06:00
|
|
|
renderCounter: number;
|
2020-02-17 00:25:27 -06:00
|
|
|
errorMessage?: string;
|
2019-05-03 08:35:37 -05:00
|
|
|
refreshWhenInView: boolean;
|
2021-04-30 15:33:29 -05:00
|
|
|
context: PanelContext;
|
2019-04-17 12:51:50 -05:00
|
|
|
data: PanelData;
|
2021-08-20 16:48:55 -05:00
|
|
|
liveTime?: TimeRange;
|
2018-10-14 08:39:34 -05:00
|
|
|
}
|
2018-07-05 15:10:39 -05:00
|
|
|
|
2021-10-13 01:53:36 -05:00
|
|
|
export class PanelChrome extends PureComponent<Props, State> {
|
2020-12-23 03:45:31 -06:00
|
|
|
private readonly timeSrv: TimeSrv = getTimeSrv();
|
|
|
|
private subs = new Subscription();
|
2021-04-30 15:33:29 -05:00
|
|
|
private eventFilter: EventFilterOptions = { onlyLocal: true };
|
2018-11-08 07:44:12 -06:00
|
|
|
|
2019-04-17 12:51:50 -05:00
|
|
|
constructor(props: Props) {
|
2018-06-19 07:51:57 -05:00
|
|
|
super(props);
|
2020-02-17 00:25:27 -06:00
|
|
|
|
2021-05-14 12:33:47 -05:00
|
|
|
// Can this eventBus be on PanelModel? when we have more complex event filtering, that may be a better option
|
|
|
|
const eventBus = props.dashboard.events.newScopedBus(`panel:${props.panel.id}`, this.eventFilter);
|
2021-04-30 15:33:29 -05:00
|
|
|
|
2018-10-14 08:39:34 -05:00
|
|
|
this.state = {
|
2019-04-17 12:51:50 -05:00
|
|
|
isFirstLoad: true,
|
2018-11-05 10:46:09 -06:00
|
|
|
renderCounter: 0,
|
2019-05-03 08:35:37 -05:00
|
|
|
refreshWhenInView: false,
|
2021-04-30 15:33:29 -05:00
|
|
|
context: {
|
|
|
|
eventBus,
|
2021-10-05 01:07:57 -05:00
|
|
|
sync: props.isEditing ? DashboardCursorSync.Off : props.dashboard.graphTooltip,
|
|
|
|
app: this.getPanelContextApp(),
|
2021-05-03 10:23:17 -05:00
|
|
|
onSeriesColorChange: this.onSeriesColorChange,
|
2021-05-06 14:22:03 -05:00
|
|
|
onToggleSeriesVisibility: this.onSeriesVisibilityChange,
|
2021-07-08 03:39:03 -05:00
|
|
|
onAnnotationCreate: this.onAnnotationCreate,
|
|
|
|
onAnnotationUpdate: this.onAnnotationUpdate,
|
|
|
|
onAnnotationDelete: this.onAnnotationDelete,
|
|
|
|
canAddAnnotations: () => Boolean(props.dashboard.meta.canEdit || props.dashboard.meta.canMakeEditable),
|
2021-10-01 04:08:11 -05:00
|
|
|
onInstanceStateChange: this.onInstanceStateChange,
|
2021-10-25 04:21:51 -05:00
|
|
|
onToggleLegendSort: this.onToggleLegendSort,
|
2021-04-30 15:33:29 -05:00
|
|
|
},
|
2021-04-28 07:46:10 -05:00
|
|
|
data: this.getInitialPanelDataState(),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-10-01 04:08:11 -05:00
|
|
|
onInstanceStateChange = (value: any) => {
|
2021-10-25 06:55:06 -05:00
|
|
|
this.props.onInstanceStateChange(value);
|
|
|
|
|
2021-10-01 04:08:11 -05:00
|
|
|
this.setState({
|
|
|
|
context: {
|
|
|
|
...this.state.context,
|
|
|
|
instanceState: value,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2021-10-05 01:07:57 -05:00
|
|
|
getPanelContextApp() {
|
|
|
|
if (this.props.isEditing) {
|
|
|
|
return CoreApp.PanelEditor;
|
|
|
|
}
|
|
|
|
if (this.props.isViewing) {
|
|
|
|
return CoreApp.PanelViewer;
|
|
|
|
}
|
|
|
|
|
|
|
|
return CoreApp.Dashboard;
|
|
|
|
}
|
|
|
|
|
2021-05-03 10:23:17 -05:00
|
|
|
onSeriesColorChange = (label: string, color: string) => {
|
|
|
|
this.onFieldConfigChange(changeSeriesColorConfigFactory(label, color, this.props.panel.fieldConfig));
|
|
|
|
};
|
|
|
|
|
2021-05-06 14:22:03 -05:00
|
|
|
onSeriesVisibilityChange = (label: string, mode: SeriesVisibilityChangeMode) => {
|
|
|
|
this.onFieldConfigChange(
|
|
|
|
seriesVisibilityConfigFactory(label, mode, this.props.panel.fieldConfig, this.state.data.series)
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2021-10-25 04:21:51 -05:00
|
|
|
onToggleLegendSort = (sortKey: string) => {
|
|
|
|
const legendOptions: VizLegendOptions = this.props.panel.options.legend;
|
|
|
|
|
|
|
|
// We don't want to do anything when legend options are not available
|
|
|
|
if (!legendOptions) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let sortDesc = legendOptions.sortDesc;
|
|
|
|
let sortBy = legendOptions.sortBy;
|
|
|
|
if (sortKey !== sortBy) {
|
|
|
|
sortDesc = undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if already sort ascending, disable sorting
|
|
|
|
if (sortDesc === false) {
|
|
|
|
sortBy = undefined;
|
|
|
|
sortDesc = undefined;
|
|
|
|
} else {
|
|
|
|
sortDesc = !sortDesc;
|
|
|
|
sortBy = sortKey;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.onOptionsChange({
|
|
|
|
...this.props.panel.options,
|
|
|
|
legend: { ...legendOptions, sortBy, sortDesc },
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2021-04-28 07:46:10 -05:00
|
|
|
getInitialPanelDataState(): PanelData {
|
|
|
|
return {
|
|
|
|
state: LoadingState.NotStarted,
|
|
|
|
series: [],
|
|
|
|
timeRange: getDefaultTimeRange(),
|
2018-10-14 08:39:34 -05:00
|
|
|
};
|
2018-07-05 15:10:39 -05:00
|
|
|
}
|
|
|
|
|
2018-11-06 09:37:51 -06:00
|
|
|
componentDidMount() {
|
2020-05-25 07:05:43 -05:00
|
|
|
const { panel, dashboard } = this.props;
|
2020-04-10 09:37:26 -05:00
|
|
|
|
2020-11-03 06:08:54 -06:00
|
|
|
// Subscribe to panel events
|
2020-12-23 03:45:31 -06:00
|
|
|
this.subs.add(panel.events.subscribe(RefreshEvent, this.onRefresh));
|
|
|
|
this.subs.add(panel.events.subscribe(RenderEvent, this.onRender));
|
2020-05-01 07:14:52 -05:00
|
|
|
|
2019-04-17 12:51:50 -05:00
|
|
|
dashboard.panelInitialized(this.props.panel);
|
|
|
|
|
|
|
|
// Move snapshot data into the query response
|
|
|
|
if (this.hasPanelSnapshot) {
|
|
|
|
this.setState({
|
2020-11-17 03:25:36 -06:00
|
|
|
data: loadSnapshotData(panel, dashboard),
|
2019-04-17 12:51:50 -05:00
|
|
|
isFirstLoad: false,
|
|
|
|
});
|
2020-05-25 07:05:43 -05:00
|
|
|
return;
|
2019-04-17 12:51:50 -05:00
|
|
|
}
|
2020-05-01 07:14:52 -05:00
|
|
|
|
2020-05-25 07:05:43 -05:00
|
|
|
if (!this.wantsQueryExecution) {
|
|
|
|
this.setState({ isFirstLoad: false });
|
2020-05-01 07:14:52 -05:00
|
|
|
}
|
2020-05-25 07:05:43 -05:00
|
|
|
|
2020-12-23 03:45:31 -06:00
|
|
|
this.subs.add(
|
|
|
|
panel
|
|
|
|
.getQueryRunner()
|
|
|
|
.getData({ withTransforms: true, withFieldConfig: true })
|
|
|
|
.subscribe({
|
2021-01-20 00:59:48 -06:00
|
|
|
next: (data) => this.onDataUpdate(data),
|
2020-12-23 03:45:31 -06:00
|
|
|
})
|
|
|
|
);
|
2021-08-20 16:48:55 -05:00
|
|
|
|
|
|
|
// Listen for live timer events
|
|
|
|
liveTimer.listen(this);
|
2018-10-14 08:39:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
2020-12-23 03:45:31 -06:00
|
|
|
this.subs.unsubscribe();
|
2021-08-20 16:48:55 -05:00
|
|
|
liveTimer.remove(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
liveTimeChanged(liveTime: TimeRange) {
|
|
|
|
const { data } = this.state;
|
|
|
|
if (data.timeRange) {
|
|
|
|
const delta = liveTime.to.valueOf() - data.timeRange.to.valueOf();
|
|
|
|
if (delta < 100) {
|
|
|
|
// 10hz
|
|
|
|
console.log('Skip tick render', this.props.panel.title, delta);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.setState({ liveTime });
|
2018-10-14 08:39:34 -05:00
|
|
|
}
|
|
|
|
|
2019-05-03 08:35:37 -05:00
|
|
|
componentDidUpdate(prevProps: Props) {
|
2021-08-20 16:48:55 -05:00
|
|
|
const { isInView, isEditing, width } = this.props;
|
2021-10-05 01:07:57 -05:00
|
|
|
const { context } = this.state;
|
2021-05-10 07:24:23 -05:00
|
|
|
|
2021-10-05 01:07:57 -05:00
|
|
|
const app = this.getPanelContextApp();
|
|
|
|
const sync = isEditing ? DashboardCursorSync.Off : this.props.dashboard.graphTooltip;
|
2021-05-10 07:24:23 -05:00
|
|
|
|
2021-10-05 01:07:57 -05:00
|
|
|
if (context.sync !== sync || context.app !== app) {
|
|
|
|
this.setState({
|
|
|
|
context: {
|
|
|
|
...context,
|
|
|
|
sync,
|
|
|
|
app,
|
|
|
|
},
|
2021-05-10 07:24:23 -05:00
|
|
|
});
|
|
|
|
}
|
2019-05-03 08:35:37 -05:00
|
|
|
|
|
|
|
// View state has changed
|
|
|
|
if (isInView !== prevProps.isInView) {
|
|
|
|
if (isInView) {
|
|
|
|
// Check if we need a delayed refresh
|
|
|
|
if (this.state.refreshWhenInView) {
|
|
|
|
this.onRefresh();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-08-20 16:48:55 -05:00
|
|
|
|
|
|
|
// The timer depends on panel width
|
|
|
|
if (width !== prevProps.width) {
|
|
|
|
liveTimer.updateInterval(this);
|
|
|
|
}
|
2019-05-03 08:35:37 -05:00
|
|
|
}
|
|
|
|
|
2019-04-17 12:51:50 -05:00
|
|
|
// Updates the response with information from the stream
|
2019-04-18 01:22:14 -05:00
|
|
|
// The next is outside a react synthetic event so setState is not batched
|
|
|
|
// So in this context we can only do a single call to setState
|
2019-12-19 00:01:35 -06:00
|
|
|
onDataUpdate(data: PanelData) {
|
2021-05-14 12:33:47 -05:00
|
|
|
const { dashboard, panel, plugin } = this.props;
|
2021-02-24 04:12:11 -06:00
|
|
|
|
2021-04-28 07:46:10 -05:00
|
|
|
// Ignore this data update if we are now a non data panel
|
|
|
|
if (plugin.meta.skipDataQuery) {
|
|
|
|
this.setState({ data: this.getInitialPanelDataState() });
|
2019-12-19 00:01:35 -06:00
|
|
|
return;
|
|
|
|
}
|
2019-05-03 08:35:37 -05:00
|
|
|
|
2019-12-19 00:01:35 -06:00
|
|
|
let { isFirstLoad } = this.state;
|
2020-02-17 00:25:27 -06:00
|
|
|
let errorMessage: string | undefined;
|
2019-10-01 03:22:41 -05:00
|
|
|
|
2019-12-19 00:01:35 -06:00
|
|
|
switch (data.state) {
|
|
|
|
case LoadingState.Loading:
|
|
|
|
// Skip updating state data if it is already in loading state
|
|
|
|
// This is to avoid rendering partial loading responses
|
|
|
|
if (this.state.data.state === LoadingState.Loading) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case LoadingState.Error:
|
|
|
|
const { error } = data;
|
|
|
|
if (error) {
|
|
|
|
if (errorMessage !== error.message) {
|
|
|
|
errorMessage = error.message;
|
2019-10-01 03:22:41 -05:00
|
|
|
}
|
2019-12-19 00:01:35 -06:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case LoadingState.Done:
|
|
|
|
// If we are doing a snapshot save data in panel model
|
2021-04-28 07:46:10 -05:00
|
|
|
if (dashboard.snapshot) {
|
|
|
|
panel.snapshotData = data.series.map((frame) => toDataFrameDTO(frame));
|
2019-12-19 00:01:35 -06:00
|
|
|
}
|
|
|
|
if (isFirstLoad) {
|
|
|
|
isFirstLoad = false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2019-04-18 01:22:14 -05:00
|
|
|
|
2021-08-20 16:48:55 -05:00
|
|
|
this.setState({ isFirstLoad, errorMessage, data, liveTime: undefined });
|
2019-12-19 00:01:35 -06:00
|
|
|
}
|
2019-04-17 12:51:50 -05:00
|
|
|
|
2018-10-14 08:39:34 -05:00
|
|
|
onRefresh = () => {
|
2019-05-03 08:35:37 -05:00
|
|
|
const { panel, isInView, width } = this.props;
|
2021-05-14 12:33:47 -05:00
|
|
|
|
2019-05-03 08:35:37 -05:00
|
|
|
if (!isInView) {
|
|
|
|
this.setState({ refreshWhenInView: true });
|
2018-11-08 07:44:12 -06:00
|
|
|
return;
|
|
|
|
}
|
2018-11-12 04:23:41 -06:00
|
|
|
|
2018-11-13 08:05:07 -06:00
|
|
|
const timeData = applyPanelTimeOverrides(panel, this.timeSrv.timeRange());
|
2018-07-09 15:24:15 -05:00
|
|
|
|
2019-04-17 12:51:50 -05:00
|
|
|
// Issue Query
|
2019-04-18 01:22:14 -05:00
|
|
|
if (this.wantsQueryExecution) {
|
2019-04-17 12:51:50 -05:00
|
|
|
if (width < 0) {
|
|
|
|
return;
|
|
|
|
}
|
2019-04-18 16:10:18 -05:00
|
|
|
|
2021-05-14 12:33:47 -05:00
|
|
|
if (this.state.refreshWhenInView) {
|
|
|
|
this.setState({ refreshWhenInView: false });
|
|
|
|
}
|
2021-06-01 06:52:08 -05:00
|
|
|
panel.runAllPanelQueries(this.props.dashboard.id, this.props.dashboard.getTimezone(), timeData, width);
|
2020-05-21 02:55:58 -05:00
|
|
|
} else {
|
|
|
|
// The panel should render on refresh as well if it doesn't have a query, like clock panel
|
2021-07-11 00:35:27 -05:00
|
|
|
this.setState({
|
|
|
|
data: { ...this.state.data, timeRange: this.timeSrv.timeRange() },
|
|
|
|
renderCounter: this.state.renderCounter + 1,
|
2021-08-20 16:48:55 -05:00
|
|
|
liveTime: undefined,
|
2021-07-11 00:35:27 -05:00
|
|
|
});
|
2019-04-17 12:51:50 -05:00
|
|
|
}
|
2018-10-14 08:39:34 -05:00
|
|
|
};
|
2018-07-09 15:24:15 -05:00
|
|
|
|
2018-11-05 10:46:09 -06:00
|
|
|
onRender = () => {
|
2019-04-25 13:01:02 -05:00
|
|
|
const stateUpdate = { renderCounter: this.state.renderCounter + 1 };
|
|
|
|
this.setState(stateUpdate);
|
2018-11-03 17:36:40 -05:00
|
|
|
};
|
|
|
|
|
2019-04-24 03:14:18 -05:00
|
|
|
onOptionsChange = (options: any) => {
|
|
|
|
this.props.panel.updateOptions(options);
|
|
|
|
};
|
|
|
|
|
2020-03-19 05:50:31 -05:00
|
|
|
onFieldConfigChange = (config: FieldConfigSource) => {
|
|
|
|
this.props.panel.updateFieldConfig(config);
|
|
|
|
};
|
|
|
|
|
2021-10-22 04:52:05 -05:00
|
|
|
onPanelError = (error: Error) => {
|
|
|
|
const errorMessage = error.message || DEFAULT_PLUGIN_ERROR;
|
|
|
|
if (this.state.errorMessage !== errorMessage) {
|
|
|
|
this.setState({ errorMessage });
|
2019-02-14 08:18:55 -06:00
|
|
|
}
|
2019-01-30 03:39:42 -06:00
|
|
|
};
|
|
|
|
|
2021-10-22 04:52:05 -05:00
|
|
|
onPanelErrorRecover = () => {
|
|
|
|
this.setState({ errorMessage: undefined });
|
|
|
|
};
|
|
|
|
|
2021-07-08 03:39:03 -05:00
|
|
|
onAnnotationCreate = async (event: AnnotationEventUIModel) => {
|
|
|
|
const isRegion = event.from !== event.to;
|
2021-07-26 13:57:52 -05:00
|
|
|
const anno = {
|
2021-07-08 03:39:03 -05:00
|
|
|
dashboardId: this.props.dashboard.id,
|
|
|
|
panelId: this.props.panel.id,
|
|
|
|
isRegion,
|
|
|
|
time: event.from,
|
|
|
|
timeEnd: isRegion ? event.to : 0,
|
|
|
|
tags: event.tags,
|
|
|
|
text: event.description,
|
2021-07-26 13:57:52 -05:00
|
|
|
};
|
|
|
|
await saveAnnotation(anno);
|
2021-07-08 03:39:03 -05:00
|
|
|
getDashboardQueryRunner().run({ dashboard: this.props.dashboard, range: this.timeSrv.timeRange() });
|
2021-07-26 13:57:52 -05:00
|
|
|
this.state.context.eventBus.publish(new AnnotationChangeEvent(anno));
|
2021-07-08 03:39:03 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
onAnnotationDelete = async (id: string) => {
|
|
|
|
await deleteAnnotation({ id });
|
|
|
|
getDashboardQueryRunner().run({ dashboard: this.props.dashboard, range: this.timeSrv.timeRange() });
|
2021-07-26 13:57:52 -05:00
|
|
|
this.state.context.eventBus.publish(new AnnotationChangeEvent({ id }));
|
2021-07-08 03:39:03 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
onAnnotationUpdate = async (event: AnnotationEventUIModel) => {
|
|
|
|
const isRegion = event.from !== event.to;
|
2021-07-26 13:57:52 -05:00
|
|
|
const anno = {
|
2021-07-08 03:39:03 -05:00
|
|
|
id: event.id,
|
|
|
|
dashboardId: this.props.dashboard.id,
|
|
|
|
panelId: this.props.panel.id,
|
|
|
|
isRegion,
|
|
|
|
time: event.from,
|
|
|
|
timeEnd: isRegion ? event.to : 0,
|
|
|
|
tags: event.tags,
|
|
|
|
text: event.description,
|
2021-07-26 13:57:52 -05:00
|
|
|
};
|
|
|
|
await updateAnnotation(anno);
|
2021-07-08 03:39:03 -05:00
|
|
|
|
|
|
|
getDashboardQueryRunner().run({ dashboard: this.props.dashboard, range: this.timeSrv.timeRange() });
|
2021-07-26 13:57:52 -05:00
|
|
|
this.state.context.eventBus.publish(new AnnotationChangeEvent(anno));
|
2021-07-08 03:39:03 -05:00
|
|
|
};
|
|
|
|
|
2019-02-12 05:25:10 -06:00
|
|
|
get hasPanelSnapshot() {
|
|
|
|
const { panel } = this.props;
|
|
|
|
return panel.snapshotData && panel.snapshotData.length;
|
|
|
|
}
|
|
|
|
|
2019-04-17 12:51:50 -05:00
|
|
|
get wantsQueryExecution() {
|
2020-02-08 06:23:16 -06:00
|
|
|
return !(this.props.plugin.meta.skipDataQuery || this.hasPanelSnapshot);
|
2019-02-12 05:25:10 -06:00
|
|
|
}
|
|
|
|
|
2019-09-16 02:31:22 -05:00
|
|
|
onChangeTimeRange = (timeRange: AbsoluteTimeRange) => {
|
|
|
|
this.timeSrv.setTime({
|
|
|
|
from: toUtc(timeRange.from),
|
|
|
|
to: toUtc(timeRange.to),
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2020-08-20 01:52:26 -05:00
|
|
|
shouldSignalRenderingCompleted(loadingState: LoadingState, pluginMeta: PanelPluginMeta) {
|
|
|
|
return loadingState === LoadingState.Done || pluginMeta.skipDataQuery;
|
|
|
|
}
|
|
|
|
|
2021-05-27 05:19:06 -05:00
|
|
|
skipFirstRender(loadingState: LoadingState) {
|
|
|
|
const { isFirstLoad } = this.state;
|
|
|
|
return (
|
|
|
|
this.wantsQueryExecution &&
|
|
|
|
isFirstLoad &&
|
|
|
|
(loadingState === LoadingState.Loading || loadingState === LoadingState.NotStarted)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-02-17 00:25:27 -06:00
|
|
|
renderPanel(width: number, height: number) {
|
2020-11-03 06:08:54 -06:00
|
|
|
const { panel, plugin, dashboard } = this.props;
|
2021-05-27 05:19:06 -05:00
|
|
|
const { renderCounter, data } = this.state;
|
2019-05-06 08:26:09 -05:00
|
|
|
const { theme } = config;
|
2020-08-20 01:52:26 -05:00
|
|
|
const { state: loadingState } = data;
|
|
|
|
|
|
|
|
// do not render component until we have first data
|
2021-05-27 05:19:06 -05:00
|
|
|
if (this.skipFirstRender(loadingState)) {
|
2020-08-20 01:52:26 -05:00
|
|
|
return null;
|
|
|
|
}
|
2019-01-30 03:39:42 -06:00
|
|
|
|
2019-02-01 01:12:58 -06:00
|
|
|
// This is only done to increase a counter that is used by backend
|
2020-04-15 15:17:41 -05:00
|
|
|
// image rendering to know when to capture image
|
2020-08-20 01:52:26 -05:00
|
|
|
if (this.shouldSignalRenderingCompleted(loadingState, plugin.meta)) {
|
2019-05-13 02:38:19 -05:00
|
|
|
profiler.renderingCompleted();
|
2019-02-01 01:12:58 -06:00
|
|
|
}
|
|
|
|
|
2020-07-09 08:16:35 -05:00
|
|
|
const PanelComponent = plugin.panel!;
|
2021-08-20 16:48:55 -05:00
|
|
|
const timeRange = this.state.liveTime ?? data.timeRange ?? this.timeSrv.timeRange();
|
2019-11-25 15:26:18 -06:00
|
|
|
const headerHeight = this.hasOverlayHeader() ? 0 : theme.panelHeaderHeight;
|
|
|
|
const chromePadding = plugin.noPadding ? 0 : theme.panelPadding;
|
|
|
|
const panelWidth = width - chromePadding * 2 - PANEL_BORDER;
|
|
|
|
const innerPanelHeight = height - headerHeight - chromePadding * 2 - PANEL_BORDER;
|
|
|
|
const panelContentClassNames = classNames({
|
|
|
|
'panel-content': true,
|
|
|
|
'panel-content--no-padding': plugin.noPadding,
|
|
|
|
});
|
2020-03-16 08:26:03 -05:00
|
|
|
const panelOptions = panel.getOptions();
|
2019-11-25 15:26:18 -06:00
|
|
|
|
2021-04-30 15:33:29 -05:00
|
|
|
// Update the event filter (dashboard settings may have changed)
|
|
|
|
// Yes this is called ever render for a function that is triggered on every mouse move
|
|
|
|
this.eventFilter.onlyLocal = dashboard.graphTooltip === 0;
|
|
|
|
|
2019-02-07 14:34:50 -06:00
|
|
|
return (
|
|
|
|
<>
|
2019-11-25 15:26:18 -06:00
|
|
|
<div className={panelContentClassNames}>
|
2021-04-30 15:33:29 -05:00
|
|
|
<PanelContextProvider value={this.state.context}>
|
2021-04-26 09:13:15 -05:00
|
|
|
<PanelComponent
|
|
|
|
id={panel.id}
|
|
|
|
data={data}
|
|
|
|
title={panel.title}
|
|
|
|
timeRange={timeRange}
|
|
|
|
timeZone={this.props.dashboard.getTimezone()}
|
|
|
|
options={panelOptions}
|
|
|
|
fieldConfig={panel.fieldConfig}
|
|
|
|
transparent={panel.transparent}
|
|
|
|
width={panelWidth}
|
|
|
|
height={innerPanelHeight}
|
|
|
|
renderCounter={renderCounter}
|
|
|
|
replaceVariables={panel.replaceVariables}
|
|
|
|
onOptionsChange={this.onOptionsChange}
|
|
|
|
onFieldConfigChange={this.onFieldConfigChange}
|
|
|
|
onChangeTimeRange={this.onChangeTimeRange}
|
|
|
|
eventBus={dashboard.events}
|
|
|
|
/>
|
|
|
|
</PanelContextProvider>
|
2019-04-17 12:51:50 -05:00
|
|
|
</div>
|
2019-02-07 14:34:50 -06:00
|
|
|
</>
|
|
|
|
);
|
2019-04-17 12:51:50 -05:00
|
|
|
}
|
|
|
|
|
2019-11-25 15:26:18 -06:00
|
|
|
hasOverlayHeader() {
|
|
|
|
const { panel } = this.props;
|
2021-07-26 12:19:07 -05:00
|
|
|
const { data } = this.state;
|
2019-11-25 15:26:18 -06:00
|
|
|
|
|
|
|
// always show normal header if we have time override
|
|
|
|
if (data.request && data.request.timeInfo) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return !panel.hasTitle();
|
|
|
|
}
|
|
|
|
|
2018-10-14 08:39:34 -05:00
|
|
|
render() {
|
2021-10-22 04:52:05 -05:00
|
|
|
const { dashboard, panel, isViewing, isEditing, width, height, plugin } = this.props;
|
2019-04-18 02:00:46 -05:00
|
|
|
const { errorMessage, data } = this.state;
|
2019-02-07 14:34:50 -06:00
|
|
|
const { transparent } = panel;
|
2018-11-06 08:03:56 -06:00
|
|
|
|
2021-10-11 08:55:45 -05:00
|
|
|
const alertState = data.alertState?.state;
|
2021-05-03 01:52:05 -05:00
|
|
|
|
2019-05-06 08:26:09 -05:00
|
|
|
const containerClassNames = classNames({
|
|
|
|
'panel-container': true,
|
2021-08-19 01:47:37 -05:00
|
|
|
'panel-container--absolute': isSoloRoute(locationService.getLocation().pathname),
|
2019-11-25 15:26:18 -06:00
|
|
|
'panel-container--transparent': transparent,
|
|
|
|
'panel-container--no-title': this.hasOverlayHeader(),
|
2021-05-03 01:52:05 -05:00
|
|
|
[`panel-alert-state--${alertState}`]: alertState !== undefined,
|
2019-05-06 08:26:09 -05:00
|
|
|
});
|
|
|
|
|
2018-06-19 07:51:57 -05:00
|
|
|
return (
|
2021-07-16 14:48:47 -05:00
|
|
|
<section
|
|
|
|
className={containerClassNames}
|
|
|
|
aria-label={selectors.components.Panels.Panel.containerByTitle(panel.title)}
|
|
|
|
>
|
2019-04-17 12:51:50 -05:00
|
|
|
<PanelHeader
|
|
|
|
panel={panel}
|
|
|
|
dashboard={dashboard}
|
|
|
|
title={panel.title}
|
|
|
|
description={panel.description}
|
|
|
|
links={panel.links}
|
|
|
|
error={errorMessage}
|
2020-04-10 09:37:26 -05:00
|
|
|
isEditing={isEditing}
|
|
|
|
isViewing={isViewing}
|
2021-05-03 01:52:05 -05:00
|
|
|
alertState={alertState}
|
2020-03-18 07:00:14 -05:00
|
|
|
data={data}
|
2019-04-17 12:51:50 -05:00
|
|
|
/>
|
2021-10-22 04:52:05 -05:00
|
|
|
<ErrorBoundary
|
|
|
|
dependencies={[data, plugin, panel.getOptions()]}
|
|
|
|
onError={this.onPanelError}
|
|
|
|
onRecover={this.onPanelErrorRecover}
|
|
|
|
>
|
2020-02-17 00:25:27 -06:00
|
|
|
{({ error }) => {
|
|
|
|
if (error) {
|
2019-04-17 12:51:50 -05:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return this.renderPanel(width, height);
|
|
|
|
}}
|
|
|
|
</ErrorBoundary>
|
2021-07-16 14:48:47 -05:00
|
|
|
</section>
|
2018-06-19 07:51:57 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|