diff --git a/.betterer.results b/.betterer.results index 39b77490211..e4f370926f8 100644 --- a/.betterer.results +++ b/.betterer.results @@ -2636,6 +2636,9 @@ exports[`better eslint`] = { "public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], + "public/app/features/dashboard-scene/utils/PanelModelCompatibilityWrapper.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], "public/app/features/dashboard-scene/utils/test-utils.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], diff --git a/public/app/features/dashboard-scene/panel-edit/PanelOptionsPane.tsx b/public/app/features/dashboard-scene/panel-edit/PanelOptionsPane.tsx index 2e2dfe3109e..b73fd69f04b 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelOptionsPane.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelOptionsPane.tsx @@ -51,8 +51,7 @@ export class PanelOptionsPane extends SceneObjectBase { const { panelManager } = model; const { panel } = panelManager.state; const dataObject = sceneGraph.getData(panel); - const rawData = dataObject.useState(); - const dataWithFieldConfig = panel.applyFieldConfig(rawData.data!); + const { data } = dataObject.useState(); const { pluginId, options, fieldConfig } = panel.useState(); const styles = useStyles2(getStyles); const panelFrameOptions = useMemo(() => getPanelFrameCategory2(panel), [panel]); @@ -77,7 +76,7 @@ export class PanelOptionsPane extends SceneObjectBase { getFieldOverrideCategories( fieldConfig, panel.getPlugin()?.fieldConfigRegistry!, - dataWithFieldConfig.series, + data?.series ?? [], searchQuery, (newConfig) => { panel.setState({ @@ -135,7 +134,9 @@ export class PanelOptionsPane extends SceneObjectBase { /> )} - {isVizPickerOpen && } + {isVizPickerOpen && ( + + )} {!isVizPickerOpen && ( <>
diff --git a/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx b/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx index 6dd2ad98e78..56d1ae0468d 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx @@ -1,26 +1,52 @@ import { css } from '@emotion/css'; -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; +import { useLocalStorage } from 'react-use'; -import { GrafanaTheme2 } from '@grafana/data'; -import { SceneObjectState } from '@grafana/scenes'; -import { CustomScrollbar, FilterInput, useStyles2 } from '@grafana/ui'; +import { GrafanaTheme2, PanelData, SelectableValue } from '@grafana/data'; +import { CustomScrollbar, Field, FilterInput, RadioButtonGroup, useStyles2 } from '@grafana/ui'; +import { LS_VISUALIZATION_SELECT_TAB_KEY, LS_WIDGET_SELECT_TAB_KEY } from 'app/core/constants'; +import { VisualizationSelectPaneTab } from 'app/features/dashboard/components/PanelEditor/types'; +import { VisualizationSuggestions } from 'app/features/panel/components/VizTypePicker/VisualizationSuggestions'; import { VizTypePicker } from 'app/features/panel/components/VizTypePicker/VizTypePicker'; +import { VizTypeChangeDetails } from 'app/features/panel/components/VizTypePicker/types'; + +import { PanelModelCompatibilityWrapper } from '../utils/PanelModelCompatibilityWrapper'; import { VizPanelManager } from './VizPanelManager'; -export interface PanelVizTypePickerState extends SceneObjectState {} - -export function PanelVizTypePicker({ - panelManager, - onChange, -}: { +export interface Props { + data?: PanelData; panelManager: VizPanelManager; onChange: () => void; -}) { +} + +export function PanelVizTypePicker({ panelManager, data, onChange }: Props) { const { panel } = panelManager.useState(); const styles = useStyles2(getStyles); const [searchQuery, setSearchQuery] = useState(''); + const isWidgetEnabled = false; + const tabKey = isWidgetEnabled ? LS_WIDGET_SELECT_TAB_KEY : LS_VISUALIZATION_SELECT_TAB_KEY; + const defaultTab = isWidgetEnabled ? VisualizationSelectPaneTab.Widgets : VisualizationSelectPaneTab.Visualizations; + const panelModel = useMemo(() => new PanelModelCompatibilityWrapper(panel), [panel]); + + const [listMode, setListMode] = useLocalStorage(tabKey, defaultTab); + + const radioOptions: Array> = [ + { label: 'Visualizations', value: VisualizationSelectPaneTab.Visualizations }, + { label: 'Suggestions', value: VisualizationSelectPaneTab.Suggestions }, + // { + // label: 'Library panels', + // value: VisualizationSelectPaneTab.LibraryPanels, + // description: 'Reusable panels you can share between multiple dashboards.', + // }, + ]; + + const onVizTypeChange = (options: VizTypeChangeDetails) => { + panelManager.changePluginType(options.pluginId); + onChange(); + }; + return (
- - { - panelManager.changePluginType(options.pluginId); - onChange(); - }} - /> + + + + + {listMode === VisualizationSelectPaneTab.Visualizations && ( + + )} + {/* {listMode === VisualizationSelectPaneTab.Widgets && ( + + )} */} + {listMode === VisualizationSelectPaneTab.Suggestions && ( + + )}
); @@ -57,6 +92,9 @@ const getStyles = (theme: GrafanaTheme2) => ({ borderBottom: 'none', borderTopLeftRadius: theme.shape.radius.default, }), + customFieldMargin: css({ + marginBottom: theme.spacing(1), + }), filter: css({ minHeight: theme.spacing(4), }), diff --git a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts index b352806381c..4e4b6c12ed2 100644 --- a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts +++ b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts @@ -5,7 +5,6 @@ import { TimeRangeUpdatedEvent } from '@grafana/runtime'; import { behaviors, SceneDataLayers, - SceneDataTransformer, sceneGraph, SceneGridItem, SceneGridLayout, @@ -13,14 +12,13 @@ import { SceneObject, VizPanel, } from '@grafana/scenes'; -import { DataSourceRef } from '@grafana/schema'; import { DashboardScene } from '../scene/DashboardScene'; -import { LibraryVizPanel } from '../scene/LibraryVizPanel'; import { dataLayersToAnnotations } from '../serialization/dataLayersToAnnotations'; +import { PanelModelCompatibilityWrapper } from './PanelModelCompatibilityWrapper'; import { dashboardSceneGraph } from './dashboardSceneGraph'; -import { findVizPanelByKey, getPanelIdForVizPanel, getQueryRunnerFor, getVizPanelKeyForPanelId } from './utils'; +import { findVizPanelByKey, getVizPanelKeyForPanelId } from './utils'; /** * Will move this to make it the main way we remain somewhat compatible with getDashboardSrv().getCurrent @@ -107,7 +105,7 @@ export class DashboardModelCompatibilityWrapper { const panels = findAllObjects(this._scene, (o) => { return Boolean(o instanceof VizPanel); }); - return panels.map((p) => new PanelCompatibilityWrapper(p as VizPanel)); + return panels.map((p) => new PanelModelCompatibilityWrapper(p as VizPanel)); } /** @@ -159,10 +157,10 @@ export class DashboardModelCompatibilityWrapper { }); } - public getPanelById(id: number): PanelCompatibilityWrapper | null { + public getPanelById(id: number): PanelModelCompatibilityWrapper | null { const vizPanel = findVizPanelByKey(this._scene, getVizPanelKeyForPanelId(id)); if (vizPanel) { - return new PanelCompatibilityWrapper(vizPanel); + return new PanelModelCompatibilityWrapper(vizPanel); } return null; @@ -171,7 +169,7 @@ export class DashboardModelCompatibilityWrapper { /** * Mainly implemented to support Getting started panel's dissmis button. */ - public removePanel(panel: PanelCompatibilityWrapper) { + public removePanel(panel: PanelModelCompatibilityWrapper) { const vizPanel = findVizPanelByKey(this._scene, getVizPanelKeyForPanelId(panel.id)); if (!vizPanel) { console.error('Trying to remove a panel that was not found in scene', panel); @@ -237,65 +235,6 @@ export class DashboardModelCompatibilityWrapper { } } -class PanelCompatibilityWrapper { - constructor(private _vizPanel: VizPanel) {} - - public get id() { - const id = getPanelIdForVizPanel( - this._vizPanel.parent instanceof LibraryVizPanel ? this._vizPanel.parent : this._vizPanel - ); - - if (isNaN(id)) { - console.error('VizPanel key could not be translated to a legacy numeric panel id', this._vizPanel); - return 0; - } - - return id; - } - - public get type() { - return this._vizPanel.state.pluginId; - } - - public get title() { - return this._vizPanel.state.title; - } - - public get transformations() { - if (this._vizPanel.state.$data instanceof SceneDataTransformer) { - return this._vizPanel.state.$data.state.transformations; - } - - return []; - } - - public get targets() { - const queryRunner = getQueryRunnerFor(this._vizPanel); - if (!queryRunner) { - return []; - } - - return queryRunner.state.queries; - } - - public get datasource(): DataSourceRef | null | undefined { - const queryRunner = getQueryRunnerFor(this._vizPanel); - return queryRunner?.state.datasource; - } - - public refresh() { - console.error('Scenes PanelCompatibilityWrapper.refresh no implemented (yet)'); - } - - public render() { - console.error('Scenes PanelCompatibilityWrapper.render no implemented (yet)'); - } - - public getQueryRunner() { - console.error('Scenes PanelCompatibilityWrapper.getQueryRunner no implemented (yet)'); - } -} - function findAllObjects(root: SceneObject, check: (o: SceneObject) => boolean) { let result: SceneObject[] = []; root.forEachChild((child) => { diff --git a/public/app/features/dashboard-scene/utils/PanelModelCompatibilityWrapper.ts b/public/app/features/dashboard-scene/utils/PanelModelCompatibilityWrapper.ts new file mode 100644 index 00000000000..a23b3081a51 --- /dev/null +++ b/public/app/features/dashboard-scene/utils/PanelModelCompatibilityWrapper.ts @@ -0,0 +1,70 @@ +import { PanelModel } from '@grafana/data'; +import { SceneDataTransformer, VizPanel } from '@grafana/scenes'; +import { DataSourceRef, DataTransformerConfig } from '@grafana/schema'; + +import { LibraryVizPanel } from '../scene/LibraryVizPanel'; + +import { getPanelIdForVizPanel, getQueryRunnerFor } from './utils'; + +export class PanelModelCompatibilityWrapper implements PanelModel { + constructor(private _vizPanel: VizPanel) {} + + public get id() { + const id = getPanelIdForVizPanel( + this._vizPanel.parent instanceof LibraryVizPanel ? this._vizPanel.parent : this._vizPanel + ); + + if (isNaN(id)) { + console.error('VizPanel key could not be translated to a legacy numeric panel id', this._vizPanel); + return 0; + } + + return id; + } + + public get description() { + return this._vizPanel.state.description; + } + + public get type() { + return this._vizPanel.state.pluginId; + } + + public get title() { + return this._vizPanel.state.title; + } + + public get transformations() { + if (this._vizPanel.state.$data instanceof SceneDataTransformer) { + return this._vizPanel.state.$data.state.transformations as DataTransformerConfig[]; + } + + return []; + } + + public get targets() { + const queryRunner = getQueryRunnerFor(this._vizPanel); + if (!queryRunner) { + return []; + } + + return queryRunner.state.queries; + } + + public get datasource(): DataSourceRef | null | undefined { + const queryRunner = getQueryRunnerFor(this._vizPanel); + return queryRunner?.state.datasource; + } + + public get options() { + return this._vizPanel.state.options; + } + + public get fieldConfig() { + return this._vizPanel.state.fieldConfig; + } + + public get pluginVersion() { + return this._vizPanel.state.pluginVersion; + } +} diff --git a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx index 0a64a096ab1..94e8a9aaa9f 100644 --- a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx +++ b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx @@ -24,39 +24,43 @@ export function VisualizationSuggestions({ searchQuery, onChange, data, panel }: const filteredSuggestions = filterSuggestionsBySearch(searchQuery, suggestions); return ( - - {({ width }) => { - if (!width) { - return null; - } + // This div is needed in some places to make AutoSizer work +
+ + {({ width }) => { + if (!width) { + return null; + } - const columnCount = Math.floor(width / 170); - const spaceBetween = 8 * (columnCount! - 1); - const previewWidth = (width - spaceBetween) / columnCount!; + width = width - 1; + const columnCount = Math.floor(width / 200); + const spaceBetween = 8 * (columnCount! - 1); + const previewWidth = Math.floor((width - spaceBetween) / columnCount!); - return ( -
-
-
Based on current data
+ return ( +
+
+
Based on current data
+
+
+ {filteredSuggestions.map((suggestion, index) => ( + + ))} + {searchQuery && filteredSuggestions.length === 0 && ( +
No results matched your query
+ )} +
-
- {filteredSuggestions.map((suggestion, index) => ( - - ))} - {searchQuery && filteredSuggestions.length === 0 && ( -
No results matched your query
- )} -
-
- ); - }} - + ); + }} + +
); }