PublicDashboards: HoverWidget in panel fix (#66685)
Hover widget modified for public dashboards.
This commit is contained in:
@@ -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();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user