Skip to content

Commit

Permalink
Merge pull request #238 from jpinsonneau/609
Browse files Browse the repository at this point in the history
NETOBSERV-609 UI: Resize a column in flow table
  • Loading branch information
jpinsonneau authored Dec 5, 2022
2 parents 0256276 + 993ac32 commit 8886986
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 26 deletions.
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';
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 @@ -848,6 +850,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 @@ -1074,6 +1078,7 @@ export const NetflowTraffic: React.FC<{
setModalOpen={setColModalOpen}
columns={columns}
setColumns={setColumns}
setColumnSizes={setColumnSizes}
/>
<ExportModal
id="export-modal"
Expand Down
Loading

0 comments on commit 8886986

Please sign in to comment.