Skip to content

Commit 9545a04

Browse files
chore: Track table counter text from title as fallback (#3549)
1 parent 86e81d5 commit 9545a04

File tree

12 files changed

+151
-34
lines changed

12 files changed

+151
-34
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@
164164
{
165165
"path": "lib/components/internal/widget-exports.js",
166166
"brotli": false,
167-
"limit": "786 kB",
167+
"limit": "790 kB",
168168
"ignore": "react-dom"
169169
}
170170
],

pages/funnel-analytics/with-table.page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ export default function WithTablePage() {
9191
}}
9292
selectionType="multi"
9393
header={
94-
<Header headingTagOverride="h1" counter={`(${allItems.length})`}>
94+
<Header headingTagOverride="h1" counter={`(1/${allItems.length})`}>
9595
Table title
9696
</Header>
9797
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import { useEffect, useMemo } from 'react';
4+
5+
import { parseCountValue } from '../../internal/analytics/utils/parse-count-text';
6+
import { useTableComponentsContext } from '../../internal/context/table-component-context';
7+
8+
/**
9+
* Custom hook that integrates table counter values with table component context.
10+
*
11+
* The extracted count value is automatically synchronized with the table header
12+
* component through the table context, updating the countText property.
13+
*/
14+
export const useTableIntegration = (countText: string | undefined) => {
15+
const tableComponentContext = useTableComponentsContext();
16+
const countValue = useMemo(() => parseCountValue(countText), [countText]);
17+
18+
useEffect(() => {
19+
if (tableComponentContext?.headerRef?.current && countValue !== undefined) {
20+
tableComponentContext.headerRef.current.totalCount = countValue;
21+
22+
return () => {
23+
delete tableComponentContext.headerRef.current?.totalCount;
24+
};
25+
}
26+
}, [tableComponentContext?.headerRef, countValue]);
27+
};

src/header/internal.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { useMobile } from '../internal/hooks/use-mobile';
1616
import { useUniqueId } from '../internal/hooks/use-unique-id';
1717
import { useVisualRefresh } from '../internal/hooks/use-visual-mode';
1818
import { SomeRequired } from '../internal/types';
19+
import { useTableIntegration } from './analytics/use-table-integration';
1920
import { HeaderProps } from './interfaces';
2021

2122
import analyticsSelectors from './analytics-metadata/styles.css.js';
@@ -49,6 +50,9 @@ export default function InternalHeader({
4950
const assignHeaderId = useContext(CollectionLabelContext).assignId;
5051
const isInContainer = useContainerHeader();
5152
const headingId = useUniqueId('heading');
53+
54+
useTableIntegration(counter);
55+
5256
if (assignHeaderId !== undefined) {
5357
assignHeaderId(headingId);
5458
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { parseCountValue } from '../../../../../lib/components/internal/analytics/utils/parse-count-text.js';
5+
6+
test('parses out only the number from a given countText', () => {
7+
expect(parseCountValue('1 item')).toBe(1);
8+
});
9+
10+
test('parses out the total number from a given countText', () => {
11+
expect(parseCountValue('1/5')).toBe(5);
12+
});
13+
14+
test('parse out the number on open ended counts', () => {
15+
expect(parseCountValue('100+')).toBe(100);
16+
});
17+
18+
test('returns undefined when no numbers found', () => {
19+
expect(parseCountValue('No items')).toBeUndefined();
20+
expect(parseCountValue('items only')).toBeUndefined();
21+
});
22+
23+
// Format variations
24+
test('handles parentheses format', () => {
25+
expect(parseCountValue('Items (25)')).toBe(25);
26+
expect(parseCountValue('(100)')).toBe(100);
27+
});
28+
29+
test('handles strings with extra whitespace', () => {
30+
expect(parseCountValue(' 100 ')).toBe(100);
31+
expect(parseCountValue('\t50\n')).toBe(50);
32+
});
33+
34+
test('returns undefined for undefined input', () => {
35+
expect(parseCountValue(undefined)).toBeUndefined();
36+
});
37+
38+
test('returns undefined for empty string', () => {
39+
expect(parseCountValue('')).toBeUndefined();
40+
});
41+
42+
test('returns undefined for non-string input', () => {
43+
expect(parseCountValue(123 as any)).toBeUndefined();
44+
expect(parseCountValue(null as any)).toBeUndefined();
45+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
/**
5+
* Extracts the count value from table header/filter text.
6+
*
7+
* Parses various counter string formats and extracts the relevant numeric value:
8+
* - "Items (100)" - Extracts 100 (first number found)
9+
* - "1/100" - Extracts 100 (denominator of fraction, representing total count)
10+
* - "100+" - Extracts 100 (first number found)
11+
*/
12+
export const parseCountValue = (countText: string | undefined): number | undefined => {
13+
if (!countText || typeof countText !== 'string') {
14+
return undefined;
15+
}
16+
17+
const target = countText.includes('/') ? countText.split('/')[1] : countText;
18+
const match = target.match(/\d+/);
19+
return match ? parseInt(match[0], 10) : undefined;
20+
};

src/internal/context/__tests__/table-component-context.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ describe('Verify TableComponentsContext', () => {
3131
},
3232
filterRef: { current: { filterText: 'test', filterCount: 10, filtered: true } },
3333
preferencesRef: { current: { pageSize: 20, visibleColumns: ['id'] } },
34+
headerRef: { current: { totalCount: 10 } },
3435
}}
3536
>
3637
<ChildComponent />
@@ -90,6 +91,7 @@ describe('Verify TableComponentsContext', () => {
9091
},
9192
filterRef: { current: { filterText: 'test', filterCount: 10, filtered: true } },
9293
preferencesRef: { current: { pageSize: 20, visibleColumns: ['id'] } },
94+
headerRef: { current: { totalCount: 10 } },
9395
}}
9496
>
9597
<ChildComponent />

src/internal/context/table-component-context.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
// SPDX-License-Identifier: Apache-2.0
33
import { createContext, RefObject, useContext } from 'react';
44

5+
export interface HeaderRef {
6+
totalCount?: number;
7+
}
8+
59
export interface FilterRef {
610
filtered?: boolean;
711
filterText?: string;
@@ -21,6 +25,7 @@ export interface PreferencesRef {
2125
}
2226

2327
interface TableComponentsContextProps {
28+
headerRef: RefObject<HeaderRef>;
2429
filterRef: RefObject<FilterRef>;
2530
paginationRef: RefObject<PaginationRef>;
2631
preferencesRef: RefObject<PreferencesRef>;

src/table/__integ__/component-metrics.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ const baseComponentConfiguration = {
5353
},
5454
filtered: false,
5555
filteredBy: [],
56+
filteredCount: 4000,
5657
totalNumberOfResources: 4000,
5758
pagination: {
5859
currentPageIndex: 1,
@@ -80,6 +81,7 @@ const basePropertyFilterConfiguration = {
8081
},
8182
filtered: false,
8283
filteredBy: [],
84+
filteredCount: 4000,
8385
totalNumberOfResources: 4000,
8486
pagination: {
8587
currentPageIndex: 1,
@@ -298,7 +300,7 @@ describe('filtering', () => {
298300
componentConfiguration: {
299301
...baseComponentConfiguration,
300302
filtered: true,
301-
totalNumberOfResources: 92,
303+
filteredCount: 92,
302304
pagination: {
303305
currentPageIndex: 1,
304306
openEnd: false,
@@ -327,7 +329,7 @@ describe('filtering', () => {
327329
...basePropertyFilterConfiguration,
328330
filtered: true,
329331
filteredBy: ['state'],
330-
totalNumberOfResources: 852,
332+
filteredCount: 852,
331333
pagination: {
332334
currentPageIndex: 1,
333335
openEnd: false,
@@ -356,7 +358,7 @@ describe('filtering', () => {
356358
...basePropertyFilterConfiguration,
357359
filtered: true,
358360
filteredBy: [],
359-
totalNumberOfResources: 852,
361+
filteredCount: 852,
360362
pagination: {
361363
currentPageIndex: 1,
362364
openEnd: false,

src/table/internal.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { CollectionLabelContext } from '../internal/context/collection-label-con
1818
import { LinkDefaultVariantContext } from '../internal/context/link-default-variant-context';
1919
import {
2020
FilterRef,
21+
HeaderRef,
2122
PaginationRef,
2223
PreferencesRef,
2324
TableComponentsContextProvider,
@@ -193,6 +194,7 @@ const InternalTable = React.forwardRef(
193194
const paginationRef = useRef<PaginationRef>({});
194195
const filterRef = useRef<FilterRef>({});
195196
const preferencesRef = useRef<PreferencesRef>({});
197+
const headerRef = useRef<HeaderRef>({});
196198
/* istanbul ignore next: performance marks do not work in JSDOM */
197199
const getHeaderText = () =>
198200
toolsHeaderPerformanceMarkRef.current?.querySelector<HTMLElement>(`.${headerStyles['heading-text']}`)
@@ -232,6 +234,7 @@ const InternalTable = React.forwardRef(
232234
});
233235
};
234236
const getComponentConfiguration = () => {
237+
const headerData = headerRef.current;
235238
const filterData = filterRef.current;
236239
const paginationData = paginationRef.current;
237240
const preferencesData = preferencesRef.current;
@@ -248,9 +251,10 @@ const InternalTable = React.forwardRef(
248251
columnId: sortingColumn?.sortingField,
249252
sortingOrder: sortingColumn ? (sortingDescending ? 'desc' : 'asc') : undefined,
250253
},
251-
filtered: Boolean(filterData.filtered),
254+
filtered: filterData?.filtered ?? null,
252255
filteredBy: filterData?.filteredBy ?? [],
253-
totalNumberOfResources: filterRef.current?.filterCount ?? null,
256+
filteredCount: filterData?.filterCount ?? null,
257+
totalNumberOfResources: headerData?.totalCount ?? null,
254258
tablePreferences: {
255259
visibleColumns: preferencesData?.visibleColumns ?? [],
256260
resourcesPerPage: preferencesData?.pageSize ?? null,
@@ -433,7 +437,7 @@ const InternalTable = React.forwardRef(
433437

434438
return (
435439
<LinkDefaultVariantContext.Provider value={{ defaultVariant: 'primary' }}>
436-
<TableComponentsContextProvider value={{ paginationRef, filterRef, preferencesRef }}>
440+
<TableComponentsContextProvider value={{ paginationRef, filterRef, preferencesRef, headerRef }}>
437441
<ColumnWidthsProvider
438442
visibleColumns={visibleColumnWidthsWithSelection}
439443
resizableColumns={resizableColumns}

0 commit comments

Comments
 (0)