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
10 changes: 9 additions & 1 deletion app/vmui/packages/vmui/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,15 @@ export default [...compat.extends(
"react/jsx-first-prop-new-line": [1, "multiline"],

// Disable core indent rule due to recursion issues in ESLint 9; use JSX-specific rules instead
indent: "off",
indent: ["error", 2, {
SwitchCase: 1,
ignoredNodes: [
"JSXElement",
"JSXElement *",
"JSXFragment",
"JSXFragment *",
],
}],
"react/jsx-indent": ["error", 2],
"react/jsx-indent-props": ["error", 2],

Expand Down
4 changes: 3 additions & 1 deletion app/vmui/packages/vmui/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ export interface LegendLogHits {

export interface LegendLogHitsMenu {
title: string;
icon?: ReactNode;
iconStart?: ReactNode;
iconEnd?: ReactNode;
shortcut?: string;
handler?: () => void;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import Popper from "../../../Main/Popper/Popper";
import useBoolean from "../../../../hooks/useBoolean";
import LegendHitsMenu from "../LegendHitsMenu/LegendHitsMenu";
import { ExtraFilter } from "../../../../pages/OverviewPage/FiltersBar/types";
import { useCallback } from "react";
import useLegendHitsVisibilityMenu from "./hooks/useLegendHitsVisibilityMenu";

interface Props {
legend: LegendLogHits;
Expand All @@ -27,6 +29,7 @@ const BarHitsLegendItem: FC<Props> = ({ legend, series, onRedrawGraph, onApplyFi
const [clickPosition, setClickPosition] = useState<{ top: number; left: number } | null>(null);

const targetSeries = useMemo(() => series.find(s => s.label === legend.label), [series]);
const isOnlyTargetVisible = series.every(s => s === targetSeries || !s.show);

const fields = useMemo(() => getStreamPairs(legend.label), [legend.label]);

Expand All @@ -39,6 +42,47 @@ const BarHitsLegendItem: FC<Props> = ({ legend, series, onRedrawGraph, onApplyFi
handleOpenContextMenu();
};

const handleVisibilityToggle = useCallback(() => {
if (!targetSeries) return;
targetSeries.show = !targetSeries.show;
onRedrawGraph();
handleCloseContextMenu();
}, [targetSeries, onRedrawGraph, handleCloseContextMenu]);

const handleFocusToggle = useCallback(() => {
series.forEach(s => {
s.show = isOnlyTargetVisible || (s === targetSeries);
});
onRedrawGraph();
handleCloseContextMenu();
}, [series, isOnlyTargetVisible, targetSeries, onRedrawGraph, handleCloseContextMenu]);

const handleClickByLegend = (e: MouseEvent<HTMLDivElement>) => {
const { ctrlKey, metaKey, altKey } = e;

// alt + key // see useLegendHitsVisibilityMenu.tsx
if (altKey) {
handleVisibilityToggle();
return;
}

// cmd/ctrl + click // see useLegendHitsVisibilityMenu.tsx
const ctrlMetaKey = ctrlKey || metaKey;
if (ctrlMetaKey) {
handleFocusToggle();
return;
}

handleContextMenu(e);
};

const optionsVisibilitySection = useLegendHitsVisibilityMenu({
targetSeries,
isOnlyTargetVisible,
handleVisibilityToggle,
handleFocusToggle
});

return (
<div
ref={legendRef}
Expand All @@ -48,7 +92,7 @@ const BarHitsLegendItem: FC<Props> = ({ legend, series, onRedrawGraph, onApplyFi
"vm-bar-hits-legend-item_active": openContextMenu,
"vm-bar-hits-legend-item_hide": !targetSeries?.show,
})}
onClick={handleContextMenu}
onClick={handleClickByLegend}
>
<div
className="vm-bar-hits-legend-item__marker"
Expand All @@ -67,9 +111,8 @@ const BarHitsLegendItem: FC<Props> = ({ legend, series, onRedrawGraph, onApplyFi
<LegendHitsMenu
legend={legend}
fields={fields}
series={series}
optionsVisibilitySection={optionsVisibilitySection}
onApplyFilter={onApplyFilter}
onRedrawGraph={onRedrawGraph}
onClose={handleCloseContextMenu}
/>
</Popper>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { LegendLogHitsMenu } from "../../../../../api/types";
import { useMemo } from "react";
import { FocusIcon, UnfocusIcon, VisibilityIcon, VisibilityOffIcon } from "../../../../Main/Icons";
import { altKeyLabel, ctrlKeyLabel } from "../../../../../utils/keyboard";
import { Series } from "uplot";

type Props = {
targetSeries?: Series;
isOnlyTargetVisible: boolean;
handleVisibilityToggle: () => void;
handleFocusToggle: () => void;
}

const useLegendHitsVisibilityMenu = ({
targetSeries,
isOnlyTargetVisible,
handleVisibilityToggle,
handleFocusToggle
}: Props): LegendLogHitsMenu[] => {
const isShow = Boolean(targetSeries?.show);

return useMemo(() => [
{
title: isShow ? "Hide this series" : "Show this series",
iconStart: isShow ? <VisibilityOffIcon/> : <VisibilityIcon/>,
shortcut: `${altKeyLabel} + Click`, // handled in BarHitsLegendItem.tsx
handler: handleVisibilityToggle,
},
{
title: isOnlyTargetVisible ? "Show all series" : "Show only this series",
iconStart: isOnlyTargetVisible ? <UnfocusIcon/> : <FocusIcon/>,
shortcut: `${ctrlKeyLabel} + Click`, // handled in BarHitsLegendItem.tsx
handler: handleFocusToggle,
},
], [isOnlyTargetVisible, isShow, handleVisibilityToggle, handleFocusToggle]);
};

export default useLegendHitsVisibilityMenu;
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { FC } from "preact/compat";
import { Series } from "uplot";
import "./style.scss";
import { LegendLogHits } from "../../../../api/types";
import { LegendLogHits, LegendLogHitsMenu } from "../../../../api/types";
import LegendHitsMenuStats from "./LegendHitsMenuStats";
import LegendHitsMenuBase from "./LegendHitsMenuBase";
import LegendHitsMenuRow from "./LegendHitsMenuRow";
Expand All @@ -10,32 +9,20 @@ import { LOGS_LIMIT_HITS } from "../../../../constants/logs";
import LegendHitsMenuVisibility from "./LegendHitsMenuVisibility";
import { ExtraFilter } from "../../../../pages/OverviewPage/FiltersBar/types";

const otherDescription = `aggregated results for fields not in the top ${LOGS_LIMIT_HITS}`;
const otherDescription = `Aggregated results for fields not in the top ${LOGS_LIMIT_HITS}`;

interface Props {
legend: LegendLogHits;
fields: string[];
series: Series[];
optionsVisibilitySection: LegendLogHitsMenu[];
onApplyFilter: (value: ExtraFilter) => void;
onRedrawGraph: () => void;
onClose: () => void;
}

const LegendHitsMenu: FC<Props> = ({ legend, fields, series, onApplyFilter, onRedrawGraph, onClose }) => {
const LegendHitsMenu: FC<Props> = ({ legend, fields, optionsVisibilitySection, onApplyFilter, onClose }) => {
return (
<div className="vm-legend-hits-menu">
{legend.isOther && (
<div className="vm-legend-hits-menu-section vm-legend-hits-menu-section_info">
<LegendHitsMenuRow title={otherDescription}/>
</div>
)}

<LegendHitsMenuVisibility
legend={legend}
series={series}
onRedrawGraph={onRedrawGraph}
onClose={onClose}
/>
<LegendHitsMenuVisibility options={optionsVisibilitySection} />

{!legend.isOther && (
<LegendHitsMenuBase
Expand All @@ -54,6 +41,12 @@ const LegendHitsMenu: FC<Props> = ({ legend, fields, series, onApplyFilter, onRe
)}

<LegendHitsMenuStats legend={legend}/>

{legend.isOther && (
<div className="vm-legend-hits-menu-section vm-legend-hits-menu-section_info">
<LegendHitsMenuRow title={otherDescription}/>
</div>
)}
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,29 +33,27 @@ const LegendHitsMenuBase: FC<Props> = ({ legend, onApplyFilter, onClose }) => {
const options: LegendLogHitsMenu[] = [
{
title: `Copy ${groupFieldHits} name`,
icon: <CopyIcon/>,
iconStart: <CopyIcon/>,
handler: handlerCopyLabel,
},
{
title: `Add ${groupFieldHits} to filter`,
icon: <FilterIcon/>,
iconStart: <FilterIcon/>,
handler: handleAddStreamToFilter(ExtraFilterOperator.Equals),
},
{
title: `Exclude ${groupFieldHits} to filter`,
icon: <FilterOffIcon/>,
iconStart: <FilterOffIcon/>,
handler: handleAddStreamToFilter(ExtraFilterOperator.NotEquals),
}
];

return (
<div className="vm-legend-hits-menu-section">
{options.map(({ icon, title, handler }) => (
{options.map(({ ...menuProps }) => (
<LegendHitsMenuRow
key={title}
iconStart={icon}
title={title}
handler={handler}
key={menuProps.title}
{...menuProps}
/>
))}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,17 @@ const LegendHitsMenuFields: FC<Props> = ({ fields, onApplyFilter, onClose }) =>
return [
{
title: "Copy",
icon: <CopyIcon/>,
iconStart: <CopyIcon/>,
handler: handleCopy(field),
},
{
title: "Add to filter",
icon: <FilterIcon/>,
iconStart: <FilterIcon/>,
handler: handleAddToFilter(field, ExtraFilterOperator.Equals),
},
{
title: "Exclude to filter",
icon: <FilterOffIcon/>,
iconStart: <FilterOffIcon/>,
handler: handleAddToFilter(field, ExtraFilterOperator.NotEquals),
}
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import useClickOutside from "../../../../hooks/useClickOutside";

interface Props {
title: string;
shortcut?: string;
handler?: () => void;
iconStart?: ReactNode;
iconEnd?: ReactNode;
className?: string;
submenu?: LegendLogHitsMenu[];
}

const LegendHitsMenuRow: FC<Props> = ({ title, handler, iconStart, iconEnd, className, submenu }) => {
const LegendHitsMenuRow: FC<Props> = ({ title, shortcut, handler, iconStart, iconEnd, className, submenu }) => {
const containerRef = useRef<HTMLDivElement>(null);
const titleRef = useRef<HTMLDivElement>(null);
const submenuRef = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -79,6 +80,7 @@ const LegendHitsMenuRow: FC<Props> = ({ title, handler, iconStart, iconEnd, clas
{iconStart && <div className="vm-legend-hits-menu-row__icon">{iconStart}</div>}
{isOverflownTitle ? (<Tooltip title={title}>{titleContent}</Tooltip>) : titleContent}
{iconEnd && !hasSubmenu && <div className="vm-legend-hits-menu-row__icon">{iconEnd}</div>}
{shortcut && <div className="vm-legend-hits-menu-row__shortcut">{shortcut}</div>}

{hasSubmenu && (
<div className="vm-legend-hits-menu-row__icon vm-legend-hits-menu-row__icon_drop">
Expand All @@ -96,12 +98,10 @@ const LegendHitsMenuRow: FC<Props> = ({ title, handler, iconStart, iconEnd, clas
})}
>
<div className="vm-legend-hits-menu-section">
{submenu.map(({ icon, title, handler }) => (
{submenu.map(({ ...menuProps }) => (
<LegendHitsMenuRow
key={title}
iconStart={icon}
title={title}
handler={handler}
key={menuProps.title}
{...menuProps}
/>
))}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,60 +1,18 @@
import { FC, useCallback } from "preact/compat";
import { FC } from "preact/compat";
import LegendHitsMenuRow from "./LegendHitsMenuRow";
import { FocusIcon, UnfocusIcon, VisibilityIcon, VisibilityOffIcon } from "../../../Main/Icons";
import { LegendLogHits, LegendLogHitsMenu } from "../../../../api/types";
import { Series } from "uplot";
import { useMemo } from "react";
import { LegendLogHitsMenu } from "../../../../api/types";

interface Props {
legend: LegendLogHits;
series: Series[];
onRedrawGraph: () => void;
onClose: () => void;
export interface LegendMenuVisibilityProps {
options: LegendLogHitsMenu[]
}

const LegendHitsMenuVisibility: FC<Props> = ({ legend, series, onRedrawGraph, onClose }) => {

const targetSeries = useMemo(() => series.find(s => s.label === legend.label), [series]);

const isShow = Boolean(targetSeries?.show);
const isOnlyTargetVisible = series.every(s => s === targetSeries || !s.show);

const handleVisibilityToggle = useCallback(() => {
if (!targetSeries) return;
targetSeries.show = !targetSeries.show;
onRedrawGraph();
onClose();
}, [targetSeries, onRedrawGraph, onClose]);

const handleFocusToggle = useCallback(() => {
series.forEach(s => {
s.show = isOnlyTargetVisible || (s === targetSeries);
});
onRedrawGraph();
onClose();
}, [series, isOnlyTargetVisible, targetSeries, onRedrawGraph, onClose]);

const options: LegendLogHitsMenu[] = useMemo(() => [
{
title: isShow ? "Hide series" : "Show series",
icon: isShow ? <VisibilityOffIcon/> : <VisibilityIcon/>,
handler: handleVisibilityToggle,
},
{
title: isOnlyTargetVisible ? "Show all series" : "Focus on series",
icon: isOnlyTargetVisible ? <UnfocusIcon/> : <FocusIcon/>,
handler: handleFocusToggle,
},
], [isOnlyTargetVisible, isShow, handleFocusToggle, handleVisibilityToggle]);

const LegendHitsMenuVisibility: FC<LegendMenuVisibilityProps> = ({ options }) => {
return (
<div className="vm-legend-hits-menu-section">
{options.map(({ icon, title, handler }) => (
{options.map(({ ...menuProps }) => (
<LegendHitsMenuRow
key={title}
iconStart={icon}
title={title}
handler={handler}
key={menuProps.title}
{...menuProps}
/>
))}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
&_info {
font-size: $font-size-small;
font-weight: 500;
font-style: italic;
}
}

Expand Down Expand Up @@ -78,6 +79,12 @@
overflow: hidden;
text-overflow: ellipsis;
}

&__shortcut {
font-size: $font-size-small;
color: $color-text-secondary;
white-space: nowrap;
}
}

&-other-list {
Expand Down
Loading