Dashboards: Fix scroll position not being restored when leaving panel edit (#83787)

* Dashboards: Fix scroll position not being restored when leaving panel edit view

* remove mock from tests

* remove console log

* Remove my debugging stuff, and don't render grid if width is 0

* remove old comment (but retain old, probably unneeded css)

* rename ref

* fix it not actually working anymore!!!

* add e2e tests

* jsonnet, i guess
This commit is contained in:
Josh Hunt 2024-03-06 13:57:11 +00:00 committed by GitHub
parent 6db7eafd7e
commit 183aa09eeb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 1874 additions and 74 deletions

File diff suppressed because it is too large Load Diff

View File

@ -91,6 +91,7 @@
"table_sparkline_cell": (import '../dev-dashboards/panel-table/table_sparkline_cell.json'),
"table_tests": (import '../dev-dashboards/panel-table/table_tests.json'),
"table_tests_new": (import '../dev-dashboards/panel-table/table_tests_new.json'),
"tall_dashboard": (import '../dev-dashboards/scenarios/tall_dashboard.json'),
"templating-dashboard-links-and-variables": (import '../dev-dashboards/feature-templating/templating-dashboard-links-and-variables.json'),
"templating-repeating-panels": (import '../dev-dashboards/feature-templating/templating-repeating-panels.json'),
"templating-repeating-rows": (import '../dev-dashboards/feature-templating/templating-repeating-rows.json'),

View File

@ -0,0 +1,33 @@
import { e2e } from '../utils';
const PAGE_UNDER_TEST = 'edediimbjhdz4b/a-tall-dashboard';
describe('Dashboards', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
it('should restore scroll position', () => {
e2e.flows.openDashboard({ uid: PAGE_UNDER_TEST });
e2e.components.Panels.Panel.title('Panel #1').should('be.visible');
// scroll to the bottom
e2e.pages.Dashboard.DashNav.navV2()
.parent()
.parent() // Note, this will probably fail when we change the custom scrollbars
.scrollTo('bottom', {
timeout: 5 * 1000,
});
// The last panel should be visible...
e2e.components.Panels.Panel.title('Panel #50').should('be.visible');
// Then we open and close the panel editor
e2e.components.Panels.Panel.menu('Panel #50').click({ force: true }); // it only shows on hover
e2e.components.Panels.Panel.menuItems('Edit').click();
e2e.components.PanelEditor.applyButton().click();
// And the last panel should still be visible!
e2e.components.Panels.Panel.title('Panel #50').should('be.visible');
});
});

View File

