From ec783fbff47b78991e9488583375c114df082786 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Tue, 4 Aug 2020 21:22:14 -0700 Subject: [PATCH] Fields: __field.name as field name and __field.displayName as displayName (#26531) * name vs displayName * name vs displayName * add __values * add docs for displayName expressions * Update docs/sources/panels/field-configuration-options.md Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Update docs/sources/panels/field-configuration-options.md Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Update docs/sources/panels/field-configuration-options.md Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Update docs/sources/panels/field-configuration-options.md Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Update docs/sources/panels/field-configuration-options.md Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Update docs/sources/panels/field-configuration-options.md Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> Co-authored-by: kyle Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> --- .../panels/field-configuration-options.md | 14 ++++++- .../grafana-data/src/field/fieldDisplay.ts | 2 +- .../src/field/fieldOverrides.test.ts | 12 +----- .../grafana-data/src/field/fieldOverrides.ts | 8 +--- .../src/field/templateProxies.test.ts | 32 +++++++++++++++ .../grafana-data/src/field/templateProxies.ts | 41 +++++++++++++++++++ packages/grafana-data/src/utils/labels.ts | 5 ++- 7 files changed, 95 insertions(+), 19 deletions(-) create mode 100644 packages/grafana-data/src/field/templateProxies.test.ts create mode 100644 packages/grafana-data/src/field/templateProxies.ts diff --git a/docs/sources/panels/field-configuration-options.md b/docs/sources/panels/field-configuration-options.md index 9b528acb574..026ae3b3202 100644 --- a/docs/sources/panels/field-configuration-options.md +++ b/docs/sources/panels/field-configuration-options.md @@ -139,7 +139,19 @@ For more information and instructions, refer to [Data links]({{< relref "../link Lets you set the display title of all fields. You can use [variables]({{< relref "../variables/templates-and-variables.md" >}}) in the field title. -When multiple stats are shown, this field controls the title in each stat. By default this is the series name and field name. You can use expressions like ${__series.name} or ${**field.name} to use only series name or field name in title or \${**cell_2} to refer to other fields (2 being field/column with index 2). +When multiple stats, fields, or series are shown, this field controls the title in each stat. You can use expressions like `${__field.name}` to use only the series name or the field name in title. + +Given a field with a name of Temp, and labels of {"Loc"="PBI", "Sensor"="3"} + +| Expression syntax | Example | Renders to | Explanation | +| ---------------------------- | ---------------------- | --------------------------------- | ----------- | +| `${__field.displayName}` | Same as syntax | `Temp {Loc="PBI", Sensor="3"}` | Displays the field name, and labels in `{}` if they are present. If there is only one label key in the response, then for the label portion, Grafana displays the value of the label without the enclosing braces. | +| `${__field.name}` | Same as syntax | `Temp` | Displays the name of the field (without labels). | +| `${__field.labels}` | Same as syntax | `Loc="PBI", Sensor="3"` | Displays the labels without the name. | +| `${__field.labels.X}` | `${__field.labels.Loc}` | `PBI` | Displays the value of the specified label key. | +| `${__field.labels.__values}` | Same as Syntax | `PBI, 3` | Displays the values of the labels separated by a comma (without label keys). | + +If the value is an empty string after rendering the expression for a particular field, then the default display method is used. ### Max diff --git a/packages/grafana-data/src/field/fieldDisplay.ts b/packages/grafana-data/src/field/fieldDisplay.ts index 53efac23894..9fc5b325fc5 100644 --- a/packages/grafana-data/src/field/fieldDisplay.ts +++ b/packages/grafana-data/src/field/fieldDisplay.ts @@ -40,7 +40,7 @@ export interface ReduceDataOptions { // TODO: use built in variables, same as for data links? export const VAR_SERIES_NAME = '__series.name'; -export const VAR_FIELD_NAME = '__field.name'; +export const VAR_FIELD_NAME = '__field.displayName'; // Includes the rendered tags and naming strategy export const VAR_FIELD_LABELS = '__field.labels'; export const VAR_CALC = '__calc'; export const VAR_CELL_PREFIX = '__cell_'; // consistent with existing table templates diff --git a/packages/grafana-data/src/field/fieldOverrides.test.ts b/packages/grafana-data/src/field/fieldOverrides.test.ts index 46c7ef1db3e..f478083ea2b 100644 --- a/packages/grafana-data/src/field/fieldOverrides.test.ts +++ b/packages/grafana-data/src/field/fieldOverrides.test.ts @@ -127,11 +127,7 @@ describe('applyFieldOverrides', () => { Object { "__field": Object { "text": "Field", - "value": Object { - "formattedLabels": "", - "labels": undefined, - "name": "A message", - }, + "value": Object {}, }, "__series": Object { "text": "Series", @@ -146,11 +142,7 @@ describe('applyFieldOverrides', () => { Object { "__field": Object { "text": "Field", - "value": Object { - "formattedLabels": "", - "labels": undefined, - "name": "B info", - }, + "value": Object {}, }, "__series": Object { "text": "Series", diff --git a/packages/grafana-data/src/field/fieldOverrides.ts b/packages/grafana-data/src/field/fieldOverrides.ts index 2bf3e09ef21..60d7e525883 100644 --- a/packages/grafana-data/src/field/fieldOverrides.ts +++ b/packages/grafana-data/src/field/fieldOverrides.ts @@ -32,10 +32,10 @@ import { FieldConfigOptionsRegistry } from './FieldConfigOptionsRegistry'; import { DataLinkBuiltInVars, locationUtil } from '../utils'; import { formattedValueToString } from '../valueFormats'; import { getFieldDisplayValuesProxy } from './getFieldDisplayValuesProxy'; -import { formatLabels } from '../utils/labels'; import { getFrameDisplayName, getFieldDisplayName } from './fieldState'; import { getTimeField } from '../dataframe/processDataFrame'; import { mapInternalLinkToExplore } from '../utils/dataLinks'; +import { getTemplateProxyForField } from './templateProxies'; interface OverrideProps { match: FieldMatcher; @@ -113,11 +113,7 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra fieldScopedVars['__field'] = { text: 'Field', - value: { - name: displayName, // Generally appropriate (may include the series name if useful) - formattedLabels: formatLabels(field.labels!), - labels: field.labels, - }, + value: getTemplateProxyForField(field, frame, options.data), }; field.state = { diff --git a/packages/grafana-data/src/field/templateProxies.test.ts b/packages/grafana-data/src/field/templateProxies.test.ts new file mode 100644 index 00000000000..d1ea6745aa2 --- /dev/null +++ b/packages/grafana-data/src/field/templateProxies.test.ts @@ -0,0 +1,32 @@ +import { getTemplateProxyForField } from './templateProxies'; +import { toDataFrame } from '../dataframe'; + +describe('Template proxies', () => { + it('supports name and displayName', () => { + const frames = [ + toDataFrame({ + fields: [ + { + name: '🔥', + config: { displayName: '✨' }, + labels: { + b: 'BBB', + a: 'AAA', + }, + }, + ], + }), + ]; + + const f = getTemplateProxyForField(frames[0].fields[0], frames[0], frames); + + expect(f.name).toEqual('🔥'); + expect(f.displayName).toEqual('✨'); + expect(`${f.labels}`).toEqual('a="AAA", b="BBB"'); + expect(f.labels.__values).toEqual('AAA, BBB'); + expect(f.labels.a).toEqual('AAA'); + + // Deprecated syntax + expect(`${f.formattedLabels}`).toEqual('a="AAA", b="BBB"'); + }); +}); diff --git a/packages/grafana-data/src/field/templateProxies.ts b/packages/grafana-data/src/field/templateProxies.ts new file mode 100644 index 00000000000..6889aa45366 --- /dev/null +++ b/packages/grafana-data/src/field/templateProxies.ts @@ -0,0 +1,41 @@ +import { DataFrame, Field } from '../types'; +import { getFieldDisplayName } from './fieldState'; +import { formatLabels } from '../utils/labels'; + +/** + * This object is created often, and only used when tmplates exist. Using a proxy lets us delay + * calculations of the more complex structures (label names) until they are actually used + */ +export function getTemplateProxyForField(field: Field, frame?: DataFrame, frames?: DataFrame[]): any { + return new Proxy( + {} as any, // This object shows up in test snapshots + { + get: (obj: Field, key: string, reciever: any) => { + if (key === 'name') { + return field.name; + } + + if (key === 'displayName') { + return getFieldDisplayName(field, frame, frames); + } + + if (key === 'labels' || key === 'formattedLabels') { + // formattedLabels deprecated + if (!field.labels) { + return ''; + } + return { + ...field.labels, + __values: Object.values(field.labels) + .sort() + .join(', '), + toString: () => { + return formatLabels(field.labels!, '', true); + }, + }; + } + return undefined; // (field as any)[key]; // any property? + }, + } + ); +} diff --git a/packages/grafana-data/src/utils/labels.ts b/packages/grafana-data/src/utils/labels.ts index 3bc0bec04b8..5e72421d4bb 100644 --- a/packages/grafana-data/src/utils/labels.ts +++ b/packages/grafana-data/src/utils/labels.ts @@ -62,11 +62,14 @@ export function findUniqueLabels(labels: Labels | undefined, commonLabels: Label /** * Serializes the given labels to a string. */ -export function formatLabels(labels: Labels, defaultValue = ''): string { +export function formatLabels(labels: Labels, defaultValue = '', withoutBraces?: boolean): string { if (!labels || Object.keys(labels).length === 0) { return defaultValue; } const labelKeys = Object.keys(labels).sort(); const cleanSelector = labelKeys.map(key => `${key}="${labels[key]}"`).join(', '); + if (withoutBraces) { + return cleanSelector; + } return ['{', cleanSelector, '}'].join(''); }