DashboardScene: Move to new folder structure, some refactorings and progress on inspect (#73810)

* Progress refactoring

* Update

* Update

* Update

* DashboardScene: Folder struct propsal

* Rename loading to persistance

* Moving and renaming
This commit is contained in:
Torkel Ödegaard 2023-08-25 14:11:47 +02:00 committed by GitHub
parent 2f22946f06
commit eab6250142
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 243 additions and 99 deletions

View File

@ -1952,6 +1952,13 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"]
],
"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"],
[0, 0, 0, "Do not use any type assertions.", "2"],
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
[0, 0, 0, "Do not use any type assertions.", "4"]
],
"public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.test.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
@ -2830,13 +2837,6 @@ exports[`better eslint`] = {
"public/app/features/sandbox/TestStuffPage.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"public/app/features/scenes/dashboard/test-utils.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Do not use any type assertions.", "2"],
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
[0, 0, 0, "Do not use any type assertions.", "4"]
],
"public/app/features/search/components/SearchCard.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],

View File

@ -0,0 +1,20 @@
import React from 'react';
import { SceneComponentProps, SceneObjectBase, VizPanel } from '@grafana/scenes';
import { t } from 'app/core/internationalization';
import { InspectTab } from '../../inspector/types';
import { InspectTabState } from './types';
export class InspectDataTab extends SceneObjectBase<InspectTabState> {
constructor(public panel: VizPanel) {
super({ label: t('dashboard.inspect.data-tab', 'Data'), value: InspectTab.Data });
}
static Component = ({ model }: SceneComponentProps<InspectDataTab>) => {
//const data = sceneGraph.getData(model.panel).useState();
return <div>Data tab</div>;
};
}

View File

@ -0,0 +1,18 @@
import React from 'react';
import { SceneComponentProps, SceneObjectBase, VizPanel } from '@grafana/scenes';
import { t } from 'app/core/internationalization';
import { InspectTab } from '../../inspector/types';
import { InspectTabState } from './types';
export class InspectJsonTab extends SceneObjectBase<InspectTabState> {
constructor(public panel: VizPanel) {
super({ label: t('dashboard.inspect.json-tab', 'JSON'), value: InspectTab.JSON });
}
static Component = ({ model }: SceneComponentProps<InspectJsonTab>) => {
return <div>JSON</div>;
};
}

View File

@ -0,0 +1,26 @@
import React from 'react';
import { SceneComponentProps, sceneGraph, SceneObjectBase, VizPanel } from '@grafana/scenes';
import { t } from 'app/core/internationalization';
import { InspectStatsTab as OldInspectStatsTab } from '../../inspector/InspectStatsTab';
import { InspectTab } from '../../inspector/types';
import { InspectTabState } from './types';
export class InspectStatsTab extends SceneObjectBase<InspectTabState> {
constructor(public panel: VizPanel) {
super({ label: t('dashboard.inspect.stats-tab', 'Stats'), value: InspectTab.Stats });
}
static Component = ({ model }: SceneComponentProps<InspectStatsTab>) => {
const data = sceneGraph.getData(model.panel).useState();
const timeRange = sceneGraph.getTimeRange(model.panel);
if (!data.data) {
return null;
}
return <OldInspectStatsTab data={data.data} timeZone={timeRange.getTimeZone()} />;
};
}

View File

