DashboardScene: Skeleton for Panel edit data tabs (#79130)

This commit is contained in:
Dominik Prokop 2023-12-07 10:25:06 +01:00 committed by GitHub
parent dfc139b2ff
commit f6bd390bc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 220 additions and 10 deletions

View File

@ -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<PanelDataPaneTabState> implements PanelDataPaneTab {
static Component = PanelDataAlertingTabRendered;
tabId = 'alert';
icon: IconName = 'bell';
getTabLabel() {
return 'Alert';
}
}
function PanelDataAlertingTabRendered(props: SceneComponentProps<PanelDataAlertingTab>) {
return <div>TODO Alerting</div>;
}

View File

@ -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<VizPanel>;
tabs?: PanelDataPaneTab[];
tab?: string;
}
export class PanelDataPane extends SceneObjectBase<PanelDataPaneState> {
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<PanelDataPaneState, 'tab'> & { 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<PanelDataPane>) {
const { tab, tabs } = model.useState();
if (!tabs) {
return;
}
const currentTab = tabs.find((t) => t.tabId === tab);
return (
<div>
<TabsBar hideBorder={true}>
{tabs.map((t, index) => {
return (
<Tab
key={`${t.getTabLabel()}-${index}`}
label={t.getTabLabel()}
icon={t.icon}
// suffix={}
active={t.tabId === tab}
onChangeTab={() => model.onChangeTab(t)}
/>
);
})}
</TabsBar>
<TabContent>{currentTab && <currentTab.Component model={currentTab} />}</TabContent>
</div>
);
}

View File

@ -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<PanelDataPaneTabState> implements PanelDataPaneTab {
static Component = PanelDataQueriesTabRendered;
tabId = 'queries';
icon: IconName = 'database';
getTabLabel() {
return 'Queries';
}
}
function PanelDataQueriesTabRendered(props: SceneComponentProps<PanelDataQueriesTab>) {
return <div>TODO Queries</div>;
}

View File

@ -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<PanelDataPaneTabState> implements PanelDataPaneTab {
static Component = PanelDataTransformationsTabRendered;
tabId = 'transformations';
icon: IconName = 'process';
getTabLabel() {
return 'Transformations';
}
}
function PanelDataTransformationsTabRendered(props: SceneComponentProps<PanelDataTransformationsTab>) {
return <div>TODO Transformations</div>;
}

View File

@ -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;
}

View File

@ -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',

View File

@ -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;
}