From 0d8d861b5b3e4cbaa485a75bccac30d0e5faf435 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Thu, 25 Nov 2021 05:25:36 -0500 Subject: [PATCH] VisualizationSuggestions: Support image & text instead of real previews. Adds suggestions for all non data panels when there are no data (#42074) (#42280) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Make suggestion cards support img & text mode instead of only preview * Generic solution for non data panels * minor review tweaks (cherry picked from commit 781067ee45dec0820826c0e6683ddb402a5b0850) Co-authored-by: Torkel Ödegaard --- packages/grafana-data/src/types/panel.ts | 12 +- .../PanelEditor/VisualizationSelectPane.tsx | 15 --- ...ew.tsx => VisualizationSuggestionCard.tsx} | 104 +++++++++++------- .../VisualizationSuggestions.tsx | 4 +- .../panel/state/getAllSuggestions.test.ts | 15 ++- .../features/panel/state/getAllSuggestions.ts | 24 +++- .../app/plugins/panel/barchart/suggestions.ts | 6 +- .../app/plugins/panel/bargauge/suggestions.ts | 1 - .../plugins/panel/candlestick/suggestions.ts | 1 - public/app/plugins/panel/dashlist/module.tsx | 4 +- .../app/plugins/panel/dashlist/suggestions.ts | 20 ---- public/app/plugins/panel/gauge/suggestions.ts | 10 +- public/app/plugins/panel/logs/suggestions.ts | 1 - .../app/plugins/panel/piechart/suggestions.ts | 8 +- public/app/plugins/panel/stat/suggestions.ts | 10 +- .../panel/state-timeline/suggestions.ts | 1 - .../panel/status-history/suggestions.ts | 6 +- public/app/plugins/panel/table/suggestions.ts | 19 +++- public/app/plugins/panel/text/module.tsx | 4 +- public/app/plugins/panel/text/suggestions.ts | 29 ----- .../plugins/panel/timeseries/suggestions.ts | 12 +- public/app/types/suggestions.ts | 2 +- 22 files changed, 163 insertions(+), 145 deletions(-) rename public/app/features/panel/components/VizTypePicker/{VisualizationPreview.tsx => VisualizationSuggestionCard.tsx} (54%) delete mode 100644 public/app/plugins/panel/dashlist/suggestions.ts delete mode 100644 public/app/plugins/panel/text/suggestions.ts diff --git a/packages/grafana-data/src/types/panel.ts b/packages/grafana-data/src/types/panel.ts index 71cf1cc4637..b5a0dbdd534 100644 --- a/packages/grafana-data/src/types/panel.ts +++ b/packages/grafana-data/src/types/panel.ts @@ -201,12 +201,20 @@ export interface VisualizationSuggestion { fieldConfig?: FieldConfigSource>; /** Data transformations */ transformations?: DataTransformerConfig[]; - /** Tweak for small preview */ - previewModifier?: (suggestion: VisualizationSuggestion) => void; + /** Options for how to render suggestion card */ + cardOptions?: { + /** Tweak for small preview */ + previewModifier?: (suggestion: VisualizationSuggestion) => void; + icon?: string; + imgSrc?: string; + }; /** A value between 0-100 how suitable suggestion is */ score?: VisualizationSuggestionScore; } +/** + * @alpha + */ export enum VisualizationSuggestionScore { /** We are pretty sure this is the best possible option */ Best = 100, diff --git a/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx b/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx index 0feb1ddf4d6..ca2e2e18f40 100644 --- a/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx +++ b/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx @@ -57,21 +57,6 @@ export const VisualizationSelectPane: FC = ({ panel, data }) => { dispatch(toggleVizPicker(false)); }; - // const onKeyPress = useCallback( - // (e: React.KeyboardEvent) => { - // if (e.key === 'Enter') { - // const query = e.currentTarget.value; - // const plugins = getAllPanelPluginMeta(); - // const match = filterPluginList(plugins, query, plugin.meta); - - // if (match && match.length) { - // onPluginTypeChange(match[0], false); - // } - // } - // }, - // [onPluginTypeChange, plugin.meta] - // ); - if (!plugin) { return null; } diff --git a/public/app/features/panel/components/VizTypePicker/VisualizationPreview.tsx b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestionCard.tsx similarity index 54% rename from public/app/features/panel/components/VizTypePicker/VisualizationPreview.tsx rename to public/app/features/panel/components/VizTypePicker/VisualizationSuggestionCard.tsx index 91a6d578776..3305e1f64a2 100644 --- a/public/app/features/panel/components/VizTypePicker/VisualizationPreview.tsx +++ b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestionCard.tsx @@ -1,11 +1,11 @@ import React, { CSSProperties } from 'react'; import { GrafanaTheme2, PanelData, VisualizationSuggestion } from '@grafana/data'; import { PanelRenderer } from '../PanelRenderer'; -import { css } from '@emotion/css'; +import { css, cx } from '@emotion/css'; import { Tooltip, useStyles2 } from '@grafana/ui'; import { VizTypeChangeDetails } from './types'; -import { cloneDeep } from 'lodash'; import { selectors } from '@grafana/e2e-selectors'; +import { cloneDeep } from 'lodash'; export interface Props { data: PanelData; @@ -15,50 +15,59 @@ export interface Props { onChange: (details: VizTypeChangeDetails) => void; } -export function VisualizationPreview({ data, suggestion, onChange, width, showTitle }: Props) { +export function VisualizationSuggestionCard({ data, suggestion, onChange, width, showTitle }: Props) { const styles = useStyles2(getStyles); const { innerStyles, outerStyles, renderWidth, renderHeight } = getPreviewDimensionsAndStyles(width); + const cardOptions = suggestion.cardOptions ?? {}; - const onClick = () => { - onChange({ - pluginId: suggestion.pluginId, - options: suggestion.options, - fieldConfig: suggestion.fieldConfig, - }); + const commonButtonProps = { + 'aria-label': suggestion.name, + className: styles.vizBox, + 'data-testid': selectors.components.VisualizationPreview.card(suggestion.name), + style: outerStyles, + onClick: () => { + onChange({ + pluginId: suggestion.pluginId, + options: suggestion.options, + fieldConfig: suggestion.fieldConfig, + }); + }, }; + if (cardOptions.imgSrc) { + return ( + + + + ); + } + let preview = suggestion; - if (suggestion.previewModifier) { + if (suggestion.cardOptions?.previewModifier) { preview = cloneDeep(suggestion); - suggestion.previewModifier(preview); + suggestion.cardOptions.previewModifier(preview); } return ( -
- {showTitle &&
{suggestion.name}
} - -
+ ); } @@ -77,8 +86,7 @@ const getStyles = (theme: GrafanaTheme2) => { background: none; border-radius: ${theme.shape.borderRadius(1)}; cursor: pointer; - text-align: left; - border: 1px solid ${theme.colors.border.strong}; + border: 1px solid ${theme.colors.border.medium}; transition: ${theme.transitions.create(['background'], { duration: theme.transitions.duration.short, @@ -88,7 +96,23 @@ const getStyles = (theme: GrafanaTheme2) => { background: ${theme.colors.background.secondary}; } `, + imgBox: css` + display: flex; + flex-direction: column; + height: 100%; + + justify-self: center; + color: ${theme.colors.text.primary}; + width: 100%; + display: flex; + + justify-content: center; + align-items: center; + text-align: center; + `, name: css` + padding-bottom: ${theme.spacing(0.5)}; + margin-top: ${theme.spacing(-1)}; font-size: ${theme.typography.bodySmall.fontSize}; white-space: nowrap; overflow: hidden; @@ -96,6 +120,10 @@ const getStyles = (theme: GrafanaTheme2) => { font-weight: ${theme.typography.fontWeightMedium}; text-overflow: ellipsis; `, + img: css` + max-width: ${theme.spacing(8)}; + max-height: ${theme.spacing(8)}; + `, renderContainer: css` position: absolute; transform-origin: left top; diff --git a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx index 22d45361707..f8ecb38c7c3 100644 --- a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx +++ b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx @@ -3,7 +3,7 @@ import { useStyles2 } from '@grafana/ui'; import { GrafanaTheme2, PanelData, PanelPluginMeta, PanelModel, VisualizationSuggestion } from '@grafana/data'; import { css } from '@emotion/css'; import { VizTypeChangeDetails } from './types'; -import { VisualizationPreview } from './VisualizationPreview'; +import { VisualizationSuggestionCard } from './VisualizationSuggestionCard'; import { getAllSuggestions } from '../../state/getAllSuggestions'; import { useAsync, useLocalStorage } from 'react-use'; import AutoSizer from 'react-virtualized-auto-sizer'; @@ -44,7 +44,7 @@ export function VisualizationSuggestions({ onChange, data, panel, searchQuery }:
{filteredSuggestions.map((suggestion, index) => ( - { ctx.setData([]); it('should return correct suggestions', () => { - expect(ctx.names()).toEqual([SuggestionName.Table, SuggestionName.TextPanel, SuggestionName.DashboardList]); + expect(ctx.names()).toEqual([SuggestionName.Table, SuggestionName.TextPanel]); }); }); @@ -73,7 +84,7 @@ scenario('No rows', (ctx) => { ]); it('should return correct suggestions', () => { - expect(ctx.names()).toEqual([SuggestionName.Table, SuggestionName.TextPanel, SuggestionName.DashboardList]); + expect(ctx.names()).toEqual([SuggestionName.Table]); }); }); diff --git a/public/app/features/panel/state/getAllSuggestions.ts b/public/app/features/panel/state/getAllSuggestions.ts index c70d7508674..aaecfdf7d0c 100644 --- a/public/app/features/panel/state/getAllSuggestions.ts +++ b/public/app/features/panel/state/getAllSuggestions.ts @@ -5,6 +5,7 @@ import { PanelModel, VisualizationSuggestionScore, } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { importPanelPlugin } from 'app/features/plugins/importPanelPlugin'; export const panelsToCheckFirst = [ @@ -17,8 +18,6 @@ export const panelsToCheckFirst = [ 'table', 'state-timeline', 'status-history', - 'text', - 'dashlist', 'logs', 'candlestick', ]; @@ -35,7 +34,26 @@ export async function getAllSuggestions(data?: PanelData, panel?: PanelModel): P } } - return builder.getList().sort((a, b) => { + const list = builder.getList(); + + if (builder.dataSummary.fieldCount === 0) { + for (const plugin of Object.values(config.panels)) { + if (!plugin.skipDataQuery || plugin.hideFromList) { + continue; + } + + list.push({ + name: plugin.name, + pluginId: plugin.id, + description: plugin.info.description, + cardOptions: { + imgSrc: plugin.info.logos.small, + }, + }); + } + } + + return list.sort((a, b) => { return (b.score ?? VisualizationSuggestionScore.OK) - (a.score ?? VisualizationSuggestionScore.OK); }); } diff --git a/public/app/plugins/panel/barchart/suggestions.ts b/public/app/plugins/panel/barchart/suggestions.ts index 1fab84d8502..2264328b186 100644 --- a/public/app/plugins/panel/barchart/suggestions.ts +++ b/public/app/plugins/panel/barchart/suggestions.ts @@ -22,8 +22,10 @@ export class BarChartSuggestionsSupplier { }, overrides: [], }, - previewModifier: (s) => { - s.options!.barWidth = 0.8; + cardOptions: { + previewModifier: (s) => { + s.options!.barWidth = 0.8; + }, }, }); } diff --git a/public/app/plugins/panel/bargauge/suggestions.ts b/public/app/plugins/panel/bargauge/suggestions.ts index dfedcfff77c..be937485b4f 100644 --- a/public/app/plugins/panel/bargauge/suggestions.ts +++ b/public/app/plugins/panel/bargauge/suggestions.ts @@ -21,7 +21,6 @@ export class BarGaugeSuggestionsSupplier { }, overrides: [], }, - previewModifier: (s) => {}, }); // This is probably not a good option for many numeric fields diff --git a/public/app/plugins/panel/candlestick/suggestions.ts b/public/app/plugins/panel/candlestick/suggestions.ts index 1aaa385ae92..19b304e6f3f 100644 --- a/public/app/plugins/panel/candlestick/suggestions.ts +++ b/public/app/plugins/panel/candlestick/suggestions.ts @@ -38,7 +38,6 @@ export class CandlestickSuggestionsSupplier { }, overrides: [], }, - previewModifier: (s) => {}, }); list.append({ diff --git a/public/app/plugins/panel/dashlist/module.tsx b/public/app/plugins/panel/dashlist/module.tsx index 3cd29118bbb..f57f224f948 100644 --- a/public/app/plugins/panel/dashlist/module.tsx +++ b/public/app/plugins/panel/dashlist/module.tsx @@ -8,7 +8,6 @@ import { GENERAL_FOLDER, ReadonlyFolderPicker, } from '../../../core/components/Select/ReadonlyFolderPicker/ReadonlyFolderPicker'; -import { DashListSuggestionsSupplier } from './suggestions'; export const plugin = new PanelPlugin(DashList) .setPanelOptions((builder) => { @@ -88,5 +87,4 @@ export const plugin = new PanelPlugin(DashList) } return newOptions; - }) - .setSuggestionsSupplier(new DashListSuggestionsSupplier()); + }); diff --git a/public/app/plugins/panel/dashlist/suggestions.ts b/public/app/plugins/panel/dashlist/suggestions.ts deleted file mode 100644 index 439ce963859..00000000000 --- a/public/app/plugins/panel/dashlist/suggestions.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { VisualizationSuggestionsBuilder } from '@grafana/data'; -import { PanelOptions } from './models.gen'; - -export class DashListSuggestionsSupplier { - getSuggestionsForData(builder: VisualizationSuggestionsBuilder) { - const { dataSummary } = builder; - - if (dataSummary.hasData) { - return; - } - - const list = builder.getListAppender({ - name: 'Dashboard list', - pluginId: 'dashlist', - options: {}, - }); - - list.append({}); - } -} diff --git a/public/app/plugins/panel/gauge/suggestions.ts b/public/app/plugins/panel/gauge/suggestions.ts index 8ea16c01fd0..5863509b416 100644 --- a/public/app/plugins/panel/gauge/suggestions.ts +++ b/public/app/plugins/panel/gauge/suggestions.ts @@ -33,10 +33,12 @@ export class GaugeSuggestionsSupplier { }, overrides: [], }, - previewModifier: (s) => { - if (s.options!.reduceOptions.values) { - s.options!.reduceOptions.limit = 2; - } + cardOptions: { + previewModifier: (s) => { + if (s.options!.reduceOptions.values) { + s.options!.reduceOptions.limit = 2; + } + }, }, }); diff --git a/public/app/plugins/panel/logs/suggestions.ts b/public/app/plugins/panel/logs/suggestions.ts index a87169b058c..5a0562a4296 100644 --- a/public/app/plugins/panel/logs/suggestions.ts +++ b/public/app/plugins/panel/logs/suggestions.ts @@ -14,7 +14,6 @@ export class LogsPanelSuggestionsSupplier { }, overrides: [], }, - previewModifier: () => {}, }); const { dataSummary: ds } = builder; diff --git a/public/app/plugins/panel/piechart/suggestions.ts b/public/app/plugins/panel/piechart/suggestions.ts index f6ded5ddcd7..aa3f0b055cc 100644 --- a/public/app/plugins/panel/piechart/suggestions.ts +++ b/public/app/plugins/panel/piechart/suggestions.ts @@ -19,9 +19,11 @@ export class PieChartSuggestionsSupplier { values: [], } as any, }, - previewModifier: (s) => { - // Hide labels in preview - s.options!.legend.displayMode = LegendDisplayMode.Hidden; + cardOptions: { + previewModifier: (s) => { + // Hide labels in preview + s.options!.legend.displayMode = LegendDisplayMode.Hidden; + }, }, }); diff --git a/public/app/plugins/panel/stat/suggestions.ts b/public/app/plugins/panel/stat/suggestions.ts index 08109e68a4e..b94620205d4 100644 --- a/public/app/plugins/panel/stat/suggestions.ts +++ b/public/app/plugins/panel/stat/suggestions.ts @@ -22,10 +22,12 @@ export class StatSuggestionsSupplier { }, overrides: [], }, - previewModifier: (s) => { - if (s.options!.reduceOptions.values) { - s.options!.reduceOptions.limit = 1; - } + cardOptions: { + previewModifier: (s) => { + if (s.options!.reduceOptions.values) { + s.options!.reduceOptions.limit = 1; + } + }, }, }); diff --git a/public/app/plugins/panel/state-timeline/suggestions.ts b/public/app/plugins/panel/state-timeline/suggestions.ts index 0fe8774b4b9..1fe768bf381 100644 --- a/public/app/plugins/panel/state-timeline/suggestions.ts +++ b/public/app/plugins/panel/state-timeline/suggestions.ts @@ -35,7 +35,6 @@ export class StatTimelineSuggestionsSupplier { }, overrides: [], }, - previewModifier: (s) => {}, }); list.append({ name: SuggestionName.StateTimeline }); diff --git a/public/app/plugins/panel/status-history/suggestions.ts b/public/app/plugins/panel/status-history/suggestions.ts index ed4c07ae6d1..cac89f49d8c 100644 --- a/public/app/plugins/panel/status-history/suggestions.ts +++ b/public/app/plugins/panel/status-history/suggestions.ts @@ -43,8 +43,10 @@ export class StatusHistorySuggestionsSupplier { }, overrides: [], }, - previewModifier: (s) => { - s.options!.colWidth = 0.7; + cardOptions: { + previewModifier: (s) => { + s.options!.colWidth = 0.7; + }, }, }); diff --git a/public/app/plugins/panel/table/suggestions.ts b/public/app/plugins/panel/table/suggestions.ts index 1ddb7cd8690..3294de7839d 100644 --- a/public/app/plugins/panel/table/suggestions.ts +++ b/public/app/plugins/panel/table/suggestions.ts @@ -6,7 +6,7 @@ import { PanelOptions } from './models.gen'; export class TableSuggestionsSupplier { getSuggestionsForData(builder: VisualizationSuggestionsBuilder) { const list = builder.getListAppender({ - name: '', + name: SuggestionName.Table, pluginId: 'table', options: {}, fieldConfig: { @@ -15,9 +15,22 @@ export class TableSuggestionsSupplier { }, overrides: [], }, - previewModifier: (s) => {}, + cardOptions: { + previewModifier: (s) => { + s.fieldConfig!.defaults.custom!.minWidth = 50; + }, + }, }); - list.append({ name: SuggestionName.Table }); + // If there are not data suggest table anyway but use icon instead of real preview + if (builder.dataSummary.fieldCount === 0) { + list.append({ + cardOptions: { + imgSrc: 'public/app/plugins/panel/table/img/icn-table-panel.svg', + }, + }); + } else { + list.append({}); + } } } diff --git a/public/app/plugins/panel/text/module.tsx b/public/app/plugins/panel/text/module.tsx index 1d5532c2653..f30a4492ca7 100644 --- a/public/app/plugins/panel/text/module.tsx +++ b/public/app/plugins/panel/text/module.tsx @@ -4,7 +4,6 @@ import { TextPanel } from './TextPanel'; import { textPanelMigrationHandler } from './textPanelMigrationHandler'; import { TextPanelEditor } from './TextPanelEditor'; import { defaultPanelOptions, PanelOptions, TextMode } from './models.gen'; -import { TextPanelSuggestionSupplier } from './suggestions'; export const plugin = new PanelPlugin(TextPanel) .setPanelOptions((builder) => { @@ -30,5 +29,4 @@ export const plugin = new PanelPlugin(TextPanel) defaultValue: defaultPanelOptions.content, }); }) - .setMigrationHandler(textPanelMigrationHandler) - .setSuggestionsSupplier(new TextPanelSuggestionSupplier()); + .setMigrationHandler(textPanelMigrationHandler); diff --git a/public/app/plugins/panel/text/suggestions.ts b/public/app/plugins/panel/text/suggestions.ts deleted file mode 100644 index 2ee2cb98462..00000000000 --- a/public/app/plugins/panel/text/suggestions.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { VisualizationSuggestionsBuilder } from '@grafana/data'; -import { PanelOptions } from './models.gen'; - -export class TextPanelSuggestionSupplier { - getSuggestionsForData(builder: VisualizationSuggestionsBuilder) { - const { dataSummary } = builder; - - if (dataSummary.hasData) { - return; - } - - const list = builder.getListAppender({ - name: 'Text panel', - pluginId: 'text', - options: { - content: ` -# Title - -For markdown syntax help: [commonmark.org/help](https://commonmark.org/help/) - -* First item -* Second item -* Third item`, - }, - }); - - list.append({}); - } -} diff --git a/public/app/plugins/panel/timeseries/suggestions.ts b/public/app/plugins/panel/timeseries/suggestions.ts index e8dbbdcb3aa..72d1c020c11 100644 --- a/public/app/plugins/panel/timeseries/suggestions.ts +++ b/public/app/plugins/panel/timeseries/suggestions.ts @@ -30,12 +30,14 @@ export class TimeSeriesSuggestionsSupplier { }, overrides: [], }, - previewModifier: (s) => { - s.options!.legend.displayMode = LegendDisplayMode.Hidden; + cardOptions: { + previewModifier: (s) => { + s.options!.legend.displayMode = LegendDisplayMode.Hidden; - if (s.fieldConfig?.defaults.custom?.drawStyle !== GraphDrawStyle.Bars) { - s.fieldConfig!.defaults.custom!.lineWidth = Math.max(s.fieldConfig!.defaults.custom!.lineWidth ?? 1, 2); - } + if (s.fieldConfig?.defaults.custom?.drawStyle !== GraphDrawStyle.Bars) { + s.fieldConfig!.defaults.custom!.lineWidth = Math.max(s.fieldConfig!.defaults.custom!.lineWidth ?? 1, 2); + } + }, }, }); diff --git a/public/app/types/suggestions.ts b/public/app/types/suggestions.ts index 9727d9344fd..22889b632d9 100644 --- a/public/app/types/suggestions.ts +++ b/public/app/types/suggestions.ts @@ -24,7 +24,7 @@ export enum SuggestionName { Table = 'Table', StateTimeline = 'State timeline', StatusHistory = 'Status history', - TextPanel = 'Text panel', + TextPanel = 'Text', DashboardList = 'Dashboard list', Logs = 'Logs', }