From 5e74062b4bb99b6a41bfbebf74bc82efc665f2a2 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 24 Nov 2021 05:40:04 -0500 Subject: [PATCH] TextPanel: Fix suggestions for existing panels (#42195) (#42196) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #42118 Co-authored-by: kay delaney Co-authored-by: Josh Hunt Co-authored-by: Ashley Harrison Co-authored-by: Hugo Häggmark (cherry picked from commit df22828d7dc8332a6cd3e613b699c7c1a010e597) Co-authored-by: Hugo Häggmark --- .../PanelEditor/OptionsPaneOptions.tsx | 4 +- .../getVisualizationOptions.test.ts | 161 ++++++++++++++++++ ...ptions.tsx => getVisualizationOptions.tsx} | 50 +++++- 3 files changed, 204 insertions(+), 11 deletions(-) create mode 100644 public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.test.ts rename public/app/features/dashboard/components/PanelEditor/{getVizualizationOptions.tsx => getVisualizationOptions.tsx} (83%) diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.tsx index 9dae1eb897f..203c2095fc2 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.tsx @@ -2,7 +2,7 @@ import React, { useMemo, useState } from 'react'; import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import { CustomScrollbar, FilterInput, RadioButtonGroup, useStyles2 } from '@grafana/ui'; import { getPanelFrameCategory } from './getPanelFrameOptions'; -import { getVizualizationOptions } from './getVizualizationOptions'; +import { getVisualizationOptions } from './getVisualizationOptions'; import { css } from '@emotion/css'; import { OptionsPaneCategory } from './OptionsPaneCategory'; import { getFieldOverrideCategories } from './getFieldOverrideElements'; @@ -21,7 +21,7 @@ export const OptionsPaneOptions: React.FC = (props) => { const styles = useStyles2(getStyles); const [panelFrameOptions, vizOptions, libraryPanelOptions] = useMemo( - () => [getPanelFrameCategory(props), getVizualizationOptions(props), getLibraryPanelOptionsCategory(props)], + () => [getPanelFrameCategory(props), getVisualizationOptions(props), getLibraryPanelOptionsCategory(props)], // eslint-disable-next-line react-hooks/exhaustive-deps [panel.configRev, props.data, props.instanceState, searchQuery] diff --git a/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.test.ts b/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.test.ts new file mode 100644 index 00000000000..3d3c461f6f8 --- /dev/null +++ b/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.test.ts @@ -0,0 +1,161 @@ +import { EventBusSrv, FieldType, getDefaultTimeRange, LoadingState, toDataFrame } from '@grafana/data'; +import { getStandardEditorContext } from './getVisualizationOptions'; + +describe('getStandardEditorContext', () => { + it('defaults the series data to an empty array', () => { + const editorContext = getStandardEditorContext({ + data: undefined, + replaceVariables: jest.fn(), + options: {}, + eventBus: new EventBusSrv(), + instanceState: {}, + }); + + expect(editorContext.data).toEqual([]); + }); + + it('returns suggestions for empty data', () => { + const editorContext = getStandardEditorContext({ + data: undefined, + replaceVariables: jest.fn(), + options: {}, + eventBus: new EventBusSrv(), + instanceState: {}, + }); + + expect(editorContext.getSuggestions).toBeDefined(); + expect(editorContext.getSuggestions?.()).toEqual([ + { + documentation: 'Name of the series', + label: 'Name', + origin: 'series', + value: '__series.name', + }, + { + documentation: 'Field name of the clicked datapoint (in ms epoch)', + label: 'Name', + origin: 'field', + value: '__field.name', + }, + { + documentation: 'Adds current variables', + label: 'All variables', + origin: 'template', + value: '__all_variables', + }, + { + documentation: 'Adds current time range', + label: 'Time range', + origin: 'built-in', + value: '__url_time_range', + }, + { + documentation: "Adds current time range's from value", + label: 'Time range: from', + origin: 'built-in', + value: '__from', + }, + { + documentation: "Adds current time range's to value", + label: 'Time range: to', + origin: 'built-in', + value: '__to', + }, + ]); + }); + + it('returns suggestions for non-empty data', () => { + const series = [ + toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time }, + { name: 'score', type: FieldType.number }, + ], + }), + ]; + + const panelData = { + series, + timeRange: getDefaultTimeRange(), + state: LoadingState.Done, + }; + + const editorContext = getStandardEditorContext({ + data: panelData, + replaceVariables: jest.fn(), + options: {}, + eventBus: new EventBusSrv(), + instanceState: {}, + }); + + expect(editorContext.getSuggestions).toBeDefined(); + expect(editorContext.getSuggestions?.()).toEqual([ + { + documentation: 'Name of the series', + label: 'Name', + origin: 'series', + value: '__series.name', + }, + { + documentation: 'Field name of the clicked datapoint (in ms epoch)', + label: 'Name', + origin: 'field', + value: '__field.name', + }, + { + documentation: 'Formatted value for time on the same row', + label: 'time', + origin: 'fields', + value: '__data.fields.time', + }, + { + documentation: 'Formatted value for score on the same row', + label: 'score', + origin: 'fields', + value: '__data.fields.score', + }, + { + documentation: 'Enter the field order', + label: 'Select by index', + origin: 'fields', + value: '__data.fields[0]', + }, + { + documentation: 'the numeric field value', + label: 'Show numeric value', + origin: 'fields', + value: '__data.fields.score.numeric', + }, + { + documentation: 'the text value', + label: 'Show text value', + origin: 'fields', + value: '__data.fields.score.text', + }, + { + documentation: 'Adds current variables', + label: 'All variables', + origin: 'template', + value: '__all_variables', + }, + { + documentation: 'Adds current time range', + label: 'Time range', + origin: 'built-in', + value: '__url_time_range', + }, + { + documentation: "Adds current time range's from value", + label: 'Time range: from', + origin: 'built-in', + value: '__from', + }, + { + documentation: "Adds current time range's to value", + label: 'Time range: to', + origin: 'built-in', + value: '__to', + }, + ]); + }); +}); diff --git a/public/app/features/dashboard/components/PanelEditor/getVizualizationOptions.tsx b/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.tsx similarity index 83% rename from public/app/features/dashboard/components/PanelEditor/getVizualizationOptions.tsx rename to public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.tsx index 2a2a5ba2b58..be266ae7e9b 100644 --- a/public/app/features/dashboard/components/PanelEditor/getVizualizationOptions.tsx +++ b/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.tsx @@ -1,9 +1,15 @@ import React from 'react'; -import { StandardEditorContext, VariableSuggestionsScope } from '@grafana/data'; +import { + EventBus, + InterpolateFunction, + PanelData, + StandardEditorContext, + VariableSuggestionsScope, +} from '@grafana/data'; import { get as lodashGet } from 'lodash'; import { getDataLinksVariableSuggestions } from 'app/features/panel/panellinks/link_srv'; import { OptionPaneRenderProps } from './types'; -import { updateDefaultFieldConfigValue, setOptionImmutably } from './utils'; +import { setOptionImmutably, updateDefaultFieldConfigValue } from './utils'; import { OptionsPaneItemDescriptor } from './OptionsPaneItemDescriptor'; import { OptionsPaneCategoryDescriptor } from './OptionsPaneCategoryDescriptor'; import { @@ -16,22 +22,48 @@ import { getOptionOverrides } from './state/getOptionOverrides'; type categoryGetter = (categoryNames?: string[]) => OptionsPaneCategoryDescriptor; -export function getVizualizationOptions(props: OptionPaneRenderProps): OptionsPaneCategoryDescriptor[] { +interface GetStandardEditorContextProps { + data: PanelData | undefined; + replaceVariables: InterpolateFunction; + options: Record; + eventBus: EventBus; + instanceState: OptionPaneRenderProps['instanceState']; +} + +export function getStandardEditorContext({ + data, + replaceVariables, + options, + eventBus, + instanceState, +}: GetStandardEditorContextProps): StandardEditorContext { + const dataSeries = data?.series ?? []; + + const context: StandardEditorContext = { + data: dataSeries, + replaceVariables, + options, + eventBus, + getSuggestions: (scope?: VariableSuggestionsScope) => getDataLinksVariableSuggestions(dataSeries, scope), + instanceState, + }; + + return context; +} + +export function getVisualizationOptions(props: OptionPaneRenderProps): OptionsPaneCategoryDescriptor[] { const { plugin, panel, onPanelOptionsChanged, onFieldConfigsChange, data, dashboard, instanceState } = props; const currentOptions = panel.getOptions(); const currentFieldConfig = panel.fieldConfig; const categoryIndex: Record = {}; - const context: StandardEditorContext = { - data: data?.series || [], + const context = getStandardEditorContext({ + data, replaceVariables: panel.replaceVariables, options: currentOptions, eventBus: dashboard.events, - getSuggestions: (scope?: VariableSuggestionsScope) => { - return data ? getDataLinksVariableSuggestions(data.series, scope) : []; - }, instanceState, - }; + }); const getOptionsPaneCategory = (categoryNames?: string[]): OptionsPaneCategoryDescriptor => { const categoryName = (categoryNames && categoryNames[0]) ?? `${plugin.meta.name}`;