Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 26 additions & 9 deletions src/components/QueryResultTable/Cell/Cell.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';

import {showTooltip} from '../../../store/reducers/tooltip';
import {useTypedDispatch} from '../../../utils/hooks';
import {Popup} from '@gravity-ui/uikit';

import {b} from '../QueryResultTable';

interface CellProps {
Expand All @@ -12,14 +12,31 @@ interface CellProps {
export const Cell = React.memo(function Cell(props: CellProps) {
const {className, value} = props;

const dispatch = useTypedDispatch();
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef<HTMLSpanElement | null>(null);

const handleToggle = React.useCallback(() => {
setOpen((prevOpen) => !prevOpen);
}, []);

const handleClose = React.useCallback(() => {
setOpen(false);
}, []);

return (
<span
className={b('cell', className)}
onClick={(e) => dispatch(showTooltip(e.target, value, 'cell'))}
>
{value}
</span>
<React.Fragment>
<Popup
open={open}
hasArrow
placement={['top', 'bottom']}
anchorRef={anchorRef}
onOutsideClick={handleClose}
>
<div className={b('cell-popup')}>{value}</div>
</Popup>
<span ref={anchorRef} className={b('cell', className)} onClick={handleToggle}>
{value}
</span>
</React.Fragment>
);
});
7 changes: 7 additions & 0 deletions src/components/QueryResultTable/QueryResultTable.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
@include mixins.cell-container;
}

&__cell-popup {
max-width: 300px;
padding: 10px;

word-break: break-word;
}

&__message {
padding: 15px 10px;
}
Expand Down
6 changes: 3 additions & 3 deletions src/components/QueryResultTable/QueryResultTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const b = cn('ydb-query-result-table');

const WIDTH_PREDICTION_ROWS_COUNT = 100;

const prepareTypedColumns = (columns: ColumnType[], data?: KeyValueRow[]) => {
const prepareTypedColumns = (columns: ColumnType[], data: KeyValueRow[] | undefined) => {
if (!columns.length) {
return [];
}
Expand All @@ -49,7 +49,7 @@ const prepareTypedColumns = (columns: ColumnType[], data?: KeyValueRow[]) => {
});
};

const prepareGenericColumns = (data?: KeyValueRow[]) => {
const prepareGenericColumns = (data: KeyValueRow[] | undefined) => {
if (!data?.length) {
return [];
}
Expand Down Expand Up @@ -85,7 +85,7 @@ export const QueryResultTable = (props: QueryResultTableProps) => {

const preparedColumns = React.useMemo(() => {
return columns ? prepareTypedColumns(columns, data) : prepareGenericColumns(data);
}, [data, columns]);
}, [columns, data]);

const settings = React.useMemo(() => {
return {
Expand Down
2 changes: 0 additions & 2 deletions src/containers/App/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {Helmet} from 'react-helmet-async';
import {componentsRegistry} from '../../components/ComponentsProvider/componentsRegistry';
import {FullscreenProvider} from '../../components/Fullscreen/FullscreenContext';
import {useTypedSelector} from '../../utils/hooks';
import ReduxTooltip from '../ReduxTooltip/ReduxTooltip';
import type {YDBEmbeddedUISettings} from '../UserSettings/settings';

import {useAppTitle} from './AppTitleContext';
Expand All @@ -34,7 +33,6 @@ function App({store, history, children, userSettings, appTitle = defaultAppTitle
<Providers store={store} history={history} appTitle={appTitle}>
<AppContent userSettings={userSettings}>{children}</AppContent>
{ChatPanel && <ChatPanel />}
<ReduxTooltip />
</Providers>
);
}
Expand Down
15 changes: 15 additions & 0 deletions src/containers/Heatmap/Heatmap.scss
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
}

&__items {
position: relative;

overflow: auto;
}
&__canvas-container {
Expand All @@ -59,6 +61,19 @@
cursor: pointer;
}

&__tooltip-anchor {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add paddings
Screenshot 2025-12-11 at 17 53 55

position: absolute;

width: 1px;
height: 1px;

pointer-events: none;
}

&__tooltip {
padding: 10px;
}

&__filters {
display: flex;
align-items: center;
Expand Down
90 changes: 71 additions & 19 deletions src/containers/Heatmap/Heatmap.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React from 'react';

import {Checkbox, Select} from '@gravity-ui/uikit';
import {Checkbox, Popup, Select} from '@gravity-ui/uikit';

import {ResponseError} from '../../components/Errors/ResponseError';
import {Loader} from '../../components/Loader';
import {TabletTooltipContent} from '../../components/TooltipsContent';
import {heatmapApi, setHeatmapOptions} from '../../store/reducers/heatmap';
import {hideTooltip, showTooltip} from '../../store/reducers/tooltip';
import type {IHeatmapMetricValue} from '../../types/store/heatmap';
import type {IHeatmapMetricValue, IHeatmapTabletData} from '../../types/store/heatmap';
import {cn} from '../../utils/cn';
import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants';
import {formatNumber} from '../../utils/dataFormatters/dataFormatters';
Expand All @@ -32,6 +32,14 @@ export const Heatmap = ({path, database, databaseFullPath}: HeatmapProps) => {

const itemsContainer = React.useRef<HTMLDivElement | null>(null);

const [tabletTooltip, setTabletTooltip] = React.useState<{
tablet: IHeatmapTabletData;
position: {left: number; top: number};
} | null>(null);
const [tabletTooltipAnchorElement, setTabletTooltipAnchorElement] =
React.useState<HTMLDivElement | null>(null);
const isTabletTooltipHoveredRef = React.useRef(false);

const [autoRefreshInterval] = useAutoRefreshInterval();

const {currentData, isFetching, error} = heatmapApi.useGetHeatmapTabletsInfoQuery(
Expand All @@ -44,13 +52,35 @@ export const Heatmap = ({path, database, databaseFullPath}: HeatmapProps) => {
const {tablets = [], metrics} = currentData || {};
const {sort, heatmap, currentMetric} = useTypedSelector((state) => state.heatmap);

const onShowTooltip = (...args: Parameters<typeof showTooltip>) => {
dispatch(showTooltip(...args));
};
const handleShowTabletTooltip = React.useCallback(
(tablet: IHeatmapTabletData, position: {left: number; top: number}) => {
setTabletTooltip({tablet, position});
},
[],
);

const onHideTooltip = () => {
dispatch(hideTooltip());
};
const handleHideTabletTooltip = React.useCallback(() => {
setTabletTooltip(null);
}, []);

const handleRequestHideTabletTooltip = React.useCallback(() => {
setTabletTooltip((prev) => {
if (!prev || isTabletTooltipHoveredRef.current) {
return prev;
}

return null;
});
}, []);

const handleTooltipMouseEnter = React.useCallback(() => {
isTabletTooltipHoveredRef.current = true;
}, []);

const handleTooltipMouseLeave = React.useCallback(() => {
isTabletTooltipHoveredRef.current = false;
handleHideTabletTooltip();
}, [handleHideTabletTooltip]);

const handleMetricChange = (value: string[]) => {
dispatch(
Expand All @@ -76,14 +106,7 @@ export const Heatmap = ({path, database, databaseFullPath}: HeatmapProps) => {
};

const renderHistogram = () => {
return (
<Histogram
tablets={tablets}
currentMetric={currentMetric}
showTooltip={onShowTooltip}
hideTooltip={onHideTooltip}
/>
);
return <Histogram tablets={tablets} currentMetric={currentMetric} />;
};

const renderHeatmapCanvas = () => {
Expand All @@ -108,18 +131,30 @@ export const Heatmap = ({path, database, databaseFullPath}: HeatmapProps) => {

return (
<div ref={itemsContainer} className={b('items')}>
{tabletTooltip ? (
<div
key={`${tabletTooltip.position.left}-${tabletTooltip.position.top}`}
ref={setTabletTooltipAnchorElement}
className={b('tooltip-anchor')}
style={{
left: tabletTooltip.position.left,
top: tabletTooltip.position.top,
}}
/>
) : null}
<HeatmapCanvas
tablets={sortedTablets}
parentRef={itemsContainer}
showTooltip={onShowTooltip}
hideTooltip={onHideTooltip}
onShowTabletTooltip={handleShowTabletTooltip}
onHideTabletTooltip={handleRequestHideTabletTooltip}
/>
</div>
);
};

const renderContent = () => {
const {min, max} = getCurrentMetricLimits(currentMetric, tablets);
const isTabletTooltipPopupOpen = Boolean(tabletTooltip && tabletTooltipAnchorElement);

let content;
if (!error || currentData) {
Expand All @@ -128,6 +163,23 @@ export const Heatmap = ({path, database, databaseFullPath}: HeatmapProps) => {

return (
<div className={b()}>
{isTabletTooltipPopupOpen ? (
<Popup
open
hasArrow
placement={['top', 'bottom', 'left', 'right']}
anchorElement={tabletTooltipAnchorElement}
onOutsideClick={handleHideTabletTooltip}
>
<div
className={b('tooltip')}
onMouseEnter={handleTooltipMouseEnter}
onMouseLeave={handleTooltipMouseLeave}
>
<TabletTooltipContent data={tabletTooltip?.tablet} />
</div>
</Popup>
) : null}
<div className={b('filters')}>
<Select
className={b('heatmap-select')}
Expand Down
Loading
Loading