From 4b8bcfca3e9cb894c76f4137895b552e809c7117 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Wed, 15 Apr 2020 14:39:57 +0200 Subject: [PATCH] Attach links supplier to when applying field overrides --- .../grafana-data/src/field/fieldOverrides.ts | 109 +++++++++++++++++- packages/grafana-data/src/types/dataFrame.ts | 19 ++- .../features/dashboard/services/TimeSrv.ts | 4 +- .../panel/panellinks/linkSuppliers.ts | 21 ++-- 4 files changed, 137 insertions(+), 16 deletions(-) diff --git a/packages/grafana-data/src/field/fieldOverrides.ts b/packages/grafana-data/src/field/fieldOverrides.ts index 1e1aea69cea..10c0edac374 100644 --- a/packages/grafana-data/src/field/fieldOverrides.ts +++ b/packages/grafana-data/src/field/fieldOverrides.ts @@ -11,6 +11,11 @@ import { ScopedVars, ApplyFieldOverrideOptions, FieldConfigPropertyItem, + LinkModel, + InterpolateFunction, + ValueLinkConfig, + LinkModelSupplier, + GrafanaTheme, } from '../types'; import { fieldMatchers, ReducerID, reduceField } from '../transformations'; import { FieldMatcher } from '../types/transformations'; @@ -19,10 +24,11 @@ import set from 'lodash/set'; import unset from 'lodash/unset'; import get from 'lodash/get'; import { getDisplayProcessor } from './displayProcessor'; -import { guessFieldTypeForField } from '../dataframe'; +import { getTimeField, guessFieldTypeForField } from '../dataframe'; import { standardFieldConfigEditorRegistry } from './standardFieldConfigEditorRegistry'; import { FieldConfigOptionsRegistry } from './FieldConfigOptionsRegistry'; import { DataLinkBuiltInVars, locationUtil } from '../utils'; +import { formattedValueToString } from '../valueFormats'; import { getFieldDisplayValuesProxy } from './getFieldDisplayValuesProxy'; interface OverrideProps { @@ -104,10 +110,10 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra if (!fieldName) { fieldName = `Field[${fieldIndex}]`; } + const fieldScopedVars = { ...scopedVars }; + fieldScopedVars['__field'] = { text: 'Field', value: { name: fieldName } }; - scopedVars['__field'] = { text: 'Field', value: { name: fieldName } }; - - const config: FieldConfig = { ...field.config, scopedVars } || {}; + const config: FieldConfig = { ...field.config, scopedVars: fieldScopedVars } || {}; const context = { field, data: options.data!, @@ -184,6 +190,14 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra theme: options.theme, timeZone: options.timeZone, }); + + // Attach data links supplier + if (f.config.links && f.config.links.length > 0) { + f.getDataLinksSupplier = getLinksSupplier(frame, f, fieldScopedVars, context.replaceVariables, { + theme: options.theme, + }); + } + return f; }); @@ -316,3 +330,90 @@ export function validateFieldConfig(config: FieldConfig) { config.min = tmp; } } + +const getLinksSupplier = ( + frame: DataFrame, + field: Field, + fieldScopedVars: ScopedVars, + replaceVariables: InterpolateFunction, + options: { + theme: GrafanaTheme; + } +) => (config: ValueLinkConfig): LinkModelSupplier => { + return { + getLinks: (scopedVars?: any): Array> => { + if (!field.config.links || field.config.links.length === 0) { + return []; + } + const timeRangeUrl = locationUtil.getTimeRangeUrlParams(); + const { timeField } = getTimeField(frame); + + return field.config.links.map(link => { + let href = link.url; + let dataFrameVars = {}; + let valueVars = {}; + + const info: LinkModel = { + href: locationUtil.assureBaseUrl(href.replace(/\n/g, '')), + title: replaceVariables(link.title || '', scopedVars), + target: link.targetBlank ? '_blank' : '_self', + origin: field, + }; + + const variablesQuery = locationUtil.getVariablesUrlParams(scopedVars); + + // We are not displaying reduction result + if (config.valueRowIndex !== undefined && !isNaN(config.valueRowIndex)) { + const fieldsProxy = getFieldDisplayValuesProxy(frame, config.valueRowIndex, options); + valueVars = { + raw: field.values.get(config.valueRowIndex), + numeric: fieldsProxy[field.name].numeric, + text: fieldsProxy[field.name].text, + time: timeField ? timeField.values.get(config.valueRowIndex) : undefined, + }; + dataFrameVars = { + __data: { + value: { + name: frame.name, + refId: frame.refId, + fields: fieldsProxy, + }, + text: 'Data', + }, + }; + } else { + if (config.calculatedValue) { + valueVars = { + raw: config.calculatedValue.display.numeric, + numeric: config.calculatedValue.display.numeric, + text: formattedValueToString(config.calculatedValue.display), + calc: config.calculatedValue.name, + }; + } + } + + info.href = replaceVariables(info.href, { + ...scopedVars, + ...fieldScopedVars, + __value: { + text: 'Value', + value: valueVars, + }, + ...dataFrameVars, + [DataLinkBuiltInVars.keepTime]: { + text: timeRangeUrl, + value: timeRangeUrl, + }, + [DataLinkBuiltInVars.includeVars]: { + text: variablesQuery, + value: variablesQuery, + }, + }); + + info.href = locationUtil.processUrl(info.href); + + return info; + }); + }, + }; +}; diff --git a/packages/grafana-data/src/types/dataFrame.ts b/packages/grafana-data/src/types/dataFrame.ts index 7fa46f6a3d6..7d5c98f39db 100644 --- a/packages/grafana-data/src/types/dataFrame.ts +++ b/packages/grafana-data/src/types/dataFrame.ts @@ -2,11 +2,12 @@ import { ThresholdsConfig } from './thresholds'; import { ValueMapping } from './valueMapping'; import { QueryResultBase, Labels, NullValueMode } from './data'; import { DisplayProcessor } from './displayValue'; -import { DataLink } from './dataLink'; +import { DataLink, LinkModelSupplier } from './dataLink'; import { Vector } from './vector'; import { FieldCalcs } from '../transformations/fieldReducer'; import { FieldColor } from './fieldColor'; import { ScopedVars } from './ScopedVars'; +import { FieldDisplay } from '../field'; export enum FieldType { time = 'time', // or date @@ -57,6 +58,17 @@ export interface FieldConfig { scopedVars?: ScopedVars; } +export interface ValueLinkConfig { + /** + * Result of field reduction + */ + calculatedValue?: FieldDisplay; + /** + * Index of the value row within Field. Should be provided only when value is not a result of a reduction + */ + valueRowIndex?: number; +} + export interface Field> { /** * Name of the field (column) @@ -87,6 +99,11 @@ export interface Field> { * Convert a value for display */ display?: DisplayProcessor; + + /** + * Get value data links with variables interpolated + */ + getDataLinksSupplier?: (config: ValueLinkConfig) => LinkModelSupplier; } export interface DataFrame extends QueryResultBase { diff --git a/public/app/features/dashboard/services/TimeSrv.ts b/public/app/features/dashboard/services/TimeSrv.ts index dbc5b176941..b261d5ba076 100644 --- a/public/app/features/dashboard/services/TimeSrv.ts +++ b/public/app/features/dashboard/services/TimeSrv.ts @@ -264,7 +264,7 @@ export class TimeSrv { this.$timeout(this.refreshDashboard.bind(this), 0); } - timeRangeForUrl() { + timeRangeForUrl = () => { const range = this.timeRange().raw; if (isDateTime(range.from)) { @@ -275,7 +275,7 @@ export class TimeSrv { } return range; - } + }; timeRange(): TimeRange { // make copies if they are moment (do not want to return out internal moment, because they are mutable!) diff --git a/public/app/features/panel/panellinks/linkSuppliers.ts b/public/app/features/panel/panellinks/linkSuppliers.ts index 4b8b5ba7818..5fa8a254b8e 100644 --- a/public/app/features/panel/panellinks/linkSuppliers.ts +++ b/public/app/features/panel/panellinks/linkSuppliers.ts @@ -1,19 +1,20 @@ import { PanelModel } from 'app/features/dashboard/state/PanelModel'; import { + DataLink, + DisplayValue, + Field, FieldDisplay, - LinkModelSupplier, + formattedValueToString, + getFieldDisplayValuesProxy, getTimeField, Labels, - ScopedVars, - ScopedVar, - Field, LinkModel, - formattedValueToString, - DisplayValue, - DataLink, + LinkModelSupplier, + ScopedVar, + ScopedVars, } from '@grafana/data'; import { getLinkSrv } from './link_srv'; -import { getFieldDisplayValuesProxy } from './fieldDisplayValuesProxy'; +import { config } from 'app/core/config'; interface SeriesVars { name?: string; @@ -100,7 +101,9 @@ export const getFieldLinksSupplier = (value: FieldDisplay): LinkModelSupplier