@ -4,7 +4,6 @@ import React from 'react';
import { Provider } from 'react-redux';
import { match, Router } from 'react-router-dom';
import { useEffectOnce } from 'react-use';
import { Props as AutoSizerProps } from 'react-virtualized-auto-sizer';
import { mockToolkitActionCreator } from 'test/core/redux/mocks';
import { TestProvider } from 'test/helpers/TestProvider';
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
@ -71,18 +70,6 @@ jest.mock('@grafana/runtime', () => ({
getPluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }),
}));
jest.mock('react-virtualized-auto-sizer', () => {
// The size of the children need to be small enough to be outside the view.
// So it does not trigger the query to be run by the PanelQueryRunner.
return ({ children }: AutoSizerProps) =>
children({
height: 1,
scaledHeight: 1,
scaledWidth: 1,
width: 1,
});
});
function getTestDashboard(overrides?: Partial<Dashboard>, metaOverrides?: Partial<DashboardMeta>): DashboardModel {
const data = Object.assign(
{

View File

@ -3,7 +3,6 @@ import React from 'react';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import { useEffectOnce } from 'react-use';
import { Props as AutoSizerProps } from 'react-virtualized-auto-sizer';
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
import { TextBoxVariableModel } from '@grafana/data';
@ -42,18 +41,6 @@ jest.mock('app/features/dashboard/dashgrid/LazyLoader', () => {
return { LazyLoader };
});
jest.mock('react-virtualized-auto-sizer', () => {
// The size of the children need to be small enough to be outside the view.
// So it does not trigger the query to be run by the PanelQueryRunner.
return ({ children }: AutoSizerProps) =>
children({
scaledHeight: 1,
height: 1,
scaledWidth: 1,
width: 1,
});
});
function setup(props: Props) {
const context = getGrafanaContextMock();
const store = configureStore({});

View File

@ -1,7 +1,6 @@
import classNames from 'classnames';
import React, { PureComponent, CSSProperties } from 'react';
import ReactGridLayout, { ItemCallback } from 'react-grid-layout';
import AutoSizer from 'react-virtualized-auto-sizer';
import { Subscription } from 'rxjs';
import { config } from '@grafana/runtime';
@ -31,6 +30,7 @@ export interface Props {
interface State {
panelFilter?: RegExp;
width: number;
}
export class DashboardGrid extends PureComponent<Props, State> {
@ -47,6 +47,7 @@ export class DashboardGrid extends PureComponent<Props, State> {
super(props);
this.state = {
panelFilter: undefined,
width: document.body.clientWidth, // initial very rough estimate
};
}
@ -291,22 +292,41 @@ export class DashboardGrid extends PureComponent<Props, State> {
}
};
private resizeObserver?: ResizeObserver;
private rootEl: HTMLDivElement | null = null;
onMeasureRef = (rootEl: HTMLDivElement | null) => {
if (!rootEl) {
if (this.rootEl && this.resizeObserver) {
this.resizeObserver.unobserve(this.rootEl);
}
return;
}
this.rootEl = rootEl;
this.resizeObserver = new ResizeObserver((entries) => {
entries.forEach((entry) => {
this.setState({ width: entry.contentRect.width });
});
});
this.resizeObserver.observe(rootEl);
};
render() {
const { isEditable, dashboard } = this.props;
const { width } = this.state;
if (dashboard.panels.length === 0) {
return <DashboardEmpty dashboard={dashboard} canCreate={isEditable} />;
}
/**
* We have a parent with "flex: 1 1 0" we need to reset it to "flex: 1 1 auto" to have the AutoSizer
* properly working. For more information go here:
* https://github.com/bvaughn/react-virtualized/blob/master/docs/usingAutoSizer.md#can-i-use-autosizer-within-a-flex-container
*
* pos: rel + z-index is required to create a new stacking context to contain the escalating z-indexes of the panels
*/
const draggable = width <= config.theme2.breakpoints.values.md ? false : isEditable;
// pos: rel + z-index is required to create a new stacking context to contain
// the escalating z-indexes of the panels
return (
<div
ref={this.onMeasureRef}
style={{
flex: '1 1 auto',
position: 'relative',
@ -314,46 +334,27 @@ export class DashboardGrid extends PureComponent<Props, State> {
display: this.props.editPanel ? 'none' : undefined,
}}
>
<AutoSizer disableHeight>
{({ width }) => {
if (width === 0) {
return null;
}
// Disable draggable if mobile device, solving an issue with unintentionally
// moving panels. https://github.com/grafana/grafana/issues/18497
const draggable = width <= config.theme2.breakpoints.values.md ? false : isEditable;
return (
/**
* The children is using a width of 100% so we need to guarantee that it is wrapped
* in an element that has the calculated size given by the AutoSizer. The AutoSizer
* has a width of 0 and will let its content overflow its div.
*/
<div style={{ width: width, height: '100%' }} ref={this.onGetWrapperDivRef}>
<ReactGridLayout
width={width}
isDraggable={draggable}
isResizable={isEditable}
containerPadding={[0, 0]}
useCSSTransforms={true}
margin={[GRID_CELL_VMARGIN, GRID_CELL_VMARGIN]}
cols={GRID_COLUMN_COUNT}
rowHeight={GRID_CELL_HEIGHT}
draggableHandle=".grid-drag-handle"
draggableCancel=".grid-drag-cancel"
layout={this.buildLayout()}
onDragStop={this.onDragStop}
onResize={this.onResize}
onResizeStop={this.onResizeStop}
onLayoutChange={this.onLayoutChange}
>
{this.renderPanels(width, draggable)}
</ReactGridLayout>
</div>
);
}}
</AutoSizer>
<div style={{ width: width, height: '100%' }} ref={this.onGetWrapperDivRef}>
<ReactGridLayout
width={width}
isDraggable={draggable}
isResizable={isEditable}
containerPadding={[0, 0]}
useCSSTransforms={true}
margin={[GRID_CELL_VMARGIN, GRID_CELL_VMARGIN]}
cols={GRID_COLUMN_COUNT}
rowHeight={GRID_CELL_HEIGHT}
draggableHandle=".grid-drag-handle"
draggableCancel=".grid-drag-cancel"
layout={this.buildLayout()}
onDragStop={this.onDragStop}
onResize={this.onResize}
onResizeStop={this.onResizeStop}
onLayoutChange={this.onLayoutChange}
>
{this.renderPanels(width, draggable)}
</ReactGridLayout>
</div>
</div>
);
}