DataLinks: enable data links in Gauge, BarGauge and SingleStat2 panel (#18605)

* datalink on field

* add dataFrame to view

* Use scoped variables to pass series name and value time to data links interpolation

* Use scoped variables to pass series name and value time to data links interpolation

* Enable value specific variable suggestions when Gauge is displaying values

* Fix prettier

* Add basic context menu with data links to GaugePanel

* Fix incorrect import in grafana/ui

* Add custom cursor indicating datalinks available via context menu (in Gauge only now)

* Add data links to SingleStat2

* Minor refactor

* Retrieve data links in a lazy way

* Update test to respect links retrieval being lazy

* delay link creation

* cleanup

* Add origin to LinkModel and introduce field & panel links suppliers

* Add value time and series name field link supplier

* Remove links prop from visualization and implement common UI for data links context menu

* Update snapshot

* Rename className prop to clickTargetClassName

* Simplify condition

* Updated drilldown dashboard and minor changes

* Use class name an onClick handler on the top level dom element in visualization

* Enable series name interpolation when presented value is a calculation
This commit is contained in:
Ryan McKinley
2019-08-27 23:50:43 -07:00
committed by Dominik Prokop
parent e1924608a2
commit ff6b8c5adc
38 changed files with 708 additions and 243 deletions

View File

@@ -5,11 +5,12 @@ import React, { PureComponent } from 'react';
import { config } from 'app/core/config';
// Components
import { BarGauge, VizRepeater, getFieldDisplayValues, FieldDisplay } from '@grafana/ui';
import { BarGauge, VizRepeater, getFieldDisplayValues, FieldDisplay, DataLinksContextMenu } from '@grafana/ui';
// Types
import { BarGaugeOptions } from './types';
import { PanelProps } from '@grafana/ui';
import { getFieldLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
renderValue = (value: FieldDisplay, width: number, height: number): JSX.Element => {
@@ -17,18 +18,26 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
const { field, display } = value;
return (
<BarGauge
value={display}
width={width}
height={height}
orientation={options.orientation}
thresholds={field.thresholds}
theme={config.theme}
itemSpacing={this.getItemSpacing()}
displayMode={options.displayMode}
minValue={field.min}
maxValue={field.max}
/>
<DataLinksContextMenu links={getFieldLinksSupplier(value)}>
{({ openMenu, targetClassName }) => {
return (
<BarGauge
value={display}
width={width}
height={height}
orientation={options.orientation}
thresholds={field.thresholds}
theme={config.theme}
itemSpacing={this.getItemSpacing()}
displayMode={options.displayMode}
minValue={field.min}
maxValue={field.max}
onClick={openMenu}
className={targetClassName}
/>
);
}}
</DataLinksContextMenu>
);
};

View File

@@ -12,11 +12,16 @@ import {
FormLabel,
PanelEditorProps,
Select,
DataLinksEditor,
} from '@grafana/ui';
import { FieldConfig } from '@grafana/data';
import { FieldConfig, DataLink } from '@grafana/data';
import { Threshold, ValueMapping } from '@grafana/data';
import { BarGaugeOptions, orientationOptions, displayModes } from './types';
import {
getDataLinksVariableSuggestions,
getCalculationValueDataLinksVariableSuggestions,
} from 'app/features/panel/panellinks/link_srv';
export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGaugeOptions>> {
onThresholdsChanged = (thresholds: Threshold[]) => {
@@ -51,11 +56,20 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
onOrientationChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, orientation: value });
onDisplayModeChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, displayMode: value });
onDataLinksChanged = (links: DataLink[]) => {
this.onDefaultsChange({
...this.props.options.fieldOptions.defaults,
links,
});
};
render() {
const { options } = this.props;
const { fieldOptions } = options;
const { defaults } = fieldOptions;
const suggestions = fieldOptions.values
? getDataLinksVariableSuggestions()
: getCalculationValueDataLinksVariableSuggestions();
const labelWidth = 6;
return (
@@ -92,6 +106,15 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
</PanelOptionsGrid>
<ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
<PanelOptionsGroup title="Data links">
<DataLinksEditor
value={defaults.links}
onChange={this.onDataLinksChanged}
suggestions={suggestions}
maxLinks={10}
/>
</PanelOptionsGroup>
</>
);
}

View File

