From f6bd390bc18ac43a90739d541772bad5387879e2 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Thu, 7 Dec 2023 10:25:06 +0100 Subject: [PATCH] DashboardScene: Skeleton for Panel edit data tabs (#79130) --- .../PanelDataPane/PanelDataAlertingTab.tsx | 20 +++ .../PanelDataPane/PanelDataPane.tsx | 128 ++++++++++++++++++ .../PanelDataPane/PanelDataQueriesTab.tsx | 20 +++ .../PanelDataTransformationsTab.tsx | 20 +++ .../panel-edit/PanelDataPane/types.ts | 10 ++ .../panel-edit/PanelEditor.tsx | 12 +- .../components/PanelEditor/state/selectors.ts | 20 +-- 7 files changed, 220 insertions(+), 10 deletions(-) create mode 100644 public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.tsx create mode 100644 public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx create mode 100644 public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx create mode 100644 public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.tsx create mode 100644 public/app/features/dashboard-scene/panel-edit/PanelDataPane/types.ts diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.tsx new file mode 100644 index 00000000000..a3a54960bc4 --- /dev/null +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +import { IconName } from '@grafana/data'; +import { SceneObjectBase, SceneComponentProps } from '@grafana/scenes'; + +import { PanelDataPaneTabState, PanelDataPaneTab } from './types'; + +export class PanelDataAlertingTab extends SceneObjectBase implements PanelDataPaneTab { + static Component = PanelDataAlertingTabRendered; + tabId = 'alert'; + icon: IconName = 'bell'; + + getTabLabel() { + return 'Alert'; + } +} + +function PanelDataAlertingTabRendered(props: SceneComponentProps) { + return
TODO Alerting
; +} diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx new file mode 100644 index 00000000000..a71b20f7a02 --- /dev/null +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx @@ -0,0 +1,128 @@ +import React from 'react'; + +import { + SceneComponentProps, + SceneObjectBase, + SceneObjectRef, + SceneObjectState, + SceneObjectUrlSyncConfig, + SceneObjectUrlValues, + VizPanel, +} from '@grafana/scenes'; +import { Tab, TabContent, TabsBar } from '@grafana/ui'; +import { shouldShowAlertingTab } from 'app/features/dashboard/components/PanelEditor/state/selectors'; + +import { PanelDataAlertingTab } from './PanelDataAlertingTab'; +import { PanelDataQueriesTab } from './PanelDataQueriesTab'; +import { PanelDataTransformationsTab } from './PanelDataTransformationsTab'; +import { PanelDataPaneTab } from './types'; + +export interface PanelDataPaneState extends SceneObjectState { + panelRef: SceneObjectRef; + tabs?: PanelDataPaneTab[]; + tab?: string; +} + +export class PanelDataPane extends SceneObjectBase { + static Component = PanelDataPaneRendered; + protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: ['tab'] }); + + getUrlState() { + return { + tab: this.state.tab, + }; + } + + updateFromUrl(values: SceneObjectUrlValues) { + if (!values.tab) { + return; + } + if (typeof values.tab === 'string') { + this.setState({ tab: values.tab }); + } + } + + constructor(state: Omit & { tab?: string }) { + super({ + tab: 'queries', + ...state, + }); + + const { panelRef } = this.state; + const panel = panelRef.resolve(); + + if (panel) { + // The subscription below is needed because the plugin may not be loaded when this pane is mounted. + // This can happen i.e. when the user opens the panel editor directly via an URL. + this._subs.add( + panel.subscribeToState((n, p) => { + if (n.pluginVersion || p.pluginId !== n.pluginId) { + this.buildTabs(); + } + }) + ); + } + + this.addActivationHandler(() => this.buildTabs()); + } + + private buildTabs() { + const { panelRef } = this.state; + const tabs: PanelDataPaneTab[] = []; + + if (panelRef) { + const plugin = panelRef.resolve().getPlugin(); + if (!plugin) { + this.setState({ tabs }); + return; + } + if (plugin.meta.skipDataQuery) { + this.setState({ tabs }); + return; + } else { + tabs.push(new PanelDataQueriesTab({})); + tabs.push(new PanelDataTransformationsTab({})); + + if (shouldShowAlertingTab(plugin)) { + tabs.push(new PanelDataAlertingTab({})); + } + } + } + + this.setState({ tabs }); + } + + onChangeTab = (tab: PanelDataPaneTab) => { + this.setState({ tab: tab.tabId }); + }; +} + +function PanelDataPaneRendered({ model }: SceneComponentProps) { + const { tab, tabs } = model.useState(); + + if (!tabs) { + return; + } + + const currentTab = tabs.find((t) => t.tabId === tab); + + return ( +
+ + {tabs.map((t, index) => { + return ( + model.onChangeTab(t)} + /> + ); + })} + + {currentTab && } +
+ ); +} diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx new file mode 100644 index 00000000000..7fd6a5c6703 --- /dev/null +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +import { IconName } from '@grafana/data'; +import { SceneObjectBase, SceneComponentProps } from '@grafana/scenes'; + +import { PanelDataPaneTabState, PanelDataPaneTab } from './types'; + +export class PanelDataQueriesTab extends SceneObjectBase implements PanelDataPaneTab { + static Component = PanelDataQueriesTabRendered; + tabId = 'queries'; + icon: IconName = 'database'; + + getTabLabel() { + return 'Queries'; + } +} + +function PanelDataQueriesTabRendered(props: SceneComponentProps) { + return
TODO Queries
; +} diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.tsx new file mode 100644 index 00000000000..9bf9a76a22c --- /dev/null +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +import { IconName } from '@grafana/data'; +import { SceneObjectBase, SceneComponentProps } from '@grafana/scenes'; + +import { PanelDataPaneTabState, PanelDataPaneTab } from './types'; + +export class PanelDataTransformationsTab extends SceneObjectBase implements PanelDataPaneTab { + static Component = PanelDataTransformationsTabRendered; + tabId = 'transformations'; + icon: IconName = 'process'; + + getTabLabel() { + return 'Transformations'; + } +} + +function PanelDataTransformationsTabRendered(props: SceneComponentProps) { + return
TODO Transformations
; +} diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/types.ts b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/types.ts new file mode 100644 index 00000000000..851820c5065 --- /dev/null +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/types.ts @@ -0,0 +1,10 @@ +import { IconName } from '@grafana/data'; +import { SceneObject, SceneObjectState } from '@grafana/scenes'; + +export interface PanelDataPaneTabState extends SceneObjectState {} + +export interface PanelDataPaneTab extends SceneObject { + getTabLabel(): string; + tabId: string; + icon: IconName; +} diff --git a/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx b/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx index 278982ecd16..f9b824471b8 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx @@ -4,6 +4,7 @@ import { NavIndex } from '@grafana/data'; import { locationService } from '@grafana/runtime'; import { getUrlSyncManager, + SceneFlexItem, SceneFlexLayout, SceneObject, SceneObjectBase, @@ -17,6 +18,7 @@ import { import { DashboardScene } from '../scene/DashboardScene'; import { getDashboardUrl } from '../utils/urlBuilders'; +import { PanelDataPane } from './PanelDataPane/PanelDataPane'; import { PanelEditorRenderer } from './PanelEditorRenderer'; import { PanelOptionsPane } from './PanelOptionsPane'; import { PanelVizTypePicker } from './PanelVizTypePicker'; @@ -124,9 +126,15 @@ export function buildPanelEditScene(dashboard: DashboardScene, panel: VizPanel): $timeRange: dashboardStateCloned.$timeRange, body: new SplitLayout({ direction: 'row', - primary: new SceneFlexLayout({ + primary: new SplitLayout({ direction: 'column', - children: [vizPanelMgr], + primary: new SceneFlexLayout({ + direction: 'column', + children: [panelClone], + }), + secondary: new SceneFlexItem({ + body: new PanelDataPane({ panelRef: panelClone.getRef() }), + }), }), secondary: new SceneFlexLayout({ direction: 'column', diff --git a/public/app/features/dashboard/components/PanelEditor/state/selectors.ts b/public/app/features/dashboard/components/PanelEditor/state/selectors.ts index 02bde1dbaff..a8f71b34ca6 100644 --- a/public/app/features/dashboard/components/PanelEditor/state/selectors.ts +++ b/public/app/features/dashboard/components/PanelEditor/state/selectors.ts @@ -39,14 +39,7 @@ export const getPanelEditorTabs = memoizeOne((tab?: string, plugin?: PanelPlugin }); } - const { alertingEnabled, unifiedAlertingEnabled } = getConfig(); - const hasRuleReadPermissions = contextSrv.hasPermission(getRulesPermissions(GRAFANA_RULES_SOURCE_NAME).read); - const isAlertingAvailable = alertingEnabled || (unifiedAlertingEnabled && hasRuleReadPermissions); - - const isGraph = plugin.meta.id === 'graph'; - const isTimeseries = plugin.meta.id === 'timeseries'; - - if ((isAlertingAvailable && isGraph) || isTimeseries) { + if (shouldShowAlertingTab(plugin)) { tabs.push({ id: PanelEditorTabId.Alert, text: 'Alert', @@ -60,3 +53,14 @@ export const getPanelEditorTabs = memoizeOne((tab?: string, plugin?: PanelPlugin return tabs; }); + +export function shouldShowAlertingTab(plugin: PanelPlugin) { + const { alertingEnabled, unifiedAlertingEnabled } = getConfig(); + const hasRuleReadPermissions = contextSrv.hasPermission(getRulesPermissions(GRAFANA_RULES_SOURCE_NAME).read); + const isAlertingAvailable = alertingEnabled || (unifiedAlertingEnabled && hasRuleReadPermissions); + + const isGraph = plugin.meta.id === 'graph'; + const isTimeseries = plugin.meta.id === 'timeseries'; + + return (isAlertingAvailable && isGraph) || isTimeseries; +}