Skip to content

Commit 00686bc

Browse files
authored
fix(AnalyticalTable): allow nested sub-rows as subRowsKey, add manualGroupBy support (#4598)
1 parent dd01768 commit 00686bc

File tree

7 files changed

+177
-13
lines changed

7 files changed

+177
-13
lines changed

packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1854,6 +1854,140 @@ describe('AnalyticalTable', () => {
18541854
);
18551855
});
18561856

1857+
it('manualGroupBy - backend grouping', () => {
1858+
const cols = [
1859+
{
1860+
accessor: 'values.name',
1861+
Header: 'Name',
1862+
// replace value for grouped rows, otherwise the aggregated value will be added in brackets
1863+
Cell: ({ value }) => value,
1864+
// bug with placeholder (repeated value), for some reason "Simon" is handled as placeholder when manualGroupBy is active
1865+
// --> show value, but hide it for nested row, either here or in the data
1866+
RepeatedValue: (instance) => {
1867+
if (instance.manualGroupBy) {
1868+
// this can be omitted when handled by data
1869+
if (instance.row.id.includes('.')) {
1870+
return null;
1871+
}
1872+
return instance.value;
1873+
}
1874+
return null;
1875+
}
1876+
},
1877+
{ accessor: 'values.age', Header: 'Age' }
1878+
];
1879+
1880+
const SERVER_DATA = [
1881+
{ values: { name: 'Simon', age: '72', children: undefined } },
1882+
{ values: { name: 'Peter', age: '25', children: [] } },
1883+
{ values: { name: 'Martha', age: '30', children: [] } }
1884+
];
1885+
const SERVER_DATA_AGGREGATED = [
1886+
{ values: { name: 'Simon', age: '72', children: undefined } },
1887+
{ values: { name: 'Peter', age: 'Aggregated', children: [] } },
1888+
{ values: { name: 'Martha', age: 'Aggregated', children: [] } }
1889+
];
1890+
const SERVER_DATA_PETER = [
1891+
{ values: { name: 'Simon', age: '72', children: undefined } },
1892+
{
1893+
values: {
1894+
name: 'Peter',
1895+
age: 'Aggregated',
1896+
children: [
1897+
{ values: { age: '25' } },
1898+
{ values: { age: '25' } },
1899+
{ values: { age: '30' } },
1900+
{ values: { age: '30' } },
1901+
{ values: { age: '30' } }
1902+
]
1903+
}
1904+
},
1905+
{ values: { name: 'Martha', age: 'Aggregated', children: [] } }
1906+
];
1907+
1908+
const SERVER_DATA_MARTHA = [
1909+
{ values: { name: 'Simon', age: '72', children: undefined } },
1910+
{ values: { name: 'Peter', age: 'Aggregated', children: [] } },
1911+
{
1912+
values: {
1913+
name: 'Martha',
1914+
age: 'Aggregated',
1915+
children: [
1916+
{ values: { name: 'Martha', age: '30' } },
1917+
{ values: { name: 'Martha', age: '25' } },
1918+
{ values: { name: 'Martha', age: '25' } }
1919+
]
1920+
}
1921+
}
1922+
];
1923+
const TestComp = () => {
1924+
const [groupedCols, setGroupedCols] = useState([]);
1925+
const [serverData, setServerData] = useState(SERVER_DATA);
1926+
const handleRowExpandChange = (e) => {
1927+
const { isExpanded, original } = e.detail.row;
1928+
const { column } = e.detail;
1929+
if (!isExpanded) {
1930+
if (groupedCols.includes(column.id)) {
1931+
switch (original.values.name) {
1932+
case 'Peter':
1933+
setServerData(SERVER_DATA_PETER);
1934+
break;
1935+
case 'Martha':
1936+
setServerData(SERVER_DATA_MARTHA);
1937+
break;
1938+
default:
1939+
break;
1940+
}
1941+
}
1942+
}
1943+
};
1944+
1945+
const handleGrouping = (e) => {
1946+
const { groupedColumns } = e.detail;
1947+
setGroupedCols(groupedColumns);
1948+
if (groupedColumns.includes('values.name')) {
1949+
setServerData(SERVER_DATA_AGGREGATED);
1950+
} else {
1951+
setServerData(SERVER_DATA);
1952+
}
1953+
};
1954+
return (
1955+
<AnalyticalTable
1956+
data={serverData}
1957+
groupable
1958+
columns={cols}
1959+
reactTableOptions={{
1960+
autoResetGroupBy: false,
1961+
autoResetExpanded: false,
1962+
manualGroupBy: true
1963+
}}
1964+
// only use subRowsKey --> can't use subRows as it's always an array (not undefined - internal logic will fail)
1965+
subRowsKey="values.children"
1966+
onRowExpandChange={handleRowExpandChange}
1967+
onGroup={handleGrouping}
1968+
/>
1969+
);
1970+
};
1971+
cy.mount(<TestComp />);
1972+
cy.findByText('Simon').should('be.visible').should('have.length', 1);
1973+
cy.findByText('Peter').should('be.visible').should('have.length', 1);
1974+
cy.findByText('Martha').should('be.visible').should('have.length', 1);
1975+
cy.findByText('Aggregated').should('not.exist');
1976+
1977+
cy.findByText('Name').click();
1978+
cy.findByText('Group').click();
1979+
cy.findByText('Simon').should('be.visible').should('have.length', 1);
1980+
cy.findAllByText('Aggregated').should('be.visible').should('have.length', 2);
1981+
cy.get('[ui5-icon][name="navigation-right-arrow"]').should('be.visible').should('have.length', 2);
1982+
1983+
cy.get('[ui5-icon][name="navigation-right-arrow"]').eq(1).click();
1984+
cy.findByText('Martha').should('be.visible').should('have.length', 1);
1985+
1986+
cy.get('[data-visible-column-index="1"][data-visible-row-index="4"]').should('have.text', 30);
1987+
cy.get('[data-visible-column-index="1"][data-visible-row-index="5"]').should('have.text', 25);
1988+
cy.get('[data-visible-column-index="1"][data-visible-row-index="6"]').should('have.text', 25);
1989+
});
1990+
18571991
cypressPassThroughTestsFactory(AnalyticalTable, { data, columns });
18581992
});
18591993

