signoz/frontend/src/container/GridCardLayout/useGraphClickToShowButton.ts
SagarRajput-7 0320285a25
feat: added context redirection from panels to explorer pages (#7141)
* feat: added context redirection from panels to explorer pages

* feat: added graph coordinate - context redirection

* feat: fixed tooltip overlapping the button

* feat: code fix

* feat: removed unneccesary comment

* feat: added logic to resolve variables

* feat: added better logic to handle specific and panel redirection using query

* feat: added multi query support by datasource to panels redirction

* feat: fixing createbutton display logic

* feat: added logic and ui for specific line redirection

* feat: added logic to compute query with groupby

* feat: code fix and added aysnc await

* feat: added context redirection to fullview and edit view panels (#7252)

* feat: added context redirection to fullview and edit view panels

* feat: restricted redirection query to have only one query

* feat: added is buttonEnabled logic of graphs

* feat: code cleanup

* feat: for one query removed the queryname from onclick button

* feat: removed redirection option from action menu

* feat: redesign the format api flow to avoid delay in clickbutton appearance

* feat: updated the create filter logic for groupBys

* feat: handled the error on format api
2025-03-20 11:29:31 +05:30

192 lines
4.8 KiB
TypeScript

import './GridCardLayout.styles.scss';
import { isUndefined } from 'lodash-es';
import { useCallback, useEffect, useRef } from 'react';
interface ClickToShowButtonProps {
graphRef: React.RefObject<HTMLDivElement>;
buttonClassName?: string;
isButtonEnabled?: boolean;
}
export interface GraphClickProps {
xValue: number;
yValue: number;
mouseX: number;
mouseY: number;
metric?: { [key: string]: string };
queryData?: { queryName: string; inFocusOrNot: boolean };
menuItems?: Array<{
text: string;
onClick: (
xValue: number,
yValue: number,
mouseX: number,
mouseY: number,
metric?: { [key: string]: string },
queryData?: { queryName: string; inFocusOrNot: boolean },
) => void;
}>;
}
export const useGraphClickToShowButton = ({
graphRef,
buttonClassName = 'view-onclick-show-button',
isButtonEnabled = true,
}: ClickToShowButtonProps): ((props: GraphClickProps) => void) => {
const activeButtonRef = useRef<HTMLButtonElement | HTMLUListElement | null>(
null,
);
const hideTooltips = (): void => {
const elements = [
{ id: 'overlay', selector: '#overlay' },
{ className: 'uplot-tooltip', selector: '.uplot-tooltip' },
];
elements.forEach(({ selector }) => {
const element = document.querySelector(selector) as HTMLElement;
if (element) {
element.style.display = 'none';
}
});
};
const cleanup = useCallback((): void => {
if (activeButtonRef.current) {
activeButtonRef.current.remove();
activeButtonRef.current = null;
}
// Restore tooltips
['#overlay', '.uplot-tooltip'].forEach((selector) => {
const element = document.querySelector(selector) as HTMLElement;
if (element) {
element.style.display = 'block';
}
});
}, []);
const createMenu = (
xValue: number,
yValue: number,
mouseX: number,
mouseY: number,
menuItems: Array<{
text: string;
onClick: (
xValue: number,
yValue: number,
mouseX: number,
mouseY: number,
metric?: { [key: string]: string },
queryData?: { queryName: string; inFocusOrNot: boolean },
) => void;
}>,
metric?: { [key: string]: string },
queryData?: { queryName: string; inFocusOrNot: boolean },
): void => {
const menuList = document.createElement('ul');
menuList.className = buttonClassName;
menuList.style.position = 'absolute';
menuList.style.zIndex = '9999';
const graphBounds = graphRef.current?.getBoundingClientRect();
if (!graphBounds) return;
graphRef.current?.appendChild(menuList);
// After appending, get menu dimensions and adjust if needed so it stays within the graph boundaries
const menuBounds = menuList.getBoundingClientRect();
// Calculate position considering menu dimensions
let finalLeft = mouseX;
let finalTop = mouseY;
// Adjust horizontal position if menu would overflow
if (mouseX + menuBounds.width > graphBounds.width) {
finalLeft = mouseX - menuBounds.width;
}
// Ensure menu doesn't go off the left edge
finalLeft = Math.max(0, finalLeft);
// Adjust vertical position if menu would overflow
if (mouseY + menuBounds.height > graphBounds.height) {
finalTop = mouseY - menuBounds.height;
}
// Ensure menu doesn't go off the top edge
finalTop = Math.max(0, finalTop);
menuList.style.left = `${finalLeft}px`;
menuList.style.top = `${finalTop}px`;
// Create a list item for each menu option provided in props
menuItems.forEach((item) => {
const listItem = document.createElement('li');
listItem.textContent = item.text;
listItem.className = 'menu-item';
// Style the list item as needed (padding, cursor, etc.)
listItem.onclick = (e: MouseEvent): void => {
e.stopPropagation();
// Execute the provided onClick handler for this menu item
item.onClick(xValue, yValue, mouseX, mouseY, metric, queryData);
cleanup();
};
menuList.appendChild(listItem);
});
activeButtonRef.current = menuList;
};
useEffect(() => {
const handleOutsideClick = (e: MouseEvent): void => {
if (!graphRef.current?.contains(e.target as Node)) {
cleanup();
}
};
document.addEventListener('click', handleOutsideClick);
return (): void => {
document.removeEventListener('click', handleOutsideClick);
cleanup();
};
}, [cleanup, graphRef]);
return useCallback(
(props: GraphClickProps) => {
cleanup();
const {
xValue,
yValue,
mouseX,
mouseY,
metric,
queryData,
menuItems,
} = props;
if (
isButtonEnabled &&
!isUndefined(props.xValue) &&
props.queryData &&
queryData?.inFocusOrNot &&
Object.keys(queryData).length > 0
) {
hideTooltips();
// createButton(xValue, yValue, mouseX, mouseY, metric, queryData);
createMenu(
xValue,
yValue,
mouseX,
mouseY,
menuItems || [],
metric,
queryData,
);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[buttonClassName, graphRef, isButtonEnabled, cleanup],
);
};