Skip to content

Commit 1f9a438

Browse files
authored
NETOBSERV-608 UI: Reorder columns in flow table (#236)
* columns drag & drop * fix update columns on reorder
1 parent 27eb0dd commit 1f9a438

File tree

6 files changed

+127
-7
lines changed

6 files changed

+127
-7
lines changed

web/src/components/netflow-table/__tests__/netflow-table-header.spec.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ const NetflowTableHeaderWrapper: React.FC<{
1010
sortId: ColumnsId;
1111
sortDirection: SortByDirection;
1212
columns: Column[];
13-
}> = ({ onSort, sortId, sortDirection, columns }) => {
13+
setColumns: (v: Column[]) => void;
14+
}> = ({ onSort, sortId, sortDirection, columns, setColumns }) => {
1415
return (
1516
<TableComposable aria-label="Misc table" variant="compact">
1617
<NetflowTableHeader
1718
onSort={onSort}
1819
sortDirection={sortDirection}
1920
sortId={sortId}
2021
columns={columns}
22+
setColumns={setColumns}
2123
tableWidth={100}
2224
/>
2325
<Tbody></Tbody>
@@ -30,7 +32,8 @@ describe('<NetflowTableHeader />', () => {
3032
onSort: jest.fn(),
3133
sortId: ColumnsId.endtime,
3234
sortDirection: SortByDirection.asc,
33-
tableWidth: 100
35+
tableWidth: 100,
36+
setColumns: jest.fn()
3437
};
3538
it('should render component', async () => {
3639
const wrapper = mount(<NetflowTableHeaderWrapper {...mocks} columns={AllSelectedColumns} />);

web/src/components/netflow-table/__tests__/netflow-table.spec.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ describe('<NetflowTable />', () => {
2121
const mocks = {
2222
size: 'm' as Size,
2323
onSelect: jest.fn(),
24-
filterActionLinks: <></>
24+
filterActionLinks: <></>,
25+
setColumns: jest.fn()
2526
};
2627

2728
it('should render component', async () => {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
th.netobserv-header {
2+
cursor: pointer;
3+
}
4+
5+
th.netobserv-header.dragged:not(.dark) {
6+
opacity: 0.25;
7+
background: #fff;
8+
}
9+
10+
th.netobserv-header.dragged.dark {
11+
opacity: 0.25;
12+
background: #1b1d21;
13+
}
14+
15+
th.netobserv-header.dropzone:not(.dragged) {
16+
box-shadow: inset 5px 0px 0px 0px #0066CC;
17+
}

web/src/components/netflow-table/netflow-table-header.tsx

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as React from 'react';
22
import { SortByDirection, Th, Thead, Tr } from '@patternfly/react-table';
33
import _ from 'lodash';
44
import { Column, ColumnGroup, ColumnsId, getColumnGroups, getFullColumnName } from '../../utils/columns';
5+
import './netflow-table-header.css';
56

67
export type HeadersState = {
78
nestedHeaders: ColumnGroup[];
@@ -14,28 +15,93 @@ export const NetflowTableHeader: React.FC<{
1415
sortId: ColumnsId;
1516
sortDirection: SortByDirection;
1617
columns: Column[];
18+
setColumns: (v: Column[]) => void;
1719
tableWidth: number;
18-
}> = ({ onSort, sortId, sortDirection, columns, tableWidth }) => {
20+
isDark?: boolean;
21+
}> = ({ onSort, sortId, sortDirection, columns, setColumns, tableWidth, isDark }) => {
22+
const draggedElement = React.useRef<HTMLElement>();
23+
1924
const [headersState, setHeadersState] = React.useState<HeadersState>({
2025
nestedHeaders: [],
2126
useNested: false,
2227
headers: []
2328
});
2429

30+
const onDragStart = React.useCallback((e: React.DragEvent<HTMLElement>) => {
31+
const target = e.currentTarget;
32+
target.classList.add('dragged');
33+
draggedElement.current = target;
34+
}, []);
35+
36+
const clearDragEffects = () => {
37+
document.querySelectorAll('.netobserv-header').forEach(e => {
38+
if (e.classList.contains('dragged')) {
39+
e.classList.remove('dragged');
40+
}
41+
42+
if (e.classList.contains('dropzone')) {
43+
e.classList.remove('dropzone');
44+
}
45+
});
46+
};
47+
48+
const onDrop = React.useCallback(
49+
(e: React.DragEvent<HTMLElement>) => {
50+
if (!e.currentTarget || !draggedElement.current) {
51+
console.error('onDrop called while currentTarget or draggedElement ref missing');
52+
return;
53+
}
54+
55+
if (
56+
(e.currentTarget.classList.contains('nested') && draggedElement.current.classList.contains('nested')) ||
57+
(e.currentTarget.classList.contains('column') && draggedElement.current.classList.contains('column'))
58+
) {
59+
const srcIndex = Number(draggedElement.current.getAttribute('data-index'));
60+
const srcColSpan = Number(draggedElement.current!.getAttribute('colSpan'));
61+
const dstIndex = Number(e.currentTarget.getAttribute('data-index'));
62+
63+
const result = [...columns];
64+
const removed = result.splice(srcIndex, srcColSpan);
65+
result.splice(dstIndex, 0, ...removed);
66+
setColumns(result);
67+
}
68+
69+
e.preventDefault();
70+
},
71+
[columns, setColumns]
72+
);
73+
2574
const getNestedTableHeader = React.useCallback(
2675
(nh: ColumnGroup) => {
2776
return (
2877
<Th
78+
className={`netobserv-header nested ${isDark ? 'dark' : ''}`}
2979
data-test={`nested-th-${nh.title || 'empty'}`}
80+
data-index={columns.indexOf(nh.columns[0])}
3081
key={`nested-${nh.title}-${headersState.nestedHeaders.indexOf(nh)}`}
82+
id={`nested-${headersState.nestedHeaders.indexOf(nh)}`}
3183
hasRightBorder={_.last(headersState.nestedHeaders) !== nh}
3284
colSpan={nh.columns.length}
85+
draggable
86+
onDragStart={onDragStart}
87+
onDragOver={e => {
88+
if (draggedElement.current?.classList.contains('nested')) {
89+
e.currentTarget.classList.add('dropzone');
90+
}
91+
e.preventDefault();
92+
}}
93+
onDragLeave={e => {
94+
e.currentTarget.classList.remove('dropzone');
95+
e.preventDefault();
96+
}}
97+
onDrop={onDrop}
98+
onDragEnd={clearDragEffects}
3399
>
34100
{nh.title}
35101
</Th>
36102
);
37103
},
38-
[headersState.nestedHeaders]
104+
[columns, headersState.nestedHeaders, isDark, onDragStart, onDrop]
39105
);
40106

41107
const getTableHeader = React.useCallback(
@@ -44,9 +110,12 @@ export const NetflowTableHeader: React.FC<{
44110
headersState.useNested && headersState.nestedHeaders.find(nh => _.last(nh.columns) === c) !== undefined;
45111
return (
46112
<Th
113+
className={`netobserv-header column ${isDark ? 'dark' : ''}`}
47114
data-test={`th-${c.id}`}
115+
data-index={columns.indexOf(c)}
48116
hasRightBorder={showBorder}
49117
key={c.id}
118+
id={c.id}
50119
sort={{
51120
sortBy: {
52121
index: columns.findIndex(c => c.id === sortId),
@@ -55,6 +124,21 @@ export const NetflowTableHeader: React.FC<{
55124
onSort: (event, index, direction) => onSort(c.id, direction),
56125
columnIndex: columns.indexOf(c)
57126
}}
127+
colSpan={1}
128+
draggable
129+
onDragStart={onDragStart}
130+
onDragOver={e => {
131+
if (draggedElement.current?.classList.contains('column')) {
132+
e.currentTarget.classList.add('dropzone');
133+
}
134+
e.preventDefault();
135+
}}
136+
onDragLeave={e => {
137+
e.currentTarget.classList.remove('dropzone');
138+
e.preventDefault();
139+
}}
140+
onDrop={onDrop}
141+
onDragEnd={clearDragEffects}
58142
modifier="wrap"
59143
style={{ width: `${Math.floor((100 * c.width) / tableWidth)}%` }}
60144
info={c.tooltip ? { tooltip: c.tooltip } : undefined}
@@ -63,7 +147,18 @@ export const NetflowTableHeader: React.FC<{
63147
</Th>
64148
);
65149
},
66-
[columns, headersState.nestedHeaders, headersState.useNested, onSort, sortDirection, sortId, tableWidth]
150+
[
151+
columns,
152+
headersState.nestedHeaders,
153+
headersState.useNested,
154+
isDark,
155+
onDragStart,
156+
onDrop,
157+
onSort,
158+
sortDirection,
159+
sortId,
160+
tableWidth
161+
]
67162
);
68163

69164
React.useEffect(() => {

web/src/components/netflow-table/netflow-table.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,14 @@ const NetflowTable: React.FC<{
3131
flows: Record[];
3232
selectedRecord?: Record;
3333
columns: Column[];
34+
setColumns: (v: Column[]) => void;
3435
size: Size;
3536
onSelect: (record?: Record) => void;
3637
loading?: boolean;
3738
error?: string;
3839
filterActionLinks: JSX.Element;
3940
isDark?: boolean;
40-
}> = ({ flows, selectedRecord, columns, error, loading, size, onSelect, filterActionLinks, isDark }) => {
41+
}> = ({ flows, selectedRecord, columns, setColumns, error, loading, size, onSelect, filterActionLinks, isDark }) => {
4142
const { t } = useTranslation('plugin__netobserv-plugin');
4243

4344
//default to 300 to allow content to be rendered in tests
@@ -252,7 +253,9 @@ const NetflowTable: React.FC<{
252253
sortDirection={activeSortDirection}
253254
sortId={activeSortId}
254255
columns={columns}
256+
setColumns={setColumns}
255257
tableWidth={width}
258+
isDark={isDark}
256259
/>
257260
<Tbody id="table-body" data-test="table-body">
258261
{getBody()}

web/src/components/netflow-traffic.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,7 @@ export const NetflowTraffic: React.FC<{
828828
size={size}
829829
onSelect={onRecordSelect}
830830
columns={columns.filter(col => col.isSelected)}
831+
setColumns={(v: Column[]) => setColumns(v.concat(columns.filter(col => !col.isSelected)))}
831832
filterActionLinks={filterLinks()}
832833
isDark={isDarkTheme}
833834
/>

0 commit comments

Comments
 (0)