mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-17 15:36:48 +00:00
Fix: Opening logs link broken (Pref framework) (#9182)
* fix: logs popover content logic extracted out * fix: logs popover content in live view * fix: destory popover on close * feat: add logs format tests * feat: minor refactor * feat: test case refactor * feat: remove menu refs in logs live view
This commit is contained in:
parent
1aa5f5d0e1
commit
96cdf21a92
@ -26,7 +26,7 @@ interface LogsFormatOptionsMenuProps {
|
|||||||
config: OptionsMenuConfig;
|
config: OptionsMenuConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LogsFormatOptionsMenu({
|
function OptionsMenu({
|
||||||
items,
|
items,
|
||||||
selectedOptionFormat,
|
selectedOptionFormat,
|
||||||
config,
|
config,
|
||||||
@ -49,7 +49,6 @@ export default function LogsFormatOptionsMenu({
|
|||||||
const [selectedValue, setSelectedValue] = useState<string | null>(null);
|
const [selectedValue, setSelectedValue] = useState<string | null>(null);
|
||||||
const listRef = useRef<HTMLDivElement>(null);
|
const listRef = useRef<HTMLDivElement>(null);
|
||||||
const initialMouseEnterRef = useRef<boolean>(false);
|
const initialMouseEnterRef = useRef<boolean>(false);
|
||||||
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(key: LogViewMode) => {
|
(key: LogViewMode) => {
|
||||||
@ -209,7 +208,7 @@ export default function LogsFormatOptionsMenu({
|
|||||||
};
|
};
|
||||||
}, [selectedValue]);
|
}, [selectedValue]);
|
||||||
|
|
||||||
const popoverContent = (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
'nested-menu-container',
|
'nested-menu-container',
|
||||||
@ -447,15 +446,30 @@ export default function LogsFormatOptionsMenu({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function LogsFormatOptionsMenu({
|
||||||
|
items,
|
||||||
|
selectedOptionFormat,
|
||||||
|
config,
|
||||||
|
}: LogsFormatOptionsMenuProps): JSX.Element {
|
||||||
|
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
content={popoverContent}
|
content={
|
||||||
|
<OptionsMenu
|
||||||
|
items={items}
|
||||||
|
selectedOptionFormat={selectedOptionFormat}
|
||||||
|
config={config}
|
||||||
|
/>
|
||||||
|
}
|
||||||
trigger="click"
|
trigger="click"
|
||||||
placement="bottomRight"
|
placement="bottomRight"
|
||||||
arrow={false}
|
arrow={false}
|
||||||
open={isPopoverOpen}
|
open={isPopoverOpen}
|
||||||
onOpenChange={setIsPopoverOpen}
|
onOpenChange={setIsPopoverOpen}
|
||||||
rootClassName="format-options-popover"
|
rootClassName="format-options-popover"
|
||||||
|
destroyTooltipOnHide
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className="periscope-btn ghost"
|
className="periscope-btn ghost"
|
||||||
@ -465,3 +479,5 @@ export default function LogsFormatOptionsMenu({
|
|||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default LogsFormatOptionsMenu;
|
||||||
|
|||||||
@ -0,0 +1,157 @@
|
|||||||
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
|
import { fireEvent, render, waitFor } from 'tests/test-utils';
|
||||||
|
|
||||||
|
import LogsFormatOptionsMenu from '../LogsFormatOptionsMenu';
|
||||||
|
|
||||||
|
const mockUpdateFormatting = jest.fn();
|
||||||
|
|
||||||
|
jest.mock('providers/preferences/sync/usePreferenceSync', () => ({
|
||||||
|
usePreferenceSync: (): any => ({
|
||||||
|
preferences: {
|
||||||
|
columns: [],
|
||||||
|
formatting: {
|
||||||
|
maxLines: 2,
|
||||||
|
format: 'table',
|
||||||
|
fontSize: 'small',
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
updateColumns: jest.fn(),
|
||||||
|
updateFormatting: mockUpdateFormatting,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('LogsFormatOptionsMenu (unit)', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockUpdateFormatting.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
function setup(): {
|
||||||
|
getByTestId: ReturnType<typeof render>['getByTestId'];
|
||||||
|
findItemByLabel: (label: string) => Element | undefined;
|
||||||
|
formatOnChange: jest.Mock<any, any>;
|
||||||
|
maxLinesOnChange: jest.Mock<any, any>;
|
||||||
|
fontSizeOnChange: jest.Mock<any, any>;
|
||||||
|
} {
|
||||||
|
const items = [
|
||||||
|
{ key: 'raw', label: 'Raw', data: { title: 'max lines per row' } },
|
||||||
|
{ key: 'list', label: 'Default' },
|
||||||
|
{ key: 'table', label: 'Column', data: { title: 'columns' } },
|
||||||
|
];
|
||||||
|
|
||||||
|
const formatOnChange = jest.fn();
|
||||||
|
const maxLinesOnChange = jest.fn();
|
||||||
|
const fontSizeOnChange = jest.fn();
|
||||||
|
|
||||||
|
const { getByTestId } = render(
|
||||||
|
<LogsFormatOptionsMenu
|
||||||
|
items={items}
|
||||||
|
selectedOptionFormat="table"
|
||||||
|
config={{
|
||||||
|
format: { value: 'table', onChange: formatOnChange },
|
||||||
|
maxLines: { value: 2, onChange: maxLinesOnChange },
|
||||||
|
fontSize: { value: FontSize.SMALL, onChange: fontSizeOnChange },
|
||||||
|
addColumn: {
|
||||||
|
isFetching: false,
|
||||||
|
value: [],
|
||||||
|
options: [],
|
||||||
|
onFocus: jest.fn(),
|
||||||
|
onBlur: jest.fn(),
|
||||||
|
onSearch: jest.fn(),
|
||||||
|
onSelect: jest.fn(),
|
||||||
|
onRemove: jest.fn(),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Open the popover menu by default for each test
|
||||||
|
const formatButton = getByTestId('periscope-btn-format-options');
|
||||||
|
fireEvent.click(formatButton);
|
||||||
|
|
||||||
|
const getMenuItems = (): Element[] =>
|
||||||
|
Array.from(document.querySelectorAll('.menu-items .item'));
|
||||||
|
const findItemByLabel = (label: string): Element | undefined =>
|
||||||
|
getMenuItems().find((el) => (el.textContent || '').includes(label));
|
||||||
|
|
||||||
|
return {
|
||||||
|
getByTestId,
|
||||||
|
findItemByLabel,
|
||||||
|
formatOnChange,
|
||||||
|
maxLinesOnChange,
|
||||||
|
fontSizeOnChange,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Covers: opens menu, changes format selection, updates max-lines, changes font size
|
||||||
|
it('opens and toggles format selection', async () => {
|
||||||
|
const { findItemByLabel, formatOnChange } = setup();
|
||||||
|
|
||||||
|
// Assert initial selection
|
||||||
|
const columnItem = findItemByLabel('Column') as Element;
|
||||||
|
expect(document.querySelectorAll('.menu-items .item svg')).toHaveLength(1);
|
||||||
|
expect(columnItem.querySelector('svg')).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Change selection to 'Raw'
|
||||||
|
const rawItem = findItemByLabel('Raw') as Element;
|
||||||
|
fireEvent.click(rawItem as HTMLElement);
|
||||||
|
await waitFor(() => {
|
||||||
|
const rawEl = findItemByLabel('Raw') as Element;
|
||||||
|
expect(document.querySelectorAll('.menu-items .item svg')).toHaveLength(1);
|
||||||
|
expect(rawEl.querySelector('svg')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
expect(formatOnChange).toHaveBeenCalledWith('raw');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('increments max-lines and calls onChange', async () => {
|
||||||
|
const { maxLinesOnChange } = setup();
|
||||||
|
|
||||||
|
// Increment max lines
|
||||||
|
const input = document.querySelector(
|
||||||
|
'.max-lines-per-row-input input',
|
||||||
|
) as HTMLInputElement;
|
||||||
|
const initial = Number(input.value);
|
||||||
|
const buttons = document.querySelectorAll(
|
||||||
|
'.max-lines-per-row-input .periscope-btn',
|
||||||
|
);
|
||||||
|
const incrementBtn = buttons[1] as HTMLElement;
|
||||||
|
fireEvent.click(incrementBtn);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(Number(input.value)).toBe(initial + 1);
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(maxLinesOnChange).toHaveBeenCalledWith(initial + 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('changes font size to MEDIUM and calls onChange', async () => {
|
||||||
|
const { fontSizeOnChange } = setup();
|
||||||
|
// Open font dropdown
|
||||||
|
const fontButton = document.querySelector(
|
||||||
|
'.font-size-container .value',
|
||||||
|
) as HTMLElement;
|
||||||
|
fireEvent.click(fontButton);
|
||||||
|
|
||||||
|
// Choose MEDIUM
|
||||||
|
const optionButtons = Array.from(
|
||||||
|
document.querySelectorAll('.font-size-dropdown .option-btn'),
|
||||||
|
);
|
||||||
|
const mediumBtn = optionButtons[1] as HTMLElement;
|
||||||
|
fireEvent.click(mediumBtn);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(
|
||||||
|
document.querySelectorAll('.font-size-dropdown .option-btn .icon'),
|
||||||
|
).toHaveLength(1);
|
||||||
|
expect(
|
||||||
|
(optionButtons[1] as Element).querySelector('.icon'),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(fontSizeOnChange).toHaveBeenCalledWith(FontSize.MEDIUM);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import './LiveLogsContainer.styles.scss';
|
import './LiveLogsContainer.styles.scss';
|
||||||
|
|
||||||
import { Button, Switch, Typography } from 'antd';
|
import { Switch, Typography } from 'antd';
|
||||||
import LogsFormatOptionsMenu from 'components/LogsFormatOptionsMenu/LogsFormatOptionsMenu';
|
import LogsFormatOptionsMenu from 'components/LogsFormatOptionsMenu/LogsFormatOptionsMenu';
|
||||||
import { MAX_LOGS_LIST_SIZE } from 'constants/liveTail';
|
import { MAX_LOGS_LIST_SIZE } from 'constants/liveTail';
|
||||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
@ -8,10 +8,8 @@ import GoToTop from 'container/GoToTop';
|
|||||||
import { useOptionsMenu } from 'container/OptionsMenu';
|
import { useOptionsMenu } from 'container/OptionsMenu';
|
||||||
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
|
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import useClickOutside from 'hooks/useClickOutside';
|
|
||||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||||
import { useEventSourceEvent } from 'hooks/useEventSourceEvent';
|
import { useEventSourceEvent } from 'hooks/useEventSourceEvent';
|
||||||
import { Sliders } from 'lucide-react';
|
|
||||||
import { useEventSource } from 'providers/EventSource';
|
import { useEventSource } from 'providers/EventSource';
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
@ -41,9 +39,6 @@ function LiveLogsContainer(): JSX.Element {
|
|||||||
|
|
||||||
const batchedEventsRef = useRef<ILiveLogsLog[]>([]);
|
const batchedEventsRef = useRef<ILiveLogsLog[]>([]);
|
||||||
|
|
||||||
const [showFormatMenuItems, setShowFormatMenuItems] = useState(false);
|
|
||||||
const menuRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const prevFilterExpressionRef = useRef<string | null>(null);
|
const prevFilterExpressionRef = useRef<string | null>(null);
|
||||||
|
|
||||||
const { options, config } = useOptionsMenu({
|
const { options, config } = useOptionsMenu({
|
||||||
@ -73,18 +68,6 @@ function LiveLogsContainer(): JSX.Element {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleToggleShowFormatOptions = (): void =>
|
|
||||||
setShowFormatMenuItems(!showFormatMenuItems);
|
|
||||||
|
|
||||||
useClickOutside({
|
|
||||||
ref: menuRef,
|
|
||||||
onClickOutside: () => {
|
|
||||||
if (showFormatMenuItems) {
|
|
||||||
setShowFormatMenuItems(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleStartOpenConnection,
|
handleStartOpenConnection,
|
||||||
handleCloseConnection,
|
handleCloseConnection,
|
||||||
@ -231,21 +214,11 @@ function LiveLogsContainer(): JSX.Element {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="format-options-container" ref={menuRef}>
|
|
||||||
<Button
|
|
||||||
className="periscope-btn ghost"
|
|
||||||
onClick={handleToggleShowFormatOptions}
|
|
||||||
icon={<Sliders size={14} />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{showFormatMenuItems && (
|
|
||||||
<LogsFormatOptionsMenu
|
<LogsFormatOptionsMenu
|
||||||
items={formatItems}
|
items={formatItems}
|
||||||
selectedOptionFormat={options.format}
|
selectedOptionFormat={options.format}
|
||||||
config={config}
|
config={config}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showLiveLogsFrequencyChart && (
|
{showLiveLogsFrequencyChart && (
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user