Compare commits
3 Commits
main
...
feat/log-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22d1b90e2a | ||
|
|
aa9a2863af | ||
|
|
c5fddb2e09 |
@@ -80,12 +80,32 @@ function LogDetailInner({
|
||||
return stagedQuery.builder.queryData.find((item) => !item.disabled) || null;
|
||||
}, [stagedQuery]);
|
||||
|
||||
const { options } = useOptionsMenu({
|
||||
const { options, config } = useOptionsMenu({
|
||||
storageKey: LOCALSTORAGE.LOGS_LIST_OPTIONS,
|
||||
dataSource: DataSource.LOGS,
|
||||
aggregateOperator: listQuery?.aggregateOperator || StringOperators.NOOP,
|
||||
});
|
||||
|
||||
const handleAddColumn = useCallback(
|
||||
(fieldName: string): void => {
|
||||
if (config?.addColumn?.onSelect) {
|
||||
// onSelect from SelectProps has signature (value, option), but handleSelectColumns only needs value
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
config.addColumn.onSelect(fieldName, {} as any);
|
||||
}
|
||||
},
|
||||
[config],
|
||||
);
|
||||
|
||||
const handleRemoveColumn = useCallback(
|
||||
(fieldName: string): void => {
|
||||
if (config?.addColumn?.onRemove) {
|
||||
config.addColumn.onRemove(fieldName);
|
||||
}
|
||||
},
|
||||
[config],
|
||||
);
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const location = useLocation();
|
||||
const { safeNavigate } = useSafeNavigate();
|
||||
@@ -369,6 +389,8 @@ function LogDetailInner({
|
||||
isListViewPanel={isListViewPanel}
|
||||
selectedOptions={options}
|
||||
listViewPanelSelectedFields={listViewPanelSelectedFields}
|
||||
onAddColumn={handleAddColumn}
|
||||
onRemoveColumn={handleRemoveColumn}
|
||||
/>
|
||||
)}
|
||||
{selectedView === VIEW_TYPES.JSON && <JSONView logData={log} />}
|
||||
|
||||
@@ -30,6 +30,8 @@ interface OverviewProps {
|
||||
selectedOptions: OptionsQuery;
|
||||
listViewPanelSelectedFields?: IField[] | null;
|
||||
onGroupByAttribute?: (fieldKey: string, dataType?: DataTypes) => Promise<void>;
|
||||
onAddColumn?: (fieldName: string) => void;
|
||||
onRemoveColumn?: (fieldName: string) => void;
|
||||
}
|
||||
|
||||
type Props = OverviewProps &
|
||||
@@ -44,6 +46,8 @@ function Overview({
|
||||
selectedOptions,
|
||||
onGroupByAttribute,
|
||||
listViewPanelSelectedFields,
|
||||
onAddColumn,
|
||||
onRemoveColumn,
|
||||
}: Props): JSX.Element {
|
||||
const [isWrapWord, setIsWrapWord] = useState<boolean>(true);
|
||||
const [isSearchVisible, setIsSearchVisible] = useState<boolean>(false);
|
||||
@@ -213,6 +217,8 @@ function Overview({
|
||||
isListViewPanel={isListViewPanel}
|
||||
selectedOptions={selectedOptions}
|
||||
listViewPanelSelectedFields={listViewPanelSelectedFields}
|
||||
onAddColumn={onAddColumn}
|
||||
onRemoveColumn={onRemoveColumn}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
@@ -228,6 +234,8 @@ Overview.defaultProps = {
|
||||
isListViewPanel: false,
|
||||
listViewPanelSelectedFields: null,
|
||||
onGroupByAttribute: undefined,
|
||||
onAddColumn: undefined,
|
||||
onRemoveColumn: undefined,
|
||||
};
|
||||
|
||||
export default Overview;
|
||||
|
||||
@@ -48,6 +48,8 @@ interface TableViewProps {
|
||||
isListViewPanel?: boolean;
|
||||
listViewPanelSelectedFields?: IField[] | null;
|
||||
onGroupByAttribute?: (fieldKey: string, dataType?: DataTypes) => Promise<void>;
|
||||
onAddColumn?: (fieldName: string) => void;
|
||||
onRemoveColumn?: (fieldName: string) => void;
|
||||
}
|
||||
|
||||
type Props = TableViewProps &
|
||||
@@ -63,6 +65,8 @@ function TableView({
|
||||
selectedOptions,
|
||||
onGroupByAttribute,
|
||||
listViewPanelSelectedFields,
|
||||
onAddColumn,
|
||||
onRemoveColumn,
|
||||
}: Props): JSX.Element | null {
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
const [isfilterInLoading, setIsFilterInLoading] = useState<boolean>(false);
|
||||
@@ -292,6 +296,9 @@ function TableView({
|
||||
isfilterOutLoading={isfilterOutLoading}
|
||||
onClickHandler={onClickHandler}
|
||||
onGroupByAttribute={onGroupByAttribute}
|
||||
onAddColumn={onAddColumn}
|
||||
onRemoveColumn={onRemoveColumn}
|
||||
selectedOptions={selectedOptions}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@@ -335,6 +342,8 @@ TableView.defaultProps = {
|
||||
isListViewPanel: false,
|
||||
listViewPanelSelectedFields: null,
|
||||
onGroupByAttribute: undefined,
|
||||
onAddColumn: undefined,
|
||||
onRemoveColumn: undefined,
|
||||
};
|
||||
|
||||
export interface DataType {
|
||||
|
||||
@@ -11,7 +11,14 @@ import { OPERATORS } from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { RESTRICTED_SELECTED_FIELDS } from 'container/LogsFilters/config';
|
||||
import { MetricsType } from 'container/MetricsApplication/constant';
|
||||
import { ArrowDownToDot, ArrowUpFromDot, Ellipsis } from 'lucide-react';
|
||||
import { OptionsQuery } from 'container/OptionsMenu/types';
|
||||
import {
|
||||
ArrowDownToDot,
|
||||
ArrowUpFromDot,
|
||||
Ellipsis,
|
||||
Minus,
|
||||
Plus,
|
||||
} from 'lucide-react';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
@@ -34,6 +41,9 @@ interface ITableViewActionsProps {
|
||||
isfilterInLoading: boolean;
|
||||
isfilterOutLoading: boolean;
|
||||
onGroupByAttribute?: (fieldKey: string, dataType?: DataTypes) => Promise<void>;
|
||||
onAddColumn?: (fieldName: string) => void;
|
||||
onRemoveColumn?: (fieldName: string) => void;
|
||||
selectedOptions?: OptionsQuery;
|
||||
onClickHandler: (
|
||||
operator: string,
|
||||
fieldKey: string,
|
||||
@@ -105,6 +115,7 @@ const BodyContent: React.FC<{
|
||||
|
||||
BodyContent.displayName = 'BodyContent';
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
export default function TableViewActions(
|
||||
props: ITableViewActionsProps,
|
||||
): React.ReactElement {
|
||||
@@ -116,6 +127,9 @@ export default function TableViewActions(
|
||||
isfilterOutLoading,
|
||||
onClickHandler,
|
||||
onGroupByAttribute,
|
||||
onAddColumn,
|
||||
onRemoveColumn,
|
||||
selectedOptions,
|
||||
} = props;
|
||||
|
||||
const { pathname } = useLocation();
|
||||
@@ -142,6 +156,13 @@ export default function TableViewActions(
|
||||
|
||||
const fieldFilterKey = filterKeyForField(fieldData.field);
|
||||
|
||||
const isFieldInSelectedColumns = useMemo(() => {
|
||||
if (!selectedOptions?.selectColumns) return false;
|
||||
return selectedOptions.selectColumns.some(
|
||||
(col) => col.name === fieldFilterKey,
|
||||
);
|
||||
}, [selectedOptions, fieldFilterKey]);
|
||||
|
||||
// Memoize textToCopy computation
|
||||
const textToCopy = useMemo(() => {
|
||||
let text = fieldData.value;
|
||||
@@ -250,6 +271,32 @@ export default function TableViewActions(
|
||||
arrow={false}
|
||||
content={
|
||||
<div>
|
||||
{onAddColumn && !isFieldInSelectedColumns && (
|
||||
<Button
|
||||
className="group-by-clause"
|
||||
type="text"
|
||||
icon={<Plus size={14} />}
|
||||
onClick={(): void => {
|
||||
onAddColumn(fieldFilterKey);
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
Add to Columns
|
||||
</Button>
|
||||
)}
|
||||
{onRemoveColumn && isFieldInSelectedColumns && (
|
||||
<Button
|
||||
className="group-by-clause"
|
||||
type="text"
|
||||
icon={<Minus size={14} />}
|
||||
onClick={(): void => {
|
||||
onRemoveColumn(fieldFilterKey);
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
Remove from Columns
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
className="group-by-clause"
|
||||
type="text"
|
||||
@@ -330,6 +377,32 @@ export default function TableViewActions(
|
||||
arrow={false}
|
||||
content={
|
||||
<div>
|
||||
{onAddColumn && !isFieldInSelectedColumns && (
|
||||
<Button
|
||||
className="group-by-clause"
|
||||
type="text"
|
||||
icon={<Plus size={14} />}
|
||||
onClick={(): void => {
|
||||
onAddColumn(fieldFilterKey);
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
Add to Columns
|
||||
</Button>
|
||||
)}
|
||||
{onRemoveColumn && isFieldInSelectedColumns && (
|
||||
<Button
|
||||
className="group-by-clause"
|
||||
type="text"
|
||||
icon={<Minus size={14} />}
|
||||
onClick={(): void => {
|
||||
onRemoveColumn(fieldFilterKey);
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
Remove from Columns
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
className="group-by-clause"
|
||||
type="text"
|
||||
@@ -360,4 +433,7 @@ export default function TableViewActions(
|
||||
|
||||
TableViewActions.defaultProps = {
|
||||
onGroupByAttribute: undefined,
|
||||
onAddColumn: undefined,
|
||||
onRemoveColumn: undefined,
|
||||
selectedOptions: undefined,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { RESTRICTED_SELECTED_FIELDS } from 'container/LogsFilters/config';
|
||||
import { LogViewMode } from 'container/LogsTable';
|
||||
import { FontSize } from 'container/OptionsMenu/types';
|
||||
|
||||
import TableViewActions from '../TableViewActions';
|
||||
|
||||
@@ -33,17 +35,32 @@ jest.mock('react-router-dom', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('../useAsyncJSONProcessing', () => ({
|
||||
__esModule: true,
|
||||
default: (): {
|
||||
isLoading: boolean;
|
||||
treeData: unknown[] | null;
|
||||
error: string | null;
|
||||
} => ({
|
||||
isLoading: false,
|
||||
treeData: null,
|
||||
error: null,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('TableViewActions', () => {
|
||||
const TEST_VALUE = 'test value';
|
||||
const ACTION_BUTTON_TEST_ID = '.action-btn';
|
||||
const TEST_FIELD = 'test-field';
|
||||
|
||||
const defaultProps = {
|
||||
fieldData: {
|
||||
field: 'test-field',
|
||||
field: TEST_FIELD,
|
||||
value: TEST_VALUE,
|
||||
},
|
||||
record: {
|
||||
key: 'test-key',
|
||||
field: 'test-field',
|
||||
field: TEST_FIELD,
|
||||
value: TEST_VALUE,
|
||||
},
|
||||
isListViewPanel: false,
|
||||
@@ -127,4 +144,134 @@ describe('TableViewActions', () => {
|
||||
container.querySelector(ACTION_BUTTON_TEST_ID),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('Add/Remove Column functionality', () => {
|
||||
const ADD_TO_COLUMNS_TEXT = 'Add to Columns';
|
||||
const REMOVE_FROM_COLUMNS_TEXT = 'Remove from Columns';
|
||||
|
||||
const getEllipsisButton = (container: HTMLElement): HTMLElement => {
|
||||
const buttons = container.querySelectorAll('.filter-btn.periscope-btn');
|
||||
return buttons[buttons.length - 1] as HTMLElement;
|
||||
};
|
||||
|
||||
const defaultSelectedOptions = {
|
||||
selectColumns: [],
|
||||
maxLines: 1,
|
||||
format: 'table' as LogViewMode,
|
||||
fontSize: FontSize.MEDIUM,
|
||||
};
|
||||
|
||||
it('shows Add to Columns button when field is not selected', async () => {
|
||||
const onAddColumn = jest.fn();
|
||||
const { container } = render(
|
||||
<TableViewActions
|
||||
fieldData={defaultProps.fieldData}
|
||||
record={defaultProps.record}
|
||||
isListViewPanel={defaultProps.isListViewPanel}
|
||||
isfilterInLoading={defaultProps.isfilterInLoading}
|
||||
isfilterOutLoading={defaultProps.isfilterOutLoading}
|
||||
onClickHandler={defaultProps.onClickHandler}
|
||||
onGroupByAttribute={defaultProps.onGroupByAttribute}
|
||||
onAddColumn={onAddColumn}
|
||||
selectedOptions={defaultSelectedOptions}
|
||||
/>,
|
||||
);
|
||||
|
||||
const ellipsisButton = getEllipsisButton(container);
|
||||
fireEvent.mouseOver(ellipsisButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(ADD_TO_COLUMNS_TEXT)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it(`calls onAddColumn with correct field key when ${ADD_TO_COLUMNS_TEXT} is clicked`, async () => {
|
||||
const onAddColumn = jest.fn();
|
||||
const { container } = render(
|
||||
<TableViewActions
|
||||
fieldData={defaultProps.fieldData}
|
||||
record={defaultProps.record}
|
||||
isListViewPanel={defaultProps.isListViewPanel}
|
||||
isfilterInLoading={defaultProps.isfilterInLoading}
|
||||
isfilterOutLoading={defaultProps.isfilterOutLoading}
|
||||
onClickHandler={defaultProps.onClickHandler}
|
||||
onGroupByAttribute={defaultProps.onGroupByAttribute}
|
||||
onAddColumn={onAddColumn}
|
||||
selectedOptions={defaultSelectedOptions}
|
||||
/>,
|
||||
);
|
||||
|
||||
const ellipsisButton = getEllipsisButton(container);
|
||||
fireEvent.mouseOver(ellipsisButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(ADD_TO_COLUMNS_TEXT)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const addButton = screen.getByText(ADD_TO_COLUMNS_TEXT);
|
||||
fireEvent.click(addButton);
|
||||
|
||||
expect(onAddColumn).toHaveBeenCalledWith(TEST_FIELD);
|
||||
});
|
||||
|
||||
it('shows Remove from Columns button when field is already selected', async () => {
|
||||
const onRemoveColumn = jest.fn();
|
||||
const { container } = render(
|
||||
<TableViewActions
|
||||
fieldData={defaultProps.fieldData}
|
||||
record={defaultProps.record}
|
||||
isListViewPanel={defaultProps.isListViewPanel}
|
||||
isfilterInLoading={defaultProps.isfilterInLoading}
|
||||
isfilterOutLoading={defaultProps.isfilterOutLoading}
|
||||
onClickHandler={defaultProps.onClickHandler}
|
||||
onGroupByAttribute={defaultProps.onGroupByAttribute}
|
||||
onRemoveColumn={onRemoveColumn}
|
||||
selectedOptions={{
|
||||
...defaultSelectedOptions,
|
||||
selectColumns: [{ name: TEST_FIELD }],
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
const ellipsisButton = getEllipsisButton(container);
|
||||
fireEvent.mouseOver(ellipsisButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(REMOVE_FROM_COLUMNS_TEXT)).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.queryByText(ADD_TO_COLUMNS_TEXT)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it(`calls onRemoveColumn with correct field key when ${REMOVE_FROM_COLUMNS_TEXT} is clicked`, async () => {
|
||||
const onRemoveColumn = jest.fn();
|
||||
const { container } = render(
|
||||
<TableViewActions
|
||||
fieldData={defaultProps.fieldData}
|
||||
record={defaultProps.record}
|
||||
isListViewPanel={defaultProps.isListViewPanel}
|
||||
isfilterInLoading={defaultProps.isfilterInLoading}
|
||||
isfilterOutLoading={defaultProps.isfilterOutLoading}
|
||||
onClickHandler={defaultProps.onClickHandler}
|
||||
onGroupByAttribute={defaultProps.onGroupByAttribute}
|
||||
onRemoveColumn={onRemoveColumn}
|
||||
selectedOptions={{
|
||||
...defaultSelectedOptions,
|
||||
selectColumns: [{ name: TEST_FIELD }],
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
const ellipsisButton = getEllipsisButton(container);
|
||||
fireEvent.mouseOver(ellipsisButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Remove from Columns')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const removeButton = screen.getByText(REMOVE_FROM_COLUMNS_TEXT);
|
||||
fireEvent.click(removeButton);
|
||||
|
||||
expect(onRemoveColumn).toHaveBeenCalledWith(TEST_FIELD);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -170,7 +170,7 @@ const useOptionsMenu = ({
|
||||
...initialQueryParamsV5,
|
||||
searchText: debouncedSearchText,
|
||||
},
|
||||
{ queryKey: [debouncedSearchText, isFocused], enabled: isFocused },
|
||||
{ queryKey: [debouncedSearchText, isFocused] },
|
||||
);
|
||||
|
||||
// const {
|
||||
@@ -186,7 +186,7 @@ const useOptionsMenu = ({
|
||||
|
||||
const searchedAttributeKeys: TelemetryFieldKey[] = useMemo(() => {
|
||||
const searchedAttributesDataList = Object.values(
|
||||
searchedAttributesDataV5?.data.data.keys || {},
|
||||
searchedAttributesDataV5?.data.data?.keys || {},
|
||||
).flat();
|
||||
if (searchedAttributesDataList.length) {
|
||||
if (dataSource === DataSource.LOGS) {
|
||||
@@ -230,7 +230,7 @@ const useOptionsMenu = ({
|
||||
}
|
||||
|
||||
return [];
|
||||
}, [dataSource, searchedAttributesDataV5?.data.data.keys]);
|
||||
}, [dataSource, searchedAttributesDataV5?.data.data?.keys]);
|
||||
|
||||
const initialOptionsQuery: OptionsQuery = useMemo(() => {
|
||||
let defaultColumns: TelemetryFieldKey[] = defaultOptionsQuery.selectColumns;
|
||||
|
||||
Reference in New Issue
Block a user