Skip to content

Commit dd183f5

Browse files
authored
CONSOLE-4988: add resizable column feature to Pods list (openshift#16128)
* Upgrade @patternfly/react-data-view to 6.4.0-prerelease.12 * add resizable column feature to Pods list * extract onResize type and remove unused const * Add leftBorder and rightBorder to sticky Th * Add reset columns width button in the table toolbar * Add reset columns width button in the table toolbar
1 parent ca5dbf6 commit dd183f5

13 files changed

Lines changed: 236 additions & 52 deletions

File tree

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@
159159
"@patternfly/react-code-editor": "~6.4.0",
160160
"@patternfly/react-component-groups": "~6.4.0",
161161
"@patternfly/react-core": "~6.4.0",
162-
"@patternfly/react-data-view": "~6.3.0",
162+
"@patternfly/react-data-view": "6.4.0-prerelease.12",
163163
"@patternfly/react-drag-drop": "~6.4.0",
164164
"@patternfly/react-icons": "~6.4.0",
165165
"@patternfly/react-log-viewer": "~6.3.0",

frontend/packages/console-app/src/components/data-view/ConsoleDataView.tsx

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { FC, ReactNode } from 'react';
2-
import { useMemo } from 'react';
2+
import { useCallback, useMemo, useState } from 'react';
33
import {
44
ResponsiveAction,
55
ResponsiveActions,
@@ -14,7 +14,7 @@ import {
1414
DataViewToolbar,
1515
} from '@patternfly/react-data-view';
1616
import DataViewFilters from '@patternfly/react-data-view/dist/cjs/DataViewFilters';
17-
import { ColumnsIcon } from '@patternfly/react-icons';
17+
import { ColumnsIcon, UndoIcon } from '@patternfly/react-icons';
1818
import { InnerScrollContainer, Tbody, Td, Tr } from '@patternfly/react-table';
1919
import { useTranslation } from 'react-i18next';
2020
import type {
@@ -76,9 +76,17 @@ export const ConsoleDataView = <
7676
hideLabelFilter,
7777
hideColumnManagement,
7878
mock,
79+
isResizable,
80+
resetAllColumnWidths,
7981
}: ConsoleDataViewProps<TData, TCustomRowData, TFilters>) => {
8082
const { t } = useTranslation();
8183
const launchModal = useOverlay();
84+
const [tableKey, setTableKey] = useState(0);
85+
86+
const handleResetColumnWidths = useCallback(() => {
87+
resetAllColumnWidths?.();
88+
setTableKey((k) => k + 1);
89+
}, [resetAllColumnWidths]);
8290

8391
const { filters, onSetFilters, clearAllFilters, filteredData } = useConsoleDataViewFilters<
8492
TData,
@@ -102,6 +110,7 @@ export const ConsoleDataView = <
102110
showNamespaceOverride,
103111
columnManagementID,
104112
customRowData,
113+
isResizable,
105114
});
106115

