PublicDashboards: HoverWidget in panel fix (#66685)

Hover widget modified for public dashboards.
This commit is contained in:
Juan Cabanas
2023-04-26 09:06:38 -03:00
committed by GitHub
parent a62cb96089
commit 47aa8c7654
9 changed files with 106 additions and 12 deletions
@@ -77,6 +77,10 @@ export const Components = {
containerByTitle: (title: string) => `${title} panel`, containerByTitle: (title: string) => `${title} panel`,
headerCornerInfo: (mode: string) => `Panel header ${mode}`, headerCornerInfo: (mode: string) => `Panel header ${mode}`,
loadingBar: () => `Panel loading bar`, loadingBar: () => `Panel loading bar`,
HoverWidget: {
container: 'data-test-id hover-header-container',
dragIcon: 'data-testid drag-icon',
},
}, },
Visualization: { Visualization: {
Graph: { Graph: {
@@ -2,6 +2,7 @@ import { css, cx } from '@emotion/css';
import React, { ReactElement, useCallback, useRef, useState } from 'react'; import React, { ReactElement, useCallback, useRef, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
import { useStyles2 } from '../../themes'; import { useStyles2 } from '../../themes';
import { Icon } from '../Icon/Icon'; import { Icon } from '../Icon/Icon';
@@ -16,6 +17,8 @@ interface Props {
dragClass?: string; dragClass?: string;
} }
const selectors = e2eSelectors.components.Panels.Panel.HoverWidget;
export function HoverWidget({ menu, title, dragClass, children, offset = -32 }: Props) { export function HoverWidget({ menu, title, dragClass, children, offset = -32 }: Props) {
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
const draggableRef = useRef<HTMLDivElement>(null); const draggableRef = useRef<HTMLDivElement>(null);
@@ -39,7 +42,7 @@ export function HoverWidget({ menu, title, dragClass, children, offset = -32 }:
<div <div
className={cx(styles.container, { 'show-on-hover': !menuOpen })} className={cx(styles.container, { 'show-on-hover': !menuOpen })}
style={{ top: `${offset}px` }} style={{ top: `${offset}px` }}
data-testid="hover-header-container" data-testid={selectors.container}
> >
{dragClass && ( {dragClass && (
<div <div
@@ -47,11 +50,12 @@ export function HoverWidget({ menu, title, dragClass, children, offset = -32 }:
onPointerDown={onPointerDown} onPointerDown={onPointerDown}
onPointerUp={onPointerUp} onPointerUp={onPointerUp}
ref={draggableRef} ref={draggableRef}
data-testid={selectors.dragIcon}
> >
<Icon name="expand-arrows" className={styles.draggableIcon} /> <Icon name="expand-arrows" className={styles.draggableIcon} />
</div> </div>
)} )}
{!title && <h6 className={cx(styles.untitled, styles.draggable, dragClass)}>Untitled</h6>} {!title && <h6 className={cx(styles.untitled, { [styles.draggable]: !!dragClass }, dragClass)}>Untitled</h6>}
{children} {children}
{menu && ( {menu && (
<PanelMenu <PanelMenu
@@ -1,4 +1,5 @@
import { render, screen, waitFor } from '@testing-library/react'; import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react'; import React from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { Router } from 'react-router-dom'; import { Router } from 'react-router-dom';
@@ -8,11 +9,12 @@ import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
import { locationService } from '@grafana/runtime'; import { locationService } from '@grafana/runtime';
import { Dashboard, DashboardCursorSync } from '@grafana/schema/src'; import { Dashboard, DashboardCursorSync, FieldConfigSource, ThresholdsMode, Panel } from '@grafana/schema/src';
import config from 'app/core/config';
import { GrafanaContext } from 'app/core/context/GrafanaContext'; import { GrafanaContext } from 'app/core/context/GrafanaContext';
import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
import { DashboardInitPhase, DashboardMeta, DashboardRoutes } from 'app/types';
import * as appTypes from 'app/types'; import * as appTypes from 'app/types';
import { DashboardInitPhase, DashboardMeta, DashboardRoutes } from 'app/types';
import { SafeDynamicImport } from '../../../core/components/DynamicImports/SafeDynamicImport'; import { SafeDynamicImport } from '../../../core/components/DynamicImports/SafeDynamicImport';
import { configureStore } from '../../../store/configureStore'; import { configureStore } from '../../../store/configureStore';
@@ -125,6 +127,9 @@ const getTestDashboard = (overrides?: Partial<Dashboard>, metaOverrides?: Partia
describe('PublicDashboardPage', () => { describe('PublicDashboardPage', () => {
beforeEach(() => { beforeEach(() => {
config.featureToggles.publicDashboards = true;
config.featureToggles.newPanelChromeUI = true;
jest.clearAllMocks(); jest.clearAllMocks();
}); });
@@ -175,6 +180,68 @@ describe('PublicDashboardPage', () => {
expect(screen.queryByTestId(publicDashboardSelector.NotAvailable.container)).not.toBeInTheDocument(); expect(screen.queryByTestId(publicDashboardSelector.NotAvailable.container)).not.toBeInTheDocument();
}); });
}); });
it('Should render panel with hover widget but without drag icon when panel title is undefined', async () => {
const fieldConfig: FieldConfigSource = {
defaults: {
thresholds: {
mode: ThresholdsMode.Absolute,
steps: [
{
color: 'green',
value: 1,
},
{
color: 'red',
value: 80,
},
],
},
},
overrides: [],
};
const panels: Panel[] = [
{
id: 1,
fieldConfig,
gridPos: {
h: 8,
w: 12,
x: 0,
y: 0,
},
options: {},
title: undefined,
type: 'timeseries',
repeatDirection: 'h',
transformations: [],
transparent: false,
},
];
const newState = {
dashboard: {
getModel: () => getTestDashboard({ panels }),
initError: null,
initPhase: DashboardInitPhase.Completed,
permissions: [],
},
};
setup(undefined, newState);
await waitFor(() => {
expect(screen.getByTestId(selectors.Panels.Panel.HoverWidget.container)).toBeInTheDocument();
});
await userEvent.hover(screen.getByTestId(selectors.Panels.Panel.HoverWidget.container));
expect(screen.queryByTestId(selectors.Panels.Panel.HoverWidget.dragIcon)).not.toBeInTheDocument();
});
it('Should render panel without hover widget when panel title is not undefined', async () => {
setup(undefined, newState);
await waitFor(() => {
expect(screen.queryByTestId(selectors.Panels.Panel.HoverWidget.container)).not.toBeInTheDocument();
});
});
}); });
describe('Given a public dashboard with time range enabled', () => { describe('Given a public dashboard with time range enabled', () => {
@@ -122,7 +122,7 @@ const PublicDashboardPage = (props: Props) => {
const getStyles = (theme: GrafanaTheme2) => ({ const getStyles = (theme: GrafanaTheme2) => ({
gridContainer: css({ gridContainer: css({
flex: 1, flex: 1,
padding: theme.spacing(0, 2, 2, 2), padding: theme.spacing(2, 2, 2, 2),
overflow: 'auto', overflow: 'auto',
}), }),
}); });
@@ -142,7 +142,7 @@ export class DashboardGrid extends PureComponent<Props, State> {
return { top, bottom: this.lastPanelBottom }; return { top, bottom: this.lastPanelBottom };
} }
renderPanels(gridWidth: number) { renderPanels(gridWidth: number, isDashboardDraggable: boolean) {
const panelElements = []; const panelElements = [];
// Reset last panel bottom // Reset last panel bottom
@@ -171,7 +171,7 @@ export class DashboardGrid extends PureComponent<Props, State> {
isViewing={panel.isViewing} isViewing={panel.isViewing}
> >
{(width: number, height: number) => { {(width: number, height: number) => {
return this.renderPanel(panel, width, height); return this.renderPanel(panel, width, height, isDashboardDraggable);
}} }}
</GrafanaGridItem> </GrafanaGridItem>
); );
@@ -180,7 +180,7 @@ export class DashboardGrid extends PureComponent<Props, State> {
return panelElements; return panelElements;
} }
renderPanel(panel: PanelModel, width: number, height: number) { renderPanel(panel: PanelModel, width: number, height: number, isDraggable: boolean) {
if (panel.type === 'row') { if (panel.type === 'row') {
return <DashboardRow key={panel.key} panel={panel} dashboard={this.props.dashboard} />; return <DashboardRow key={panel.key} panel={panel} dashboard={this.props.dashboard} />;
} }
@@ -202,6 +202,7 @@ export class DashboardGrid extends PureComponent<Props, State> {
dashboard={this.props.dashboard} dashboard={this.props.dashboard}
isEditing={panel.isEditing} isEditing={panel.isEditing}
isViewing={panel.isViewing} isViewing={panel.isViewing}
isDraggable={isDraggable}
width={width} width={width}
height={height} height={height}
hideMenu={this.props.hidePanelMenus} hideMenu={this.props.hidePanelMenus}
@@ -258,7 +259,7 @@ export class DashboardGrid extends PureComponent<Props, State> {
onResizeStop={this.onResizeStop} onResizeStop={this.onResizeStop}
onLayoutChange={this.onLayoutChange} onLayoutChange={this.onLayoutChange}
> >
{this.renderPanels(width)} {this.renderPanels(width, draggable)}
</ReactGridLayout> </ReactGridLayout>
</div> </div>
) : ( ) : (
@@ -290,7 +291,7 @@ export class DashboardGrid extends PureComponent<Props, State> {
onResizeStop={this.onResizeStop} onResizeStop={this.onResizeStop}
onLayoutChange={this.onLayoutChange} onLayoutChange={this.onLayoutChange}
> >
{this.renderPanels(width)} {this.renderPanels(width, draggable)}
</ReactGridLayout> </ReactGridLayout>
</div> </div>
); );
@@ -17,6 +17,7 @@ export interface OwnProps {
dashboard: DashboardModel; dashboard: DashboardModel;
isEditing: boolean; isEditing: boolean;
isViewing: boolean; isViewing: boolean;
isDraggable?: boolean;
width: number; width: number;
height: number; height: number;
lazy?: boolean; lazy?: boolean;
@@ -72,7 +73,18 @@ export class DashboardPanelUnconnected extends PureComponent<Props> {
}; };
renderPanel = ({ isInView }: { isInView: boolean }) => { renderPanel = ({ isInView }: { isInView: boolean }) => {
const { dashboard, panel, isViewing, isEditing, width, height, plugin, timezone, hideMenu } = this.props; const {
dashboard,
panel,
isViewing,
isEditing,
width,
height,
plugin,
timezone,
hideMenu,
isDraggable = true,
} = this.props;
if (!plugin) { if (!plugin) {
return null; return null;
@@ -87,6 +99,7 @@ export class DashboardPanelUnconnected extends PureComponent<Props> {
isViewing={isViewing} isViewing={isViewing}
isEditing={isEditing} isEditing={isEditing}
isInView={isInView} isInView={isInView}
isDraggable={isDraggable}
width={width} width={width}
height={height} height={height}
/> />
@@ -101,6 +114,7 @@ export class DashboardPanelUnconnected extends PureComponent<Props> {
isViewing={isViewing} isViewing={isViewing}
isEditing={isEditing} isEditing={isEditing}
isInView={isInView} isInView={isInView}
isDraggable={isDraggable}
width={width} width={width}
height={height} height={height}
onInstanceStateChange={this.onInstanceStateChange} onInstanceStateChange={this.onInstanceStateChange}
@@ -28,6 +28,7 @@ interface OwnProps {
isViewing: boolean; isViewing: boolean;
isEditing: boolean; isEditing: boolean;
isInView: boolean; isInView: boolean;
isDraggable?: boolean;
width: number; width: number;
height: number; height: number;
hideMenu?: boolean; hideMenu?: boolean;
@@ -65,6 +65,7 @@ export interface Props {
isViewing: boolean; isViewing: boolean;
isEditing: boolean; isEditing: boolean;
isInView: boolean; isInView: boolean;
isDraggable?: boolean;
width: number; width: number;
height: number; height: number;
onInstanceStateChange: (value: any) => void; onInstanceStateChange: (value: any) => void;
@@ -17,6 +17,7 @@ interface CommonProps {
isViewing: boolean; isViewing: boolean;
isEditing: boolean; isEditing: boolean;
isInView: boolean; isInView: boolean;
isDraggable?: boolean;
width: number; width: number;
height: number; height: number;
hideMenu?: boolean; hideMenu?: boolean;
@@ -100,7 +101,8 @@ export function getPanelChromeProps(props: CommonProps) {
const description = props.panel.description ? onShowPanelDescription() : undefined; const description = props.panel.description ? onShowPanelDescription() : undefined;
const dragClass = !(props.isViewing || props.isEditing) ? 'grid-drag-handle' : ''; const dragClass =
!(props.isViewing || props.isEditing) && Boolean(props.isDraggable ?? true) ? 'grid-drag-handle' : '';
const title = props.panel.getDisplayTitle(); const title = props.panel.getDisplayTitle();