@@ -5,11 +5,12 @@ import React, { PureComponent } from 'react';
import { config } from 'app/core/config';
// Components
import { Gauge, FieldDisplay, getFieldDisplayValues, VizOrientation } from '@grafana/ui';
import { Gauge, FieldDisplay, getFieldDisplayValues, VizOrientation, DataLinksContextMenu } from '@grafana/ui';
// Types
import { GaugeOptions } from './types';
import { PanelProps, VizRepeater } from '@grafana/ui';
import { getFieldLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> {
renderValue = (value: FieldDisplay, width: number, height: number): JSX.Element => {
@@ -17,17 +18,25 @@ export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> {
const { field, display } = value;
return (
<Gauge
value={display}
width={width}
height={height}
thresholds={field.thresholds}
showThresholdLabels={options.showThresholdLabels}
showThresholdMarkers={options.showThresholdMarkers}
minValue={field.min}
maxValue={field.max}
theme={config.theme}
/>
<DataLinksContextMenu links={getFieldLinksSupplier(value)}>
{({ openMenu, targetClassName }) => {
return (
<Gauge
value={display}
width={width}
height={height}
thresholds={field.thresholds}
showThresholdLabels={options.showThresholdLabels}
showThresholdMarkers={options.showThresholdMarkers}
minValue={field.min}
maxValue={field.max}
theme={config.theme}
onClick={openMenu}
className={targetClassName}
/>
);
}}
</DataLinksContextMenu>
);
};

View File

@@ -10,10 +10,15 @@ import {
FieldPropertiesEditor,
Switch,
PanelOptionsGroup,
DataLinksEditor,
} from '@grafana/ui';
import { Threshold, ValueMapping, FieldConfig } from '@grafana/data';
import { Threshold, ValueMapping, FieldConfig, DataLink } from '@grafana/data';
import { GaugeOptions } from './types';
import {
getCalculationValueDataLinksVariableSuggestions,
getDataLinksVariableSuggestions,
} from 'app/features/panel/panellinks/link_srv';
export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOptions>> {
labelWidth = 6;
@@ -56,10 +61,20 @@ export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOption
});
};
onDataLinksChanged = (links: DataLink[]) => {
this.onDefaultsChange({
...this.props.options.fieldOptions.defaults,
links,
});
};
render() {
const { options } = this.props;
const { fieldOptions, showThresholdLabels, showThresholdMarkers } = options;
const { defaults } = fieldOptions;
const suggestions = fieldOptions.values
? getDataLinksVariableSuggestions()
: getCalculationValueDataLinksVariableSuggestions();
return (
<>
@@ -92,6 +107,15 @@ export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOption
</PanelOptionsGrid>
<ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
<PanelOptionsGroup title="Data links">
<DataLinksEditor
value={defaults.links}
onChange={this.onDataLinksChanged}
suggestions={suggestions}
maxLinks={10}
/>
</PanelOptionsGroup>
</>
);
}

View File

@@ -25,7 +25,7 @@ import ReactDOM from 'react-dom';
import { GraphLegendProps, Legend } from './Legend/Legend';
import { GraphCtrl } from './module';
import { getValueFormat, ContextMenuItem, ContextMenuGroup } from '@grafana/ui';
import { getValueFormat, ContextMenuItem, ContextMenuGroup, DataLinkBuiltInVars } from '@grafana/ui';
import { provideTheme } from 'app/core/utils/ConfigProvider';
import { DataLink, toUtc } from '@grafana/data';
import { GraphContextMenuCtrl, FlotDataPoint } from './GraphContextMenuCtrl';
@@ -196,10 +196,15 @@ class GraphElement {
{
items: [
...dataLinks.map<ContextMenuItem>(link => {
const linkUiModel = this.linkSrv.getDataLinkUIModel(link, this.panel.scopedVars, {
seriesName: item.series.alias,
datapoint: item.datapoint,
});
const linkUiModel = this.linkSrv.getDataLinkUIModel(
link,
{
...this.panel.scopedVars,
[DataLinkBuiltInVars.seriesName]: { value: item.series.alias, text: item.series.alias },
[DataLinkBuiltInVars.valueTime]: { value: item.datapoint[0], text: item.datapoint[0] },
},
item
);
return {
label: linkUiModel.title,
url: linkUiModel.href,

View File

@@ -24,9 +24,10 @@ import {
DisplayValue,
fieldReducers,
KeyValue,
LinkModel,
} from '@grafana/data';
import { auto } from 'angular';
import { LinkSrv, LinkModel } from 'app/features/panel/panellinks/link_srv';
import { LinkSrv } from 'app/features/panel/panellinks/link_srv';
import { PanelQueryRunnerFormat } from 'app/features/dashboard/state/PanelQueryRunner';
import { getProcessedDataFrames } from 'app/features/dashboard/state/PanelQueryState';
@@ -328,7 +329,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
const $sanitize = this.$sanitize;
const panel = ctrl.panel;
const templateSrv = this.templateSrv;
let linkInfo: LinkModel | null = null;
let linkInfo: LinkModel<any> | null = null;
const $panelContainer = elem.find('.panel-container');
elem = elem.find('.singlestat-panel');
@@ -592,7 +593,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
elem.toggleClass('pointer', panel.links.length > 0);
if (panel.links.length > 0) {
linkInfo = linkSrv.getDataLinkUIModel(panel.links[0], data.scopedVars);
linkInfo = linkSrv.getDataLinkUIModel(panel.links[0], data.scopedVars, {});
} else {
linkInfo = null;
}

View File

@@ -9,13 +9,18 @@ import {
FieldDisplayEditor,
FieldPropertiesEditor,
PanelOptionsGroup,
DataLinksEditor,
} from '@grafana/ui';
import { Threshold, ValueMapping, FieldConfig } from '@grafana/data';
import { Threshold, ValueMapping, FieldConfig, DataLink } from '@grafana/data';
import { SingleStatOptions, SparklineOptions } from './types';
import { ColoringEditor } from './ColoringEditor';
import { FontSizeEditor } from './FontSizeEditor';
import { SparklineEditor } from './SparklineEditor';
import {
getDataLinksVariableSuggestions,
getCalculationValueDataLinksVariableSuggestions,
} from 'app/features/panel/panellinks/link_srv';
export class SingleStatEditor extends PureComponent<PanelEditorProps<SingleStatOptions>> {
onThresholdsChanged = (thresholds: Threshold[]) => {
@@ -53,10 +58,20 @@ export class SingleStatEditor extends PureComponent<PanelEditorProps<SingleStatO
});
};
onDataLinksChanged = (links: DataLink[]) => {
this.onDefaultsChange({
...this.props.options.fieldOptions.defaults,
links,
});
};
render() {
const { options } = this.props;
const { fieldOptions } = options;
const { defaults } = fieldOptions;
const suggestions = fieldOptions.values
? getDataLinksVariableSuggestions()
: getCalculationValueDataLinksVariableSuggestions();
return (
<>
@@ -77,6 +92,15 @@ export class SingleStatEditor extends PureComponent<PanelEditorProps<SingleStatO
</PanelOptionsGrid>
<ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
<PanelOptionsGroup title="Data links">
<DataLinksEditor
value={defaults.links}
onChange={this.onDataLinksChanged}
suggestions={suggestions}
maxLinks={10}
/>
</PanelOptionsGroup>
</>
);
}

View File

@@ -6,8 +6,16 @@ import { config } from 'app/core/config';
// Types
import { SingleStatOptions } from './types';
import { PanelProps, getFieldDisplayValues, VizRepeater, FieldDisplay, BigValue } from '@grafana/ui';
import {
PanelProps,
getFieldDisplayValues,
VizRepeater,
FieldDisplay,
BigValue,
DataLinksContextMenu,
} from '@grafana/ui';
import { BigValueSparkline } from '@grafana/ui/src/components/BigValue/BigValue';
import { getFieldLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
export class SingleStatPanel extends PureComponent<PanelProps<SingleStatOptions>> {
renderValue = (value: FieldDisplay, width: number, height: number): JSX.Element => {
@@ -23,7 +31,23 @@ export class SingleStatPanel extends PureComponent<PanelProps<SingleStatOptions>
};
}
return <BigValue value={value.display} sparkline={sparkline} width={width} height={height} theme={config.theme} />;
return (
<DataLinksContextMenu links={getFieldLinksSupplier(value)}>
{({ openMenu, targetClassName }) => {
return (
<BigValue
value={value.display}
sparkline={sparkline}
width={width}
height={height}
theme={config.theme}
onClick={openMenu}
className={targetClassName}
/>
);
}}
</DataLinksContextMenu>
);
};
getValues = (): FieldDisplay[] => {