107116
const bodyLoading = useMemo(() => <BodyLoading columns={dataViewColumns.length} />, [
@@ -172,8 +181,8 @@ export const ConsoleDataView = <
172181
}
173182
clearAllFilters={clearAllFilters}
174183
actions={
175-
!hideColumnManagement && (
176-
<ResponsiveActions breakpoint="lg">
184+
<ResponsiveActions breakpoint="lg">
185+
{!hideColumnManagement && (
177186
<ResponsiveAction
178187
isPersistent
179188
variant="plain"
@@ -190,8 +199,21 @@ export const ConsoleDataView = <
190199
<ColumnsIcon />
191200
</Tooltip>
192201
</ResponsiveAction>
193-
</ResponsiveActions>
194-
)
202+
)}
203+
{isResizable && resetAllColumnWidths && (
204+
<ResponsiveAction
205+
isPersistent
206+
variant="plain"
207+
onClick={handleResetColumnWidths}
208+
aria-label={t('public~Reset column widths')}
209+
data-test="reset-column-widths"
210+
>
211+
<Tooltip content={t('public~Reset column widths')} trigger="mouseenter">
212+
<UndoIcon />
213+
</Tooltip>
214+
</ResponsiveAction>
215+
)}
216+
</ResponsiveActions>
195217
}
196218
pagination={
197219
<Pagination
@@ -203,6 +225,7 @@ export const ConsoleDataView = <
203225
/>
204226
<InnerScrollContainer>
205227
<DataViewTable
228+
key={tableKey}
206229
aria-label={t(`public~{{label}} table`, { label })}
207230
// @ts-expect-error - TODO(react18): CONSOLE-5040: Remove ConsoleDataViewColumn bodge
208231
columns={dataViewColumns}
@@ -211,6 +234,7 @@ export const ConsoleDataView = <
211234
gridBreakPoint=""
212235
variant="compact"
213236
data-test="data-view-table"
237+
isResizable={isResizable}
214238
/>
215239
</InnerScrollContainer>
216240
</DataView>
@@ -223,7 +247,7 @@ export const cellIsStickyProps = {
223247
stickyMinWidth: '0',
224248
};
225249

226-
const nameCellProps = {
250+
export const nameCellProps = {
227251
...cellIsStickyProps,
228252
hasRightBorder: true,
229253
};

frontend/packages/console-app/src/components/data-view/useConsoleDataViewData.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { ReactNode } from 'react';
22
import { useRef, useEffect, useMemo } from 'react';
3-
import type { DataViewTh } from '@patternfly/react-data-view';
43
import { useDataViewPagination } from '@patternfly/react-data-view';
4+
import type { DataViewTh } from '@patternfly/react-data-view/dist/esm/DataViewTable/DataViewTable';
55
import type { ThProps } from '@patternfly/react-table';
66
import { SortByDirection } from '@patternfly/react-table';
77
import * as _ from 'lodash';
@@ -35,6 +35,7 @@ export const useConsoleDataViewData = <
3535
showNamespaceOverride,
3636
columnManagementID,
3737
customRowData,
38+
isResizable = true,
3839
}: {
3940
columns: TableColumn<TData>[];
4041
filteredData: TData[];
@@ -43,6 +44,7 @@ export const useConsoleDataViewData = <
4344
showNamespaceOverride?: boolean;
4445
columnManagementID?: string;
4546
customRowData?: TCustomRowData;
47+
isResizable?: boolean;
4648
}) => {
4749
const { t } = useTranslation();
4850
const [searchParams, setSearchParams] = useSearchParams();
@@ -79,7 +81,7 @@ export const useConsoleDataViewData = <
7981

8082
const dataViewColumns = useMemo<ConsoleDataViewColumn<TData>[]>(
8183
() =>
82-
activeColumns.map(({ id, title, sort, props }, index) => {
84+
activeColumns.map(({ id, title, sort, props, resizableProps }, index) => {
8385
const headerProps: ThProps = {
8486
...props,
8587
dataLabel: title,
@@ -101,14 +103,15 @@ export const useConsoleDataViewData = <
101103
title,
102104
sortFunction: sort,
103105
props: headerProps,
106+
resizableProps: isResizable ? resizableProps : undefined,
104107
cell: title ? (
105108
<span>{title}</span>
106109
) : (
107110
<span className="pf-v6-u-screen-reader">{t('public~Actions')}</span>
108111
),
109112
};
110113
}),
111-
[activeColumns, t],
114+
[activeColumns, t, isResizable],
112115
);
113116

114117
const { sortBy, onSort } = useConsoleDataViewSort<TData>({
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import type { MouseEvent } from 'react';
2+
import { useCallback, useMemo } from 'react';
3+
import type { K8sModel } from '@console/dynamic-plugin-sdk';
4+
import { referenceForModel } from '@console/internal/module/k8s';
5+
import { COLUMN_WIDTH_CONFIGMAP_KEY } from '@console/shared/src/constants/common';
6+
import { useUserPreference } from '@console/shared/src/hooks/useUserPreference';
7+
8+
/**
9+
* Stored column widths keyed by table then column for easy reading in the configMap.
10+
* Example: { "core~v1~Pod": { "name": 200, "namespace": 150 } }
11+
*/
12+
export type ColumnWidthUserSettings = Record<string, Record<string, number>>;
13+
14+
/** Callback when a table column is resized: event, column id, and new width in pixels. */
15+
type ColumnResizeOnResize = (
16+
event: MouseEvent<HTMLDivElement>,
17+
id: string | number | undefined,
18+
width: number,
19+
) => void;
20+
21+
/**
22+
* Table-level hook: one source of truth per table so that each column resize merges into
23+
* the same state and creates/updates an entry for that column without replacing others.
24+
* Use this when a table has many columns so that all column widths are stored under one
25+
* configMap key and each resize adds/updates only that column's entry.
26+
*
27+
* @param model - K8s model (e.g. PodModel) for the resource table
28+
* @returns getResizableProps(columnId), getWidth(columnId), and resetAllColumnWidths()
29+
*/
30+
export const useColumnWidthSettings = (
31+
model: K8sModel,
32+
): {
33+
getResizableProps: (
34+
columnId: string,
35+
) => {
36+
isResizable: true;
37+
width?: number;
38+
onResize: ColumnResizeOnResize;
39+
resizeButtonAriaLabel: string;
40+
};
41+
getWidth: (columnId: string) => number | undefined;
42+
/** Reset saved widths for all columns in this table. */
43+
resetAllColumnWidths: () => void;
44+
} => {
45+
const resolvedTableId = useMemo(() => referenceForModel(model), [model]);
46+
47+
const [columnWidths, setColumnWidths] = useUserPreference<ColumnWidthUserSettings>(
48+
COLUMN_WIDTH_CONFIGMAP_KEY,
49+
{},
50+
);
51+
52+
const setColumnWidth = useCallback(
53+
(columnId: string, newWidth: number) => {
54+
setColumnWidths((prev) => {
55+
const existing = prev ?? {};
56+
const tableColumns = existing[resolvedTableId] ?? {};
57+
return {
58+
...existing,
59+
[resolvedTableId]: {
60+
...tableColumns,
61+
[columnId]: newWidth,
62+
},
63+
};
64+
});
65+
},
66+
[resolvedTableId, setColumnWidths],
67+
);
68+
69+
const resetAllColumnWidths = useCallback(() => {
70+
setColumnWidths((prev) => {
71+
const existing = prev ?? {};
72+
const { [resolvedTableId]: _removed, ...rest } = existing;
73+
return rest;
74+
});
75+
}, [resolvedTableId, setColumnWidths]);
76+
77+
const getWidth = useCallback((columnId: string) => columnWidths?.[resolvedTableId]?.[columnId], [
78+
columnWidths,
79+
resolvedTableId,
80+
]);
81+
82+
const getResizableProps = useCallback(
83+
(columnId: string) => {
84+
const onResize: ColumnResizeOnResize = (_event, _id, newWidth) =>
85+
setColumnWidth(columnId, newWidth);
86+
return {
87+
isResizable: true as const,
88+
width: columnWidths?.[resolvedTableId]?.[columnId],
89+
onResize,
90+
resizeButtonAriaLabel: `Resize ${columnId} column`,
91+
};
92+
},
93+
[columnWidths, resolvedTableId, setColumnWidth],
94+
);
95+
96+
return { getResizableProps, getWidth, resetAllColumnWidths };
97+
};
98+
99+
/**
100+
* Per-column hook for resizable props. Prefer {@link useColumnWidthSettings} for tables
101+
* with many columns so that one configMap entry holds all column widths and each resize
102+
* creates/updates only that column's entry.
103+
*/
104+
export const useResizableColumnProps = (
105+
model: K8sModel,
106+
columnId: string,
107+
): {
108+
isResizable: true;
109+
width?: number;
110+
onResize: ColumnResizeOnResize;
111+
resizeButtonAriaLabel: string;
112+
resetAllColumnWidths: () => void;
113+
} => {
114+
const { getResizableProps, getWidth, resetAllColumnWidths } = useColumnWidthSettings(model);
115+
const resizableProps = getResizableProps(columnId);
116+
return useMemo(
117+
() => ({
118+
...resizableProps,
119+
width: getWidth(columnId),
120+
resetAllColumnWidths,
121+
}),
122+
[resizableProps, getWidth, columnId, resetAllColumnWidths],
123+
);
124+
};

frontend/packages/console-dynamic-plugin-sdk/src/api/internal-types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { ReactNode, ComponentType, SetStateAction, Dispatch } from 'react';
22
import type { QuickStart } from '@patternfly/quickstarts';
3-
import type { DataViewTh } from '@patternfly/react-data-view';
3+
import type { DataViewTh } from '@patternfly/react-data-view/dist/esm/DataViewTable/DataViewTable';
44
import type { SortByDirection, ThProps } from '@patternfly/react-table';
55
import type { Map as ImmutableMap } from 'immutable';
66
import type {
@@ -322,6 +322,7 @@ export type ConsoleDataViewColumn<TData> = ConsoleDataViewTh & {
322322
id: string;
323323
title: string;
324324
sortFunction?: string | ((filteredData: TData[], sortDirection: SortByDirection) => TData[]);
325+
resizableProps?: any;
325326
};
326327

327328
export type ConsoleDataViewRow = any[];
@@ -354,6 +355,9 @@ export type ConsoleDataViewProps<
354355
hideLabelFilter?: boolean;
355356
hideColumnManagement?: boolean;
356357
mock?: boolean;
358+
isResizable?: boolean;
359+
/** When provided and isResizable is true, a toolbar action is shown to reset all column widths. */
360+
resetAllColumnWidths?: () => void;
357361
};
358362

359363
// ConsoleDataView helper types

frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ export type TableColumn<D> = ICell & {
311311
id: string;
312312
additional?: boolean;
313313
sort?: ((data: D[], sortDirection: SortByDirection) => D[]) | string;
314+
resizableProps?: any;
314315
};
315316

316317
export type RowProps<D, R extends any = {}> = {

frontend/packages/console-shared/src/constants/common.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ export enum FLAGS {
104104

105105
export const CONFIG_STORAGE_CONSOLE = 'console';
106106
export const COLUMN_MANAGEMENT_CONFIGMAP_KEY = `${CONFIG_STORAGE_CONSOLE}.tableColumns`;
107+
export const COLUMN_WIDTH_CONFIGMAP_KEY = `${CONFIG_STORAGE_CONSOLE}.tableColumnsWidth`;
107108
export const ACM_LINK_ID = 'acm-console-link';
108109

109110
export const SYSTEM_NAMESPACES_PREFIX = ['kube-', 'openshift-', 'kubernetes-'];

frontend/packages/helm-plugin/src/components/details-page/history/HelmReleaseHistoryTable.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import type { MouseEvent, FC } from 'react';
22
import { useMemo, useCallback, Suspense } from 'react';
33
import { Pagination } from '@patternfly/react-core';
4-
import type { DataViewTd, DataViewTh } from '@patternfly/react-data-view';
54
import {
65
DataView,
76
DataViewTable,
87
DataViewToolbar,
98
useDataViewPagination,
109
} from '@patternfly/react-data-view';
10+
import type {
11+
DataViewTd,
12+
DataViewTh,
13+
} from '@patternfly/react-data-view/dist/esm/DataViewTable/DataViewTable';
1114
import { InnerScrollContainer, SortByDirection } from '@patternfly/react-table';
1215
import { useTranslation } from 'react-i18next';
1316
import { useSearchParams } from 'react-router-dom-v5-compat';

frontend/packages/helm-plugin/src/components/details-page/history/HelmReleaseHistoryTableHelpers.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import type { FC, MouseEvent } from 'react';
22
import { useMemo } from 'react';
3-
import type { DataViewTd, DataViewTh } from '@patternfly/react-data-view';
3+
import type {
4+
DataViewTd,
5+
DataViewTh,
6+
} from '@patternfly/react-data-view/dist/esm/DataViewTable/DataViewTable';
47
import type { SortByDirection, ThProps } from '@patternfly/react-table';
58
import { Trans, useTranslation } from 'react-i18next';
69
import { coFetchJSON } from '@console/internal/co-fetch';

frontend/packages/helm-plugin/src/components/forms/rollback/RevisionListHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { MouseEvent } from 'react';
22
import { useMemo } from 'react';
3-
import type { DataViewTh } from '@patternfly/react-data-view';
3+
import type { DataViewTh } from '@patternfly/react-data-view/dist/esm/DataViewTable/DataViewTable';
44
import type { ThProps, SortByDirection } from '@patternfly/react-table';
55
import { useTranslation } from 'react-i18next';
66

0 commit comments

Comments
 (0)