mirror of
https://github.com/apache/superset.git
synced 2024-09-06 22:07:34 -04:00
fix(dashboard): Ensure correct positioning of "Drill to detail by" submenu (#21894)
This commit is contained in:
parent
f4a4ab41e0
commit
40f82545ab
@ -36,9 +36,7 @@ import { findPermission } from 'src/utils/findPermission';
|
|||||||
import { Menu } from 'src/components/Menu';
|
import { Menu } from 'src/components/Menu';
|
||||||
import { AntdDropdown as Dropdown } from 'src/components';
|
import { AntdDropdown as Dropdown } from 'src/components';
|
||||||
import { DrillDetailMenuItems } from './DrillDetail';
|
import { DrillDetailMenuItems } from './DrillDetail';
|
||||||
|
import { getMenuAdjustedY } from './utils';
|
||||||
const MENU_ITEM_HEIGHT = 32;
|
|
||||||
const MENU_VERTICAL_SPACING = 32;
|
|
||||||
|
|
||||||
export interface ChartContextMenuProps {
|
export interface ChartContextMenuProps {
|
||||||
id: number;
|
id: number;
|
||||||
@ -78,8 +76,9 @@ const ChartContextMenu = (
|
|||||||
<DrillDetailMenuItems
|
<DrillDetailMenuItems
|
||||||
chartId={id}
|
chartId={id}
|
||||||
formData={formData}
|
formData={formData}
|
||||||
isContextMenu
|
|
||||||
filters={filters}
|
filters={filters}
|
||||||
|
isContextMenu
|
||||||
|
contextMenuY={clientY}
|
||||||
onSelection={onSelection}
|
onSelection={onSelection}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
@ -91,21 +90,12 @@ const ChartContextMenu = (
|
|||||||
clientY: number,
|
clientY: number,
|
||||||
filters?: BinaryQueryObjectFilterClause[],
|
filters?: BinaryQueryObjectFilterClause[],
|
||||||
) => {
|
) => {
|
||||||
// Viewport height
|
|
||||||
const vh = Math.max(
|
|
||||||
document.documentElement.clientHeight || 0,
|
|
||||||
window.innerHeight || 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
const itemsCount =
|
const itemsCount =
|
||||||
[
|
[
|
||||||
showDrillToDetail ? 2 : 0, // Drill to detail always has 2 top-level menu items
|
showDrillToDetail ? 2 : 0, // Drill to detail always has 2 top-level menu items
|
||||||
].reduce((a, b) => a + b, 0) || 1; // "No actions" appears if no actions in menu
|
].reduce((a, b) => a + b, 0) || 1; // "No actions" appears if no actions in menu
|
||||||
|
|
||||||
const menuHeight = MENU_ITEM_HEIGHT * itemsCount + MENU_VERTICAL_SPACING;
|
const adjustedY = getMenuAdjustedY(clientY, itemsCount);
|
||||||
// Always show the context menu inside the viewport
|
|
||||||
const adjustedY = vh - clientY < menuHeight ? vh - menuHeight : clientY;
|
|
||||||
|
|
||||||
setState({
|
setState({
|
||||||
clientX,
|
clientX,
|
||||||
clientY: adjustedY,
|
clientY: adjustedY,
|
||||||
|
@ -34,6 +34,9 @@ import { Menu } from 'src/components/Menu';
|
|||||||
import Icons from 'src/components/Icons';
|
import Icons from 'src/components/Icons';
|
||||||
import { Tooltip } from 'src/components/Tooltip';
|
import { Tooltip } from 'src/components/Tooltip';
|
||||||
import DrillDetailModal from './DrillDetailModal';
|
import DrillDetailModal from './DrillDetailModal';
|
||||||
|
import { getMenuAdjustedY, MENU_ITEM_HEIGHT } from '../utils';
|
||||||
|
|
||||||
|
const MENU_PADDING = 4;
|
||||||
|
|
||||||
const DisabledMenuItemTooltip = ({ title }: { title: ReactNode }) => (
|
const DisabledMenuItemTooltip = ({ title }: { title: ReactNode }) => (
|
||||||
<Tooltip title={title} placement="top">
|
<Tooltip title={title} placement="top">
|
||||||
@ -79,6 +82,7 @@ export type DrillDetailMenuItemsProps = {
|
|||||||
formData: QueryFormData;
|
formData: QueryFormData;
|
||||||
filters?: BinaryQueryObjectFilterClause[];
|
filters?: BinaryQueryObjectFilterClause[];
|
||||||
isContextMenu?: boolean;
|
isContextMenu?: boolean;
|
||||||
|
contextMenuY?: number;
|
||||||
onSelection?: () => void;
|
onSelection?: () => void;
|
||||||
onClick?: (event: MouseEvent) => void;
|
onClick?: (event: MouseEvent) => void;
|
||||||
};
|
};
|
||||||
@ -88,6 +92,7 @@ const DrillDetailMenuItems = ({
|
|||||||
formData,
|
formData,
|
||||||
filters = [],
|
filters = [],
|
||||||
isContextMenu = false,
|
isContextMenu = false,
|
||||||
|
contextMenuY = 0,
|
||||||
onSelection = () => null,
|
onSelection = () => null,
|
||||||
onClick = () => null,
|
onClick = () => null,
|
||||||
...props
|
...props
|
||||||
@ -176,9 +181,22 @@ const DrillDetailMenuItems = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure submenu doesn't appear offscreen
|
||||||
|
const submenuYOffset = useMemo(() => {
|
||||||
|
const itemsCount = filters.length > 1 ? filters.length + 1 : filters.length;
|
||||||
|
const submenuY =
|
||||||
|
contextMenuY + MENU_PADDING + MENU_ITEM_HEIGHT + MENU_PADDING;
|
||||||
|
|
||||||
|
return getMenuAdjustedY(submenuY, itemsCount) - submenuY;
|
||||||
|
}, [contextMenuY, filters.length]);
|
||||||
|
|
||||||
if (handlesDimensionContextMenu && !noAggregations && filters?.length) {
|
if (handlesDimensionContextMenu && !noAggregations && filters?.length) {
|
||||||
drillToDetailByMenuItem = (
|
drillToDetailByMenuItem = (
|
||||||
<Menu.SubMenu {...props} title={t('Drill to detail by')}>
|
<Menu.SubMenu
|
||||||
|
{...props}
|
||||||
|
popupOffset={[0, submenuYOffset]}
|
||||||
|
title={t('Drill to detail by')}
|
||||||
|
>
|
||||||
<div data-test="drill-to-detail-by-submenu">
|
<div data-test="drill-to-detail-by-submenu">
|
||||||
{filters.map((filter, i) => (
|
{filters.map((filter, i) => (
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
|
42
superset-frontend/src/components/Chart/utils.test.ts
Normal file
42
superset-frontend/src/components/Chart/utils.test.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getMenuAdjustedY } from './utils';
|
||||||
|
|
||||||
|
const originalInnerHeight = window.innerHeight;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
window.innerHeight = 500;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
window.innerHeight = originalInnerHeight;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('correctly positions at upper edge of screen', () => {
|
||||||
|
expect(getMenuAdjustedY(75, 1)).toEqual(75); // No adjustment
|
||||||
|
expect(getMenuAdjustedY(75, 2)).toEqual(75); // No adjustment
|
||||||
|
expect(getMenuAdjustedY(75, 3)).toEqual(75); // No adjustment
|
||||||
|
});
|
||||||
|
|
||||||
|
test('correctly positions at lower edge of screen', () => {
|
||||||
|
expect(getMenuAdjustedY(425, 1)).toEqual(425); // No adjustment
|
||||||
|
expect(getMenuAdjustedY(425, 2)).toEqual(404); // Adjustment
|
||||||
|
expect(getMenuAdjustedY(425, 3)).toEqual(372); // Adjustment
|
||||||
|
});
|
40
superset-frontend/src/components/Chart/utils.ts
Normal file
40
superset-frontend/src/components/Chart/utils.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const MENU_ITEM_HEIGHT = 32;
|
||||||
|
const MENU_VERTICAL_SPACING = 32;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates an adjusted Y-offset for a menu or submenu to prevent that
|
||||||
|
* menu from appearing offscreen
|
||||||
|
*
|
||||||
|
* @param clientY The original Y-offset
|
||||||
|
* @param itemsCount The number of menu items
|
||||||
|
*/
|
||||||
|
export function getMenuAdjustedY(clientY: number, itemsCount: number) {
|
||||||
|
// Viewport height
|
||||||
|
const vh = Math.max(
|
||||||
|
document.documentElement.clientHeight || 0,
|
||||||
|
window.innerHeight || 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuHeight = MENU_ITEM_HEIGHT * itemsCount + MENU_VERTICAL_SPACING;
|
||||||
|
// Always show the context menu inside the viewport
|
||||||
|
return vh - clientY < menuHeight ? vh - menuHeight : clientY;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user