@ -0,0 +1,100 @@
import React from 'react';
import { useLocation } from 'react-router-dom';
import { locationUtil } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import {
SceneComponentProps,
SceneObjectBase,
SceneObjectState,
SceneObject,
sceneGraph,
VizPanel,
} from '@grafana/scenes';
import { Drawer, Tab, TabsBar } from '@grafana/ui';
import { supportsDataQuery } from 'app/features/dashboard/components/PanelEditor/utils';
import { InspectDataTab } from './InspectDataTab';
import { InspectJsonTab } from './InspectJsonTab';
import { InspectStatsTab } from './InspectStatsTab';
import { InspectTabState } from './types';
interface PanelInspectDrawerState extends SceneObjectState {
tabs?: Array<SceneObject<InspectTabState>>;
}
export class PanelInspectDrawer extends SceneObjectBase<PanelInspectDrawerState> {
static Component = ScenePanelInspectorRenderer;
// Not stored in state as this is just a reference and it never changes
private _panel: VizPanel;
constructor(panel: VizPanel) {
super({});
this._panel = panel;
this.buildTabs();
}
buildTabs() {
const plugin = this._panel.getPlugin();
const tabs: Array<SceneObject<InspectTabState>> = [];
if (plugin) {
if (supportsDataQuery(plugin)) {
tabs.push(new InspectDataTab(this._panel));
tabs.push(new InspectStatsTab(this._panel));
}
}
tabs.push(new InspectJsonTab(this._panel));
this.setState({ tabs });
}
getDrawerTitle() {
return sceneGraph.interpolate(this._panel, `Inspect: ${this._panel.state.title}`);
}
onClose = () => {
locationService.partial({ inspect: null, inspectTab: null });
};
}
function ScenePanelInspectorRenderer({ model }: SceneComponentProps<PanelInspectDrawer>) {
const { tabs } = model.useState();
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
if (!tabs) {
return null;
}
const urlTab = queryParams.get('inspectTab');
const currentTab = tabs.find((tab) => tab.state.value === urlTab) ?? tabs[0];
return (
<Drawer
title={model.getDrawerTitle()}
scrollableContent
onClose={model.onClose}
size="md"
tabs={
<TabsBar>
{tabs.map((tab) => {
return (
<Tab
key={tab.state.key!}
label={tab.state.label}
active={tab === currentTab}
href={locationUtil.getUrlForPartial(location, { inspectTab: tab.state.value })}
/>
);
})}
</TabsBar>
}
>
{currentTab.Component && <currentTab.Component model={currentTab} />}
</Drawer>
);
}

View File

@ -0,0 +1,7 @@
import { SceneObjectState } from '@grafana/scenes';
import { InspectTab } from 'app/features/inspector/types';
export interface InspectTabState extends SceneObjectState {
label: string;
value: InspectTab;
}

View File

@ -9,8 +9,9 @@ import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
import { config, locationService, setPluginImportUtils } from '@grafana/runtime';
import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
import { setupLoadDashboardMock } from '../utils/test-utils';
import { DashboardScenePage, Props } from './DashboardScenePage';
import { setupLoadDashboardMock } from './test-utils';
function setup() {
const context = getGrafanaContextMock();

View File

@ -6,7 +6,7 @@ import { Page } from 'app/core/components/Page/Page';
import PageLoader from 'app/core/components/PageLoader/PageLoader';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
import { getDashboardLoader } from './DashboardsLoader';
import { getDashboardLoader } from '../serialization/DashboardsLoader';
export interface Props extends GrafanaRouteComponentProps<{ uid: string }> {}

View File

@ -4,7 +4,6 @@ import { AppEvents, locationUtil, NavModelItem } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import {
getUrlSyncManager,
sceneGraph,
SceneGridItem,
SceneGridLayout,
SceneObject,
@ -13,12 +12,13 @@ import {
SceneObjectStateChangedEvent,
SceneObjectUrlSyncHandler,
SceneObjectUrlValues,
VizPanel,
} from '@grafana/scenes';
import appEvents from 'app/core/app_events';
import { DashboardSceneRenderer } from './DashboardSceneRenderer';
import { forceRenderChildren } from './utils';
import { PanelInspectDrawer } from '../inspect/PanelInspectDrawer';
import { DashboardSceneRenderer } from '../scene/DashboardSceneRenderer';
import { findVizPanel } from '../utils/findVizPanel';
import { forceRenderChildren } from '../utils/utils';
export interface DashboardSceneState extends SceneObjectState {
title: string;
@ -32,6 +32,8 @@ export interface DashboardSceneState extends SceneObjectState {
inspectPanelKey?: string;
/** Scene object key for object to view in fullscreen */
viewPanelKey?: string;
/** Scene object that handles the current drawer */
drawer?: SceneObject;
}
export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
@ -51,19 +53,6 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
this.subscribeToEvent(SceneObjectStateChangedEvent, this.onChildStateChanged);
}
findPanel(key: string | undefined): VizPanel | null {
if (!key) {
return null;
}
const obj = sceneGraph.findObject(this, (obj) => obj.state.key === key);
if (obj instanceof VizPanel) {
return obj;
}
return null;
}
onChildStateChanged = (event: SceneObjectStateChangedEvent) => {
// Temporary hacky way to detect changes
if (event.payload.changedObject instanceof SceneGridItem) {
@ -97,10 +86,6 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
}
};
onCloseInspectDrawer = () => {
locationService.partial({ inspect: null });
};
getPageNav(location: H.Location) {
let pageNav: NavModelItem = {
text: this.state.title,
@ -116,6 +101,14 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
return pageNav;
}
/**
* Returns the body (layout) or the full view panel
*/
getBodyToRender(viewPanelKey?: string): SceneObject {
const viewPanel = findVizPanel(this, viewPanelKey);
return viewPanel ?? this.state.body;
}
}
class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
@ -136,7 +129,7 @@ class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
// Handle inspect object state
if (typeof values.inspect === 'string') {
const panel = this._scene.findPanel(values.inspect);
const panel = findVizPanel(this._scene, values.inspect);
if (!panel) {
appEvents.emit(AppEvents.alertError, ['Panel not found']);
locationService.partial({ inspect: null });
@ -144,13 +137,15 @@ class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
}
update.inspectPanelKey = values.inspect;
update.drawer = new PanelInspectDrawer(panel);
} else if (inspectPanelKey) {
update.inspectPanelKey = undefined;
update.drawer = undefined;
}
// Handle view panel state
if (typeof values.viewPanel === 'string') {
const panel = this._scene.findPanel(values.viewPanel);
const panel = findVizPanel(this._scene, values.viewPanel);
if (!panel) {
appEvents.emit(AppEvents.alertError, ['Panel not found']);
locationService.partial({ viewPanel: null });

View File

@ -9,15 +9,13 @@ import { Page } from 'app/core/components/Page/Page';
import { DashboardScene } from './DashboardScene';
import { NavToolbarActions } from './NavToolbarActions';
import { ScenePanelInspector } from './ScenePanelInspector';
export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardScene>) {
const { body, controls, inspectPanelKey, viewPanelKey } = model.useState();
const { controls, viewPanelKey, drawer } = model.useState();
const styles = useStyles2(getStyles);
const inspectPanel = model.findPanel(inspectPanelKey);
const viewPanel = model.findPanel(viewPanelKey);
const location = useLocation();
const pageNav = model.getPageNav(location);
const bodyToRender = model.getBodyToRender(viewPanelKey);
return (
<Page navId="scenes" pageNav={pageNav} layout={PageLayoutType.Custom}>
@ -31,18 +29,12 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardS
))}
</div>
)}
{viewPanel ? (
<div className={styles.viewPanel}>
<viewPanel.Component model={viewPanel} />
</div>
) : (
<div className={styles.body}>
<body.Component model={body} />
</div>
)}
<div className={styles.body}>
<bodyToRender.Component model={bodyToRender} />
</div>
</div>
</CustomScrollbar>
{inspectPanel && <ScenePanelInspector panel={inspectPanel} dashboard={model} />}
{drawer && <drawer.Component model={drawer} />}
</Page>
);
}
@ -62,11 +54,6 @@ function getStyles(theme: GrafanaTheme2) {
flexGrow: 1,
display: 'flex',
gap: '8px',
}),
viewPanel: css({
display: 'flex',
position: 'relative',
flexGrow: 1,
marginBottom: theme.spacing(2),
}),
controls: css({

View File

@ -4,7 +4,7 @@ import { SceneComponentProps, SceneObjectBase, SceneObjectState, VizPanel } from
import { PanelModel } from 'app/features/dashboard/state';
import { getLibraryPanel } from 'app/features/library-panels/state/api';
import { createPanelDataProvider } from './utils/createPanelDataProvider';
import { createPanelDataProvider } from '../utils/createPanelDataProvider';
interface LibraryVizPanelState extends SceneObjectState {
// Library panels use title from dashboard JSON's panel model, not from library panel definition, hence we pass it.

View File

@ -8,8 +8,9 @@ import {
SceneObjectState,
} from '@grafana/scenes';
import { activateFullSceneTree, getVizPanelKeyForPanelId } from '../utils/utils';
import { ShareQueryDataProvider } from './ShareQueryDataProvider';
import { activateFullSceneTree, getVizPanelKeyForPanelId } from './utils';
export class SceneDummyPanel extends SceneObjectBase<SceneObjectState> {}

View File

@ -10,7 +10,7 @@ import {
} from '@grafana/scenes';
import { DashboardQuery } from 'app/plugins/datasource/dashboard/types';
import { getVizPanelKeyForPanelId } from './utils';
import { getVizPanelKeyForPanelId } from '../utils/utils';
export interface ShareQueryDataProviderState extends SceneDataState {
query: DashboardQuery;

View File

@ -19,15 +19,16 @@ import { createPanelJSONFixture } from 'app/features/dashboard/state/__fixtures_
import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard';
import { DASHBOARD_DATASOURCE_PLUGIN_ID } from 'app/plugins/datasource/dashboard/types';
import { DashboardScene } from './DashboardScene';
import { DashboardScene } from '../scene/DashboardScene';
import { ShareQueryDataProvider } from '../scene/ShareQueryDataProvider';
import { setupLoadDashboardMock } from '../utils/test-utils';
import {
createDashboardSceneFromDashboardModel,
createVizPanelFromPanelModel,
createSceneVariableFromVariableModel,
DashboardLoader,
} from './DashboardsLoader';
import { ShareQueryDataProvider } from './ShareQueryDataProvider';
import { setupLoadDashboardMock } from './test-utils';
describe('DashboardLoader', () => {
describe('when fetching/loading a dashboard', () => {

View File

@ -29,11 +29,11 @@ import { StateManagerBase } from 'app/core/services/StateManagerBase';
import { dashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { DashboardScene } from './DashboardScene';
import { LibraryVizPanel } from './LibraryVizPanel';
import { panelMenuBehavior } from './PanelMenuBehavior';
import { getVizPanelKeyForPanelId } from './utils';
import { createPanelDataProvider } from './utils/createPanelDataProvider';
import { DashboardScene } from '../scene/DashboardScene';
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
import { panelMenuBehavior } from '../scene/PanelMenuBehavior';
import { createPanelDataProvider } from '../utils/createPanelDataProvider';
import { getVizPanelKeyForPanelId } from '../utils/utils';
export interface DashboardLoaderState {
dashboard?: DashboardScene;

View File

@ -3,7 +3,7 @@ import { SceneDataProvider, SceneDataTransformer, SceneQueryRunner } from '@graf
import { PanelModel } from 'app/features/dashboard/state';
import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard';
import { ShareQueryDataProvider } from '../ShareQueryDataProvider';
import { ShareQueryDataProvider } from '../scene/ShareQueryDataProvider';
export function createPanelDataProvider(panel: PanelModel): SceneDataProvider | undefined {
// Skip setting query runner for panels without queries

View File

@ -0,0 +1,14 @@
import { sceneGraph, SceneObject, VizPanel } from '@grafana/scenes';
export function findVizPanel(scene: SceneObject, key: string | undefined): VizPanel | null {
if (!key) {
return null;
}
const obj = sceneGraph.findObject(scene, (obj) => obj.state.key === key);
if (obj instanceof VizPanel) {
return obj;
}
return null;
}

View File

@ -4,7 +4,7 @@ import { dateTimeFormat, formattedValueToString, getValueFormat, SelectableValue
import { config } from '@grafana/runtime';
import { SceneObject } from '@grafana/scenes';
import { StateManagerBase } from 'app/core/services/StateManagerBase';
import { createDashboardSceneFromDashboardModel } from 'app/features/scenes/dashboard/DashboardsLoader';
import { createDashboardSceneFromDashboardModel } from 'app/features/dashboard-scene/serialization/DashboardsLoader';
import { getTimeSrv } from '../../services/TimeSrv';
import { DashboardModel, PanelModel } from '../../state';

View File

@ -1,26 +0,0 @@
import React from 'react';
import { VizPanel } from '@grafana/scenes';
import { Drawer } from '@grafana/ui';
import { DashboardScene } from './DashboardScene';
interface Props {
dashboard: DashboardScene;
panel: VizPanel;
}
export const ScenePanelInspector = React.memo<Props>(({ panel, dashboard }) => {
return (
<Drawer
title={`Inspect: ${panel.state.title}`}
scrollableContent
onClose={dashboard.onCloseInspectDrawer}
size="md"
>
Magic content
</Drawer>
);
});
ScenePanelInspector.displayName = 'ScenePanelInspector';

View File

@ -9,7 +9,7 @@ import {
} from '@grafana/scenes';
import { TestDataQueryType } from 'app/plugins/datasource/testdata/dataquery.gen';
import { DashboardScene } from '../dashboard/DashboardScene';
import { DashboardScene } from '../../dashboard-scene/scene/DashboardScene';
import { getQueryRunnerWithRandomWalkQuery } from './queries';

View File

@ -9,7 +9,7 @@ import {
PanelBuilders,
} from '@grafana/scenes';
import { DashboardScene } from '../dashboard/DashboardScene';
import { DashboardScene } from '../../dashboard-scene/scene/DashboardScene';
import { getQueryRunnerWithRandomWalkQuery } from './queries';

View File

@ -9,7 +9,7 @@ import {
} from '@grafana/scenes';
import { TestDataQueryType } from 'app/plugins/datasource/testdata/dataquery.gen';
import { DashboardScene } from '../dashboard/DashboardScene';
import { DashboardScene } from '../../dashboard-scene/scene/DashboardScene';
import { getQueryRunnerWithRandomWalkQuery } from './queries';

View File

@ -1,4 +1,4 @@
import { DashboardScene } from '../dashboard/DashboardScene';
import { DashboardScene } from '../../dashboard-scene/scene/DashboardScene';
import { getGridWithMultipleTimeRanges } from './gridMultiTimeRange';
import { getMultipleGridLayoutTest } from './gridMultiple';

View File

@ -13,7 +13,7 @@ import {
SceneFlexItem,
} from '@grafana/scenes';
import { DashboardScene } from '../dashboard/DashboardScene';
import { DashboardScene } from '../../dashboard-scene/scene/DashboardScene';
export function getQueryVariableDemo(): DashboardScene {
return new DashboardScene({

View File

@ -8,7 +8,7 @@ import {
PanelBuilders,
} from '@grafana/scenes';
import { DashboardScene } from '../dashboard/DashboardScene';
import { DashboardScene } from '../../dashboard-scene/scene/DashboardScene';
import { getQueryRunnerWithRandomWalkQuery } from './queries';

View File

@ -8,7 +8,7 @@ import {
PanelBuilders,
} from '@grafana/scenes';
import { DashboardScene } from '../dashboard/DashboardScene';
import { DashboardScene } from '../../dashboard-scene/scene/DashboardScene';
import { getQueryRunnerWithRandomWalkQuery } from './queries';

View File

@ -15,7 +15,7 @@ import {
PanelBuilders,
} from '@grafana/scenes';
import { DashboardScene } from '../dashboard/DashboardScene';
import { DashboardScene } from '../../dashboard-scene/scene/DashboardScene';
import { getQueryRunnerWithRandomWalkQuery } from './queries';

View File

@ -560,7 +560,7 @@ export function getDynamicDashboardRoutes(cfg = config): RouteDescriptor[] {
{
path: '/scenes/dashboard/:uid',
component: SafeDynamicImport(
() => import(/* webpackChunkName: "scenes"*/ 'app/features/scenes/dashboard/DashboardScenePage')
() => import(/* webpackChunkName: "scenes"*/ 'app/features/dashboard-scene/pages/DashboardScenePage')
),
},
{