packages/main/src/components/AnalyticalTable/TableBody/VirtualTableBody.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { clsx } from 'clsx';
33
import type { MutableRefObject, ReactNode } from 'react';
44
import React, { useCallback, useMemo, useRef } from 'react';
55
import type { ScrollToRefType } from '../interfaces.js';
6+
import { getSubRowsByString } from '../util/index.js';
67
import { EmptyRow } from './EmptyRow.js';
78
import { RowSubComponent as SubComponent } from './RowSubComponent.js';
89

@@ -27,6 +28,8 @@ interface VirtualTableBodyProps {
2728
dispatch?: (e: { type: string; payload?: Record<string, unknown> }) => void;
2829
subComponentsHeight?: Record<string, { rowId: string; subComponentHeight?: number }>;
2930
columnVirtualizer: Record<string, any>;
31+
manualGroupBy?: boolean;
32+
subRowsKey: string;
3033
}
3134

3235
const measureElement = (el) => el.offsetHeight;
@@ -52,7 +55,9 @@ export const VirtualTableBody = (props: VirtualTableBodyProps) => {
5255
alwaysShowSubComponent,
5356
dispatch,
5457
subComponentsHeight,
55-
columnVirtualizer
58+
columnVirtualizer,
59+
manualGroupBy,
60+
subRowsKey
5661
} = props;
5762

5863
const itemCount = Math.max(minRows, rows.length);
@@ -238,7 +243,10 @@ export const VirtualTableBody = (props: VirtualTableBodyProps) => {
238243
contentToRender = 'Cell';
239244
} else if (isTreeTable || (!alwaysShowSubComponent && RowSubComponent)) {
240245
contentToRender = 'Expandable';
241-
} else if (cell.isGrouped) {
246+
} else if (
247+
cell.isGrouped ||
248+
(manualGroupBy && cell.column.isGrouped && getSubRowsByString(subRowsKey, row.original) != null)
249+
) {
242250
contentToRender = 'Grouped';
243251
} else if (cell.isAggregated) {
244252
contentToRender = 'Aggregated';

packages/main/src/components/AnalyticalTable/defaults/Column/Grouped.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const Grouped = (props) => {
2525
return (
2626
<>
2727
<span
28-
{...row.getToggleRowExpandedProps({ style })}
28+
{...row.getToggleRowExpandedProps({ style, column: cell.column })}
2929
title={row.isExpanded ? translatableTexts.collapseNodeA11yText : translatableTexts.expandNodeA11yText}
3030
>
3131
<Icon name={row.isExpanded ? iconNavDownArrow : iconNavRightArrow} style={tableGroupExpandCollapseIcon} />

packages/main/src/components/AnalyticalTable/hooks/useStyling.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { CSSProperties } from 'react';
22
import { AnalyticalTableSelectionBehavior, AnalyticalTableSelectionMode } from '../../../enums/index.js';
3-
import { resolveCellAlignment } from '../util/index.js';
3+
import { getSubRowsByString, resolveCellAlignment } from '../util/index.js';
44

55
const getHeaderGroupProps = (headerGroupProps, { instance }) => {
66
const { classes } = instance.webComponentsReactProperties;
@@ -44,14 +44,18 @@ const ROW_SELECTION_ATTRIBUTE = 'data-is-selected';
4444

4545
const getRowProps = (rowProps, { instance, row }) => {
4646
const { webComponentsReactProperties } = instance;
47-
const { classes, selectionBehavior, selectionMode, alternateRowColor } = webComponentsReactProperties;
47+
const { classes, selectionBehavior, selectionMode, alternateRowColor, subRowsKey } = webComponentsReactProperties;
4848
let className = classes.tr;
4949
const rowCanBeSelected = [
5050
AnalyticalTableSelectionMode.SingleSelect,
5151
AnalyticalTableSelectionMode.MultiSelect
5252
].includes(selectionMode);
53-
54-
if (row.isGrouped) {
53+
if (
54+
row.isGrouped ||
55+
(instance.manualGroupBy &&
56+
row.cells.some((item) => item.column.isGrouped) &&
57+
getSubRowsByString(subRowsKey, row.original) != null)
58+
) {
5559
className += ` ${classes.tableGroupHeader}`;
5660
}
5761

packages/main/src/components/AnalyticalTable/hooks/useToggleRowExpand.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { enrichEventWithDetails } from '@ui5/webcomponents-react-base';
22

3-
const getToggleRowExpandedProps = (rowProps, { row, instance }) => {
4-
const { dispatch } = instance;
3+
const getToggleRowExpandedProps = (rowProps, { row, instance, userProps }) => {
4+
const { dispatch, manualGroupBy } = instance;
55
const { onRowExpandChange, isTreeTable, renderRowSubComponent } = instance.webComponentsReactProperties;
66
const onClick = (e, noPropagation = true) => {
77
if (noPropagation) {
@@ -11,7 +11,11 @@ const getToggleRowExpandedProps = (rowProps, { row, instance }) => {
1111
row.toggleRowExpanded();
1212
let column = null;
1313
if (!isTreeTable && !renderRowSubComponent) {
14-
column = row.cells.find((cell) => cell.column.id === row.groupByID).column;
14+
if (!manualGroupBy) {
15+
column = row.cells.find((cell) => cell.column.id === row.groupByID)?.column;
16+
} else {
17+
column = userProps.column;
18+
}
1519
}
1620

1721
if (row.isExpanded) {

packages/main/src/components/AnalyticalTable/index.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ import { VirtualTableBody } from './TableBody/VirtualTableBody.js';
8282
import { VirtualTableBodyContainer } from './TableBody/VirtualTableBodyContainer.js';
8383
import { stateReducer } from './tableReducer/stateReducer.js';
8484
import { TitleBar } from './TitleBar/index.js';
85-
import { getRowHeight, tagNamesWhichShouldNotSelectARow } from './util/index.js';
85+
import { getRowHeight, getSubRowsByString, tagNamesWhichShouldNotSelectARow } from './util/index.js';
8686
import { VerticalResizer } from './VerticalResizer.js';
8787

8888
export interface AnalyticalTableColumnDefinition {
@@ -462,6 +462,8 @@ export interface AnalyticalTablePropTypes extends Omit<CommonProps, 'title'> {
462462
/**
463463
* Defines the key for nested rows.
464464
*
465+
* __Note__: You can also specify deeply nested sub-rows with accessors like `values.subRows`.
466+
*
465467
* Default: "subRows"
466468
*/
467469
subRowsKey?: string;
@@ -651,7 +653,7 @@ const AnalyticalTable = forwardRef<AnalyticalTableDomRef, AnalyticalTablePropTyp
651653

652654
const isRtl = useIsRTL(analyticalTableRef);
653655

654-
const getSubRows = useCallback((row) => row.subRows || row[subRowsKey] || [], [subRowsKey]);
656+
const getSubRows = useCallback((row) => getSubRowsByString(subRowsKey, row) || [], [subRowsKey]);
655657

656658
const invalidTableA11yText = i18nBundle.getText(INVALID_TABLE);
657659
const tableInstanceRef = useRef<Record<string, any>>(null);
@@ -699,7 +701,8 @@ const AnalyticalTable = forwardRef<AnalyticalTableDomRef, AnalyticalTablePropTyp
699701
scrollToRef,
700702
showOverlay,
701703
uniqueId,
702-
scaleXFactor
704+
scaleXFactor,
705+
subRowsKey
703706
},
704707
...reactTableOptions
705708
},
@@ -1133,6 +1136,8 @@ const AnalyticalTable = forwardRef<AnalyticalTableDomRef, AnalyticalTablePropTyp
11331136
subComponentsHeight={tableState.subComponentsHeight}
11341137
dispatch={dispatch}
11351138
columnVirtualizer={columnVirtualizer}
1139+
manualGroupBy={reactTableOptions?.manualGroupBy as boolean | undefined}
1140+
subRowsKey={subRowsKey}
11361141
/>
11371142
</VirtualTableBodyContainer>
11381143
)}

packages/main/src/components/AnalyticalTable/util/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export const resolveCellAlignment = (column) => {
133133
return style;
134134
};
135135

136+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
136137
export function getRowHeight(rowHeight: number, tableRef: RefObject<any>) {
137138
if (rowHeight) {
138139
return rowHeight;
@@ -147,3 +148,11 @@ export function getRowHeight(rowHeight: number, tableRef: RefObject<any>) {
147148
// fallback for SSR
148149
return 44;
149150
}
151+
152+
export function getSubRowsByString(subRowsKey, row) {
153+
if (!subRowsKey.includes('.')) {
154+
return row.subRows || row[subRowsKey];
155+
} else {
156+
return subRowsKey.split('.').reduce((acc, cur) => acc?.[cur], row);
157+
}
158+
}

0 commit comments

Comments
 (0)