Panel Header Fix: Implement new Panel Header on Angular Panels (#66826)
Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
This commit is contained in:
co-authored by
Dominik Prokop
parent
e6e741546f
commit
2a67b8ad32
@@ -6,6 +6,7 @@ import { Subscription } from 'rxjs';
|
|||||||
import { getDefaultTimeRange, LoadingState, PanelData, PanelPlugin } from '@grafana/data';
|
import { getDefaultTimeRange, LoadingState, PanelData, PanelPlugin } from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { AngularComponent, getAngularLoader, locationService } from '@grafana/runtime';
|
import { AngularComponent, getAngularLoader, locationService } from '@grafana/runtime';
|
||||||
|
import { PanelChrome } from '@grafana/ui';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import { PANEL_BORDER } from 'app/core/constants';
|
import { PANEL_BORDER } from 'app/core/constants';
|
||||||
import { setPanelAngularComponent } from 'app/features/panel/state/reducers';
|
import { setPanelAngularComponent } from 'app/features/panel/state/reducers';
|
||||||
@@ -15,8 +16,10 @@ import { StoreState } from 'app/types';
|
|||||||
import { isSoloRoute } from '../../../routes/utils';
|
import { isSoloRoute } from '../../../routes/utils';
|
||||||
import { getTimeSrv, TimeSrv } from '../services/TimeSrv';
|
import { getTimeSrv, TimeSrv } from '../services/TimeSrv';
|
||||||
import { DashboardModel, PanelModel } from '../state';
|
import { DashboardModel, PanelModel } from '../state';
|
||||||
|
import { getPanelChromeProps } from '../utils/getPanelChromeProps';
|
||||||
|
|
||||||
import { PanelHeader } from './PanelHeader/PanelHeader';
|
import { PanelHeader } from './PanelHeader/PanelHeader';
|
||||||
|
import { PanelHeaderMenuWrapperNew } from './PanelHeader/PanelHeaderMenuWrapper';
|
||||||
|
|
||||||
interface OwnProps {
|
interface OwnProps {
|
||||||
panel: PanelModel;
|
panel: PanelModel;
|
||||||
@@ -27,6 +30,7 @@ interface OwnProps {
|
|||||||
isInView: boolean;
|
isInView: boolean;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
|
hideMenu?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ConnectedProps {
|
interface ConnectedProps {
|
||||||
@@ -58,7 +62,6 @@ export class PanelChromeAngularUnconnected extends PureComponent<Props, State> {
|
|||||||
timeSrv: TimeSrv = getTimeSrv();
|
timeSrv: TimeSrv = getTimeSrv();
|
||||||
scopeProps?: AngularScopeProps;
|
scopeProps?: AngularScopeProps;
|
||||||
subs = new Subscription();
|
subs = new Subscription();
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -179,9 +182,10 @@ export class PanelChromeAngularUnconnected extends PureComponent<Props, State> {
|
|||||||
const { dashboard, panel, isViewing, isEditing, plugin } = this.props;
|
const { dashboard, panel, isViewing, isEditing, plugin } = this.props;
|
||||||
const { errorMessage, data } = this.state;
|
const { errorMessage, data } = this.state;
|
||||||
const { transparent } = panel;
|
const { transparent } = panel;
|
||||||
|
|
||||||
const alertState = data.alertState?.state;
|
const alertState = data.alertState?.state;
|
||||||
|
|
||||||
|
const panelChromeProps = getPanelChromeProps({ ...this.props, data });
|
||||||
|
|
||||||
const containerClassNames = classNames({
|
const containerClassNames = classNames({
|
||||||
'panel-container': true,
|
'panel-container': true,
|
||||||
'panel-container--absolute': isSoloRoute(locationService.getLocation().pathname),
|
'panel-container--absolute': isSoloRoute(locationService.getLocation().pathname),
|
||||||
@@ -196,29 +200,63 @@ export class PanelChromeAngularUnconnected extends PureComponent<Props, State> {
|
|||||||
'panel-content--no-padding': plugin.noPadding,
|
'panel-content--no-padding': plugin.noPadding,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
if (config.featureToggles.newPanelChromeUI) {
|
||||||
<div
|
// Shift the hover menu down if it's on the top row so it doesn't get clipped by topnav
|
||||||
className={containerClassNames}
|
const hoverHeaderOffset = (panel.gridPos?.y ?? 0) === 0 ? -16 : undefined;
|
||||||
data-testid={selectors.components.Panels.Panel.title(panel.title)}
|
|
||||||
aria-label={selectors.components.Panels.Panel.containerByTitle(panel.title)}
|
const menu = (
|
||||||
>
|
<div data-testid="panel-dropdown">
|
||||||
<PanelHeader
|
<PanelHeaderMenuWrapperNew panel={panel} dashboard={dashboard} loadingState={data.state} />
|
||||||
panel={panel}
|
|
||||||
dashboard={dashboard}
|
|
||||||
title={panel.title}
|
|
||||||
description={panel.description}
|
|
||||||
links={panel.links}
|
|
||||||
error={errorMessage}
|
|
||||||
isViewing={isViewing}
|
|
||||||
isEditing={isEditing}
|
|
||||||
data={data}
|
|
||||||
alertState={alertState}
|
|
||||||
/>
|
|
||||||
<div className={panelContentClassNames}>
|
|
||||||
<div ref={(element) => (this.element = element)} className="panel-height-helper" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
|
||||||
|
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}
|
||||||
|
>
|
||||||
|
{() => <div ref={(element) => (this.element = element)} className="panel-height-helper" />}
|
||||||
|
</PanelChrome>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={containerClassNames}
|
||||||
|
data-testid={selectors.components.Panels.Panel.title(panel.title)}
|
||||||
|
aria-label={selectors.components.Panels.Panel.containerByTitle(panel.title)}
|
||||||
|
>
|
||||||
|
<PanelHeader
|
||||||
|
panel={panel}
|
||||||
|
dashboard={dashboard}
|
||||||
|
title={panel.title}
|
||||||
|
description={panel.description}
|
||||||
|
links={panel.links}
|
||||||
|
error={errorMessage}
|
||||||
|
isViewing={isViewing}
|
||||||
|
isEditing={isEditing}
|
||||||
|
data={data}
|
||||||
|
alertState={alertState}
|
||||||
|
/>
|
||||||
|
<div className={panelContentClassNames}>
|
||||||
|
<div ref={(element) => (this.element = element)} className="panel-height-helper" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,34 +13,29 @@ import {
|
|||||||
FieldConfigSource,
|
FieldConfigSource,
|
||||||
getDataSourceRef,
|
getDataSourceRef,
|
||||||
getDefaultTimeRange,
|
getDefaultTimeRange,
|
||||||
LinkModel,
|
|
||||||
LoadingState,
|
LoadingState,
|
||||||
PanelData,
|
PanelData,
|
||||||
PanelPlugin,
|
PanelPlugin,
|
||||||
PanelPluginMeta,
|
PanelPluginMeta,
|
||||||
PluginContextProvider,
|
PluginContextProvider,
|
||||||
renderMarkdown,
|
|
||||||
TimeRange,
|
TimeRange,
|
||||||
toDataFrameDTO,
|
toDataFrameDTO,
|
||||||
toUtc,
|
toUtc,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { getTemplateSrv, config, locationService, RefreshEvent, reportInteraction } from '@grafana/runtime';
|
import { config, locationService, RefreshEvent } from '@grafana/runtime';
|
||||||
import { VizLegendOptions } from '@grafana/schema';
|
import { VizLegendOptions } from '@grafana/schema';
|
||||||
import {
|
import {
|
||||||
ErrorBoundary,
|
ErrorBoundary,
|
||||||
PanelChrome,
|
PanelChrome,
|
||||||
PanelContext,
|
PanelContext,
|
||||||
PanelContextProvider,
|
PanelContextProvider,
|
||||||
PanelPadding,
|
|
||||||
SeriesVisibilityChangeMode,
|
SeriesVisibilityChangeMode,
|
||||||
AdHocFilterItem,
|
AdHocFilterItem,
|
||||||
} from '@grafana/ui';
|
} from '@grafana/ui';
|
||||||
import { PANEL_BORDER } from 'app/core/constants';
|
import { PANEL_BORDER } from 'app/core/constants';
|
||||||
import { profiler } from 'app/core/profiler';
|
import { profiler } from 'app/core/profiler';
|
||||||
import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel';
|
import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel';
|
||||||
import { InspectTab } from 'app/features/inspector/types';
|
|
||||||
import { getPanelLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
|
|
||||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||||
import { applyFilterFromTable } from 'app/features/variables/adhoc/actions';
|
import { applyFilterFromTable } from 'app/features/variables/adhoc/actions';
|
||||||
import { onUpdatePanelSnapshotData } from 'app/plugins/datasource/grafana/utils';
|
import { onUpdatePanelSnapshotData } from 'app/plugins/datasource/grafana/utils';
|
||||||
@@ -53,11 +48,11 @@ import { deleteAnnotation, saveAnnotation, updateAnnotation } from '../../annota
|
|||||||
import { getDashboardQueryRunner } from '../../query/state/DashboardQueryRunner/DashboardQueryRunner';
|
import { getDashboardQueryRunner } from '../../query/state/DashboardQueryRunner/DashboardQueryRunner';
|
||||||
import { getTimeSrv, TimeSrv } from '../services/TimeSrv';
|
import { getTimeSrv, TimeSrv } from '../services/TimeSrv';
|
||||||
import { DashboardModel, PanelModel } from '../state';
|
import { DashboardModel, PanelModel } from '../state';
|
||||||
|
import { getPanelChromeProps } from '../utils/getPanelChromeProps';
|
||||||
import { loadSnapshotData } from '../utils/loadSnapshotData';
|
import { loadSnapshotData } from '../utils/loadSnapshotData';
|
||||||
|
|
||||||
import { PanelHeader } from './PanelHeader/PanelHeader';
|
import { PanelHeader } from './PanelHeader/PanelHeader';
|
||||||
import { PanelHeaderMenuWrapperNew } from './PanelHeader/PanelHeaderMenuWrapper';
|
import { PanelHeaderMenuWrapperNew } from './PanelHeader/PanelHeaderMenuWrapper';
|
||||||
import { PanelHeaderTitleItems } from './PanelHeader/PanelHeaderTitleItems';
|
|
||||||
import { seriesVisibilityConfigFactory } from './SeriesVisibilityConfigFactory';
|
import { seriesVisibilityConfigFactory } from './SeriesVisibilityConfigFactory';
|
||||||
import { liveTimer } from './liveTimer';
|
import { liveTimer } from './liveTimer';
|
||||||
|
|
||||||
@@ -91,7 +86,6 @@ export class PanelStateWrapper extends PureComponent<Props, State> {
|
|||||||
private readonly timeSrv: TimeSrv = getTimeSrv();
|
private readonly timeSrv: TimeSrv = getTimeSrv();
|
||||||
private subs = new Subscription();
|
private subs = new Subscription();
|
||||||
private eventFilter: EventFilterOptions = { onlyLocal: true };
|
private eventFilter: EventFilterOptions = { onlyLocal: true };
|
||||||
private descriptionInteractionReported = false;
|
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -605,53 +599,6 @@ export class PanelStateWrapper extends PureComponent<Props, State> {
|
|||||||
return !panel.hasTitle();
|
return !panel.hasTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
onShowPanelDescription = () => {
|
|
||||||
const { panel } = this.props;
|
|
||||||
const descriptionMarkdown = getTemplateSrv().replace(panel.description, panel.scopedVars);
|
|
||||||
const interpolatedDescription = renderMarkdown(descriptionMarkdown);
|
|
||||||
|
|
||||||
if (!this.descriptionInteractionReported) {
|
|
||||||
// Description rendering function can be called multiple times due to re-renders but we want to report the interaction once.
|
|
||||||
reportInteraction('dashboards_panelheader_description_displayed');
|
|
||||||
this.descriptionInteractionReported = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return interpolatedDescription;
|
|
||||||
};
|
|
||||||
|
|
||||||
onShowPanelLinks = (): LinkModel[] => {
|
|
||||||
const { panel } = this.props;
|
|
||||||
const linkSupplier = getPanelLinksSupplier(panel);
|
|
||||||
if (linkSupplier) {
|
|
||||||
const panelLinks = linkSupplier && linkSupplier.getLinks(panel.replaceVariables);
|
|
||||||
|
|
||||||
return panelLinks.map((panelLink) => ({
|
|
||||||
...panelLink,
|
|
||||||
onClick: (...args) => {
|
|
||||||
reportInteraction('dashboards_panelheader_datalink_clicked', { has_multiple_links: panelLinks.length > 1 });
|
|
||||||
panelLink.onClick?.(...args);
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
};
|
|
||||||
|
|
||||||
onOpenInspector = (e: React.SyntheticEvent, tab: string) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
locationService.partial({ inspect: this.props.panel.id, inspectTab: tab });
|
|
||||||
};
|
|
||||||
|
|
||||||
onOpenErrorInspect = (e: React.SyntheticEvent) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
locationService.partial({ inspect: this.props.panel.id, inspectTab: InspectTab.Error });
|
|
||||||
reportInteraction('dashboards_panelheader_statusmessage_clicked');
|
|
||||||
};
|
|
||||||
|
|
||||||
onCancelQuery = () => {
|
|
||||||
this.props.panel.getQueryRunner().cancelQuery();
|
|
||||||
reportInteraction('dashboards_panelheader_cancelquery_clicked', { data_state: this.state.data.state });
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { dashboard, panel, isViewing, isEditing, width, height, plugin } = this.props;
|
const { dashboard, panel, isViewing, isEditing, width, height, plugin } = this.props;
|
||||||
const { errorMessage, data } = this.state;
|
const { errorMessage, data } = this.state;
|
||||||
@@ -668,27 +615,8 @@ export class PanelStateWrapper extends PureComponent<Props, State> {
|
|||||||
[`panel-alert-state--${alertState}`]: alertState !== undefined,
|
[`panel-alert-state--${alertState}`]: alertState !== undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const title = panel.getDisplayTitle();
|
const panelChromeProps = getPanelChromeProps({ ...this.props, data });
|
||||||
const padding: PanelPadding = plugin.noPadding ? 'none' : 'md';
|
|
||||||
|
|
||||||
const showTitleItems =
|
|
||||||
(panel.links && panel.links.length > 0 && this.onShowPanelLinks) ||
|
|
||||||
(data.series.length > 0 && data.series.some((v) => (v.meta?.notices?.length ?? 0) > 0)) ||
|
|
||||||
(data.request && data.request.timeInfo) ||
|
|
||||||
alertState;
|
|
||||||
|
|
||||||
const titleItems = showTitleItems && (
|
|
||||||
<PanelHeaderTitleItems
|
|
||||||
key="title-items"
|
|
||||||
alertState={alertState}
|
|
||||||
data={data}
|
|
||||||
panelId={panel.id}
|
|
||||||
panelLinks={panel.links}
|
|
||||||
onShowPanelLinks={this.onShowPanelLinks}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const dragClass = !(isViewing || isEditing) ? 'grid-drag-handle' : '';
|
|
||||||
if (config.featureToggles.newPanelChromeUI) {
|
if (config.featureToggles.newPanelChromeUI) {
|
||||||
// Shift the hover menu down if it's on the top row so it doesn't get clipped by topnav
|
// 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;
|
const hoverHeaderOffset = (panel.gridPos?.y ?? 0) === 0 ? -16 : undefined;
|
||||||
@@ -703,20 +631,20 @@ export class PanelStateWrapper extends PureComponent<Props, State> {
|
|||||||
<PanelChrome
|
<PanelChrome
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
title={title}
|
title={panelChromeProps.title}
|
||||||
loadingState={data.state}
|
loadingState={data.state}
|
||||||
statusMessage={errorMessage}
|
statusMessage={errorMessage}
|
||||||
statusMessageOnClick={this.onOpenErrorInspect}
|
statusMessageOnClick={panelChromeProps.onOpenErrorInspect}
|
||||||
description={!!panel.description ? this.onShowPanelDescription : undefined}
|
description={panelChromeProps.description}
|
||||||
titleItems={titleItems}
|
titleItems={panelChromeProps.titleItems}
|
||||||
menu={this.props.hideMenu ? undefined : menu}
|
menu={this.props.hideMenu ? undefined : menu}
|
||||||
dragClass={dragClass}
|
dragClass={panelChromeProps.dragClass}
|
||||||
dragClassCancel="grid-drag-cancel"
|
dragClassCancel="grid-drag-cancel"
|
||||||
padding={padding}
|
padding={panelChromeProps.padding}
|
||||||
hoverHeaderOffset={hoverHeaderOffset}
|
hoverHeaderOffset={hoverHeaderOffset}
|
||||||
hoverHeader={this.hasOverlayHeader()}
|
hoverHeader={panelChromeProps.hasOverlayHeader()}
|
||||||
displayMode={transparent ? 'transparent' : 'default'}
|
displayMode={transparent ? 'transparent' : 'default'}
|
||||||
onCancelQuery={this.onCancelQuery}
|
onCancelQuery={panelChromeProps.onCancelQuery}
|
||||||
>
|
>
|
||||||
{(innerWidth, innerHeight) => (
|
{(innerWidth, innerHeight) => (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { LinkModel, PanelData, PanelPlugin, renderMarkdown } from '@grafana/data';
|
||||||
|
import { getTemplateSrv, locationService, reportInteraction } from '@grafana/runtime';
|
||||||
|
import { PanelPadding } from '@grafana/ui';
|
||||||
|
import { InspectTab } from 'app/features/inspector/types';
|
||||||
|
import { getPanelLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
|
||||||
|
|
||||||
|
import { PanelHeaderTitleItems } from '../dashgrid/PanelHeader/PanelHeaderTitleItems';
|
||||||
|
import { DashboardModel, PanelModel } from '../state';
|
||||||
|
|
||||||
|
interface CommonProps {
|
||||||
|
panel: PanelModel;
|
||||||
|
data: PanelData;
|
||||||
|
dashboard: DashboardModel;
|
||||||
|
plugin: PanelPlugin;
|
||||||
|
isViewing: boolean;
|
||||||
|
isEditing: boolean;
|
||||||
|
isInView: boolean;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
hideMenu?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPanelChromeProps(props: CommonProps) {
|
||||||
|
let descriptionInteractionReported = false;
|
||||||
|
|
||||||
|
function hasOverlayHeader() {
|
||||||
|
// always show normal header if we have time override
|
||||||
|
if (props.data.request && props.data.request.timeInfo) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !props.panel.hasTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
const onShowPanelDescription = () => {
|
||||||
|
const descriptionMarkdown = getTemplateSrv().replace(props.panel.description, props.panel.scopedVars);
|
||||||
|
const interpolatedDescription = renderMarkdown(descriptionMarkdown);
|
||||||
|
|
||||||
|
if (!descriptionInteractionReported) {
|
||||||
|
// Description rendering function can be called multiple times due to re-renders but we want to report the interaction once.
|
||||||
|
reportInteraction('dashboards_panelheader_description_displayed');
|
||||||
|
descriptionInteractionReported = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return interpolatedDescription;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onShowPanelLinks = (): LinkModel[] => {
|
||||||
|
const linkSupplier = getPanelLinksSupplier(props.panel);
|
||||||
|
if (!linkSupplier) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const panelLinks = linkSupplier && linkSupplier.getLinks(props.panel.replaceVariables);
|
||||||
|
|
||||||
|
return panelLinks.map((panelLink) => ({
|
||||||
|
...panelLink,
|
||||||
|
onClick: (...args) => {
|
||||||
|
reportInteraction('dashboards_panelheader_datalink_clicked', { has_multiple_links: panelLinks.length > 1 });
|
||||||
|
panelLink.onClick?.(...args);
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOpenInspector = (e: React.SyntheticEvent, tab: string) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
locationService.partial({ inspect: props.panel.id, inspectTab: tab });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOpenErrorInspect = (e: React.SyntheticEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
locationService.partial({ inspect: props.panel.id, inspectTab: InspectTab.Error });
|
||||||
|
reportInteraction('dashboards_panelheader_statusmessage_clicked');
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCancelQuery = () => {
|
||||||
|
props.panel.getQueryRunner().cancelQuery();
|
||||||
|
reportInteraction('dashboards_panelheader_cancelquery_clicked', { data_state: props.data.state });
|
||||||
|
};
|
||||||
|
|
||||||
|
const padding: PanelPadding = props.plugin.noPadding ? 'none' : 'md';
|
||||||
|
const alertState = props.data.alertState?.state;
|
||||||
|
|
||||||
|
const showTitleItems =
|
||||||
|
(props.panel.links && props.panel.links.length > 0 && onShowPanelLinks) ||
|
||||||
|
(props.data.series.length > 0 && props.data.series.some((v) => (v.meta?.notices?.length ?? 0) > 0)) ||
|
||||||
|
(props.data.request && props.data.request.timeInfo) ||
|
||||||
|
alertState;
|
||||||
|
|
||||||
|
const titleItems = showTitleItems && (
|
||||||
|
<PanelHeaderTitleItems
|
||||||
|
alertState={alertState}
|
||||||
|
data={props.data}
|
||||||
|
panelId={props.panel.id}
|
||||||
|
panelLinks={props.panel.links}
|
||||||
|
onShowPanelLinks={onShowPanelLinks}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const description = props.panel.description ? onShowPanelDescription() : undefined;
|
||||||
|
|
||||||
|
const dragClass = !(props.isViewing || props.isEditing) ? 'grid-drag-handle' : '';
|
||||||
|
|
||||||
|
const title = props.panel.getDisplayTitle();
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasOverlayHeader,
|
||||||
|
onShowPanelDescription,
|
||||||
|
onShowPanelLinks,
|
||||||
|
onOpenInspector,
|
||||||
|
onOpenErrorInspect,
|
||||||
|
onCancelQuery,
|
||||||
|
padding,
|
||||||
|
description,
|
||||||
|
dragClass,
|
||||||
|
title,
|
||||||
|
titleItems,
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user