Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NETOBSERV-609 UI: Resize a column in flow table #238

Merged
merged 2 commits into from
Dec 5, 2022
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
1 change: 1 addition & 0 deletions web/src/components/modals/__tests__/columns-modal.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ describe('<ColumnsModal />', () => {
setModalOpen: jest.fn(),
columns: ShuffledDefaultColumns,
setColumns: jest.fn(),
setColumnSizes: jest.fn(),
id: 'columns-modal'
};
it('should render component', async () => {
Expand Down
41 changes: 26 additions & 15 deletions web/src/components/modals/columns-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,37 @@
import * as React from 'react';
import {
Button,
DataList,
DataListCell,
DataListCheck,
DataListControl,
DataListDragButton,
DataListItem,
DataListItemCells,
DataListItemRow,
DataListDragButton,
DataListCheck,
DataListCell,
DragDrop,
Draggable,
Droppable,
DataListItemCells,
Text,
TextContent,
TextVariants,
Tooltip
} from '@patternfly/react-core';
import Modal from './modal';
import { useTranslation } from 'react-i18next';
import { Column, getDefaultColumns, getFullColumnName } from '../../utils/columns';
import * as _ from 'lodash';
import * as React from 'react';
Copy link
Member

Choose a reason for hiding this comment

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

out of curiosity: do you use something to reorder imports, or was that manual? Just to understand if there's a rationale for this reordering, no problem per se.
BTW I guess it would be cool, and help reduce the number of conflicts, if we all used something to reorder imports

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This comes from VSCode ``Organize importsusingShift` + `Alt` + `O`

We can add sort imports in eslint
https://eslint.org/docs/latest/rules/sort-imports
and maybe need to tweak auto fix
https://github.com/aladdin-add/eslint-plugin/tree/master/packages/autofix

import { useTranslation } from 'react-i18next';
import { Column, ColumnSizeMap, getDefaultColumns, getFullColumnName } from '../../utils/columns';
import './columns-modal.css';
import Modal from './modal';

export const ColumnsModal: React.FC<{
isModalOpen: boolean;
setModalOpen: (v: boolean) => void;
columns: Column[];
setColumns: (v: Column[]) => void;
setColumnSizes: (v: ColumnSizeMap) => void;
id?: string;
}> = ({ id, isModalOpen, setModalOpen, columns, setColumns }) => {
}> = ({ id, isModalOpen, setModalOpen, columns, setColumns, setColumnSizes }) => {
const [resetClicked, setResetClicked] = React.useState<boolean>(false);
const [updatedColumns, setUpdatedColumns] = React.useState<Column[]>([]);
const [isSaveDisabled, setSaveDisabled] = React.useState<boolean>(true);
const [isAllSelected, setAllSelected] = React.useState<boolean>(false);
Expand Down Expand Up @@ -80,8 +82,9 @@ export const ColumnsModal: React.FC<{
);

const onReset = React.useCallback(() => {
setResetClicked(true);
setUpdatedColumns(getDefaultColumns(t));
}, [setUpdatedColumns, t]);
}, [setResetClicked, setUpdatedColumns, t]);

const onSelectAll = React.useCallback(() => {
const result = [...updatedColumns];
Expand All @@ -91,10 +94,18 @@ export const ColumnsModal: React.FC<{
setUpdatedColumns(result);
}, [updatedColumns, setUpdatedColumns, isAllSelected]);

const onClose = React.useCallback(() => {
setResetClicked(false);
setModalOpen(false);
}, [setModalOpen]);

const onSave = React.useCallback(() => {
if (resetClicked) {
setColumnSizes({});
}
setColumns(updatedColumns);
setModalOpen(false);
}, [updatedColumns, setColumns, setModalOpen]);
onClose();
}, [resetClicked, setColumns, updatedColumns, onClose, setColumnSizes]);

const draggableItems = updatedColumns.map((column, i) => (
<Draggable key={i} hasNoWrapper>
Expand Down Expand Up @@ -134,7 +145,7 @@ export const ColumnsModal: React.FC<{
title={t('Manage columns')}
isOpen={isModalOpen}
scrollable={true}
onClose={() => setModalOpen(false)}
onClose={onClose}
description={
<TextContent>
<Text component={TextVariants.p}>
Expand All @@ -151,10 +162,10 @@ export const ColumnsModal: React.FC<{
<Button data-test="columns-reset-button" key="reset" variant="link" onClick={() => onReset()}>
{t('Restore default columns')}
</Button>
<Button data-test="columns-cancel-button" key="cancel" variant="link" onClick={() => setModalOpen(false)}>
<Button data-test="columns-cancel-button" key="cancel" variant="link" onClick={() => onClose()}>
{t('Cancel')}
</Button>
<Tooltip content={t('At least one column must be selected')} isVisible={isSaveDisabled}>
<Tooltip content={t('At least one column must be selected')} trigger="" isVisible={isSaveDisabled}>
<Button
data-test="columns-save-button"
isDisabled={isSaveDisabled}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SortByDirection, TableComposable, Tbody, Th, Thead, Tr } from '@patternfly/react-table';
import { mount } from 'enzyme';
import * as React from 'react';
import { Column, ColumnsId } from '../../../utils/columns';
import { Column, ColumnsId, ColumnSizeMap } from '../../../utils/columns';
import { AllSelectedColumns, DefaultColumns, filterOrderedColumnsByIds } from '../../__tests-data__/columns';
import { NetflowTableHeader } from '../netflow-table-header';

Expand All @@ -11,7 +11,9 @@ const NetflowTableHeaderWrapper: React.FC<{
sortDirection: SortByDirection;
columns: Column[];
setColumns: (v: Column[]) => void;
}> = ({ onSort, sortId, sortDirection, columns, setColumns }) => {
columnSizes: ColumnSizeMap;
setColumnSizes: (v: ColumnSizeMap) => void;
}> = ({ onSort, sortId, sortDirection, columns, setColumns, columnSizes, setColumnSizes }) => {
return (
<TableComposable aria-label="Misc table" variant="compact">
<NetflowTableHeader
Expand All @@ -20,6 +22,8 @@ const NetflowTableHeaderWrapper: React.FC<{
sortId={sortId}
columns={columns}
setColumns={setColumns}
columnSizes={columnSizes}
setColumnSizes={setColumnSizes}
tableWidth={100}
/>
<Tbody></Tbody>
Expand All @@ -33,7 +37,9 @@ describe('<NetflowTableHeader />', () => {
sortId: ColumnsId.endtime,
sortDirection: SortByDirection.asc,
tableWidth: 100,
setColumns: jest.fn()
setColumns: jest.fn(),
columnSizes: {},
setColumnSizes: jest.fn()
};
it('should render component', async () => {
const wrapper = mount(<NetflowTableHeaderWrapper {...mocks} columns={AllSelectedColumns} />);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ describe('<NetflowTable />', () => {
size: 'm' as Size,
onSelect: jest.fn(),
filterActionLinks: <></>,
setColumns: jest.fn()
setColumns: jest.fn(),
columnSizes: {},
setColumnSizes: jest.fn()
};

it('should render component', async () => {
Expand Down
14 changes: 14 additions & 0 deletions web/src/components/netflow-table/netflow-table-header.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ th.netobserv-header {
cursor: pointer;
}

th.netobserv-header.column:hover:not(.dragged)::after {
position: absolute;
top: 0;
right: 0;
content: ' ';
cursor: col-resize;
height: 100%;
width: 15px;
}

th.netobserv-header.dragged:not(.dark) {
opacity: 0.25;
background: #fff;
Expand All @@ -12,6 +22,10 @@ th.netobserv-header.dragged.dark {
background: #1b1d21;
}

th.netobserv-header.resizing {
border-bottom: solid #0066CC;
}

th.netobserv-header.dropzone:not(.dragged) {
box-shadow: inset 5px 0px 0px 0px #0066CC;
}
62 changes: 58 additions & 4 deletions web/src/components/netflow-table/netflow-table-header.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react';
import { SortByDirection, Th, Thead, Tr } from '@patternfly/react-table';
import _ from 'lodash';
import { Column, ColumnGroup, ColumnsId, getColumnGroups, getFullColumnName } from '../../utils/columns';
import * as React from 'react';
import { Column, ColumnGroup, ColumnsId, ColumnSizeMap, getColumnGroups, getFullColumnName } from '../../utils/columns';
import './netflow-table-header.css';

export type HeadersState = {
Expand All @@ -10,15 +10,24 @@ export type HeadersState = {
headers: Column[];
};

export type ResizedElement = {
target: HTMLElement;
startClientX: number;
startClentWidth: number;
};

export const NetflowTableHeader: React.FC<{
onSort: (id: ColumnsId, direction: SortByDirection) => void;
sortId: ColumnsId;
sortDirection: SortByDirection;
columns: Column[];
setColumns: (v: Column[]) => void;
columnSizes: ColumnSizeMap;
setColumnSizes: (v: ColumnSizeMap) => void;
tableWidth: number;
isDark?: boolean;
}> = ({ onSort, sortId, sortDirection, columns, setColumns, tableWidth, isDark }) => {
}> = ({ onSort, sortId, sortDirection, columns, setColumns, columnSizes, setColumnSizes, tableWidth, isDark }) => {
const resizedElement = React.useRef<ResizedElement>();
const draggedElement = React.useRef<HTMLElement>();

const [headersState, setHeadersState] = React.useState<HeadersState>({
Expand All @@ -27,6 +36,48 @@ export const NetflowTableHeader: React.FC<{
headers: []
});

const mouseEvent = React.useCallback(
(e: MouseEvent) => {
const diffPx = e.clientX - resizedElement.current!.startClientX;
switch (e.type) {
case 'mousemove':
const minWidth = Number(resizedElement.current!.target.style.minWidth?.replace('px', '')) || 0;
if (Math.abs(minWidth - diffPx) > 10) {
const minWidth = `${resizedElement.current!.startClentWidth + diffPx}px`;
columnSizes[resizedElement.current!.target.id as ColumnsId] = minWidth;
resizedElement.current!.target.style.minWidth = minWidth;
}
break;
default:
document.getElementById('cursor-style')!.remove();
resizedElement.current!.target.classList.remove('resizing');
document.removeEventListener('mousemove', mouseEvent);
document.removeEventListener('mouseup', mouseEvent);
setColumnSizes(columnSizes);
break;
}
},
[columnSizes, setColumnSizes]
);

const onMouseDown = React.useCallback(
(e: React.MouseEvent<HTMLElement>) => {
const target = e.currentTarget;
if (target.classList.contains('column') && e.nativeEvent.offsetX > target.clientWidth - 15) {
target.classList.add('resizing');
const cursorStyle = document.createElement('style');
cursorStyle.innerHTML = '*{cursor: col-resize!important;}';
cursorStyle.id = 'cursor-style';
document.head.appendChild(cursorStyle);
resizedElement.current = { target, startClientX: e.clientX, startClentWidth: target.clientWidth };
document.addEventListener('mousemove', mouseEvent);
document.addEventListener('mouseup', mouseEvent);
e.preventDefault();
}
},
[mouseEvent]
);

const onDragStart = React.useCallback((e: React.DragEvent<HTMLElement>) => {
const target = e.currentTarget;
target.classList.add('dragged');
Expand Down Expand Up @@ -126,6 +177,7 @@ export const NetflowTableHeader: React.FC<{
}}
colSpan={1}
draggable
onMouseDown={onMouseDown}
onDragStart={onDragStart}
onDragOver={e => {
if (draggedElement.current?.classList.contains('column')) {
Expand All @@ -140,20 +192,22 @@ export const NetflowTableHeader: React.FC<{
onDrop={onDrop}
onDragEnd={clearDragEffects}
modifier="wrap"
style={{ width: `${Math.floor((100 * c.width) / tableWidth)}%` }}
style={{ width: `${Math.floor((100 * c.width) / tableWidth)}%`, minWidth: columnSizes[c.id] }}
info={c.tooltip ? { tooltip: c.tooltip } : undefined}
>
{headersState.useNested ? c.name : getFullColumnName(c)}
</Th>
);
},
[
columnSizes,
columns,
headersState.nestedHeaders,
headersState.useNested,
isDark,
onDragStart,
onDrop,
onMouseDown,
onSort,
sortDirection,
sortId,
Expand Down
21 changes: 19 additions & 2 deletions web/src/components/netflow-table/netflow-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import * as _ from 'lodash';
import { Record } from '../../api/ipfix';
import { NetflowTableHeader } from './netflow-table-header';
import NetflowTableRow from './netflow-table-row';
import { Column, ColumnsId, getCommonColumns } from '../../utils/columns';
import { Column, ColumnsId, ColumnSizeMap, getCommonColumns } from '../../utils/columns';
import { Size } from '../dropdowns/table-display-dropdown';
import { usePrevious } from '../../utils/previous-hook';
import './netflow-table.css';
Expand All @@ -32,13 +32,28 @@ const NetflowTable: React.FC<{
selectedRecord?: Record;
columns: Column[];
setColumns: (v: Column[]) => void;
columnSizes: ColumnSizeMap;
setColumnSizes: (v: ColumnSizeMap) => void;
size: Size;
onSelect: (record?: Record) => void;
loading?: boolean;
error?: string;
filterActionLinks: JSX.Element;
isDark?: boolean;
}> = ({ flows, selectedRecord, columns, setColumns, error, loading, size, onSelect, filterActionLinks, isDark }) => {
}> = ({
flows,
selectedRecord,
columns,
setColumns,
columnSizes,
setColumnSizes,
error,
loading,
size,
onSelect,
filterActionLinks,
isDark
}) => {
const { t } = useTranslation('plugin__netobserv-plugin');

//default to 300 to allow content to be rendered in tests
Expand Down Expand Up @@ -254,6 +269,8 @@ const NetflowTable: React.FC<{
sortId={activeSortId}
columns={columns}
setColumns={setColumns}
columnSizes={columnSizes}
setColumnSizes={setColumnSizes}
tableWidth={width}
isDark={isDark}
/>
Expand Down
7 changes: 6 additions & 1 deletion web/src/components/netflow-traffic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,15 @@ import {
TopologyGroupTypes,
TopologyOptions
} from '../model/topology';
import { Column, getDefaultColumns } from '../utils/columns';
import { Column, ColumnSizeMap, getDefaultColumns } from '../utils/columns';
import { loadConfig } from '../utils/config';
import { ContextSingleton } from '../utils/context';
import { computeStepInterval, TimeRange } from '../utils/datetime';
import { getHTTPErrorDetails } from '../utils/errors';
import { useK8sModelsWithColors } from '../utils/k8s-models-hook';
import {
LOCAL_STORAGE_COLS_KEY,
LOCAL_STORAGE_COLS_SIZES_KEY,
LOCAL_STORAGE_DISABLED_FILTERS_KEY,
LOCAL_STORAGE_LAST_LIMIT_KEY,
LOCAL_STORAGE_LAST_TOP_KEY,
Expand Down Expand Up @@ -215,6 +216,7 @@ export const NetflowTraffic: React.FC<{
id: 'id',
criteria: 'isSelected'
});
const [columnSizes, setColumnSizes] = useLocalStorage<ColumnSizeMap>(LOCAL_STORAGE_COLS_SIZES_KEY, {});

React.useEffect(() => {
loadConfig().then(setConfig);
Expand Down Expand Up @@ -829,6 +831,8 @@ export const NetflowTraffic: React.FC<{
onSelect={onRecordSelect}
columns={columns.filter(col => col.isSelected)}
setColumns={(v: Column[]) => setColumns(v.concat(columns.filter(col => !col.isSelected)))}
columnSizes={columnSizes}
setColumnSizes={setColumnSizes}
filterActionLinks={filterLinks()}
isDark={isDarkTheme}
/>
Expand Down Expand Up @@ -1053,6 +1057,7 @@ export const NetflowTraffic: React.FC<{
setModalOpen={setColModalOpen}
columns={columns}
setColumns={setColumns}
setColumnSizes={setColumnSizes}
/>
<ExportModal
id="export-modal"
Expand Down
Loading