Merge remote-tracking branch 'remotes/from/ce/main'

This commit is contained in:
hc-github-team-secure-vault-core 2026-04-09 23:13:38 +00:00
commit 8416951dd7
5 changed files with 90 additions and 19 deletions

3
changelog/_13794.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
ui: Fix secrets table pagination when switching page sizes.
```

View File

@ -73,7 +73,10 @@ export default class ListTable extends Component<Args> {
}
@action
handlePaginationChange(action: 'currentPage' | 'pageSize', value: number) {
async handlePaginationChange(action: 'currentPage' | 'pageSize', value: number) {
if (action === 'pageSize') {
await this.resetPagination();
}
this[action] = value;
}
@ -88,9 +91,9 @@ export default class ListTable extends Component<Args> {
}
// TEMPLATE HELPERS
isObject = (value: any) => typeof value === 'object' && value !== null;
isObject = (value: unknown) => typeof value === 'object' && value !== null;
identifier = (cellData: Record<string, any>) => {
identifier = (cellData: Record<string, unknown>) => {
const firstColumn = this.args.columns[0]?.key;
// Use selectionKeyField if provided, otherwise default to value of the first column
const identifier = this.args.selectionKeyField || firstColumn;

View File

@ -34,31 +34,33 @@ export function paginate<T>(data: T[], options: PaginateOptions = {}) {
const { page = 1, pageSize = DEFAULT_PAGE_SIZE, filter, filterKey } = options;
if (Array.isArray(data)) {
let filteredData = [...data];
// filter data before paginating if filter is provided
if (filter) {
filteredData = data.filter((item) => {
const filterValue = filterKey ? (item as Record<string, unknown>)[filterKey] : item;
if (typeof filterValue === 'string') {
return filterValue.toLowerCase().includes(filter.toLowerCase());
}
return false;
});
}
let filteredData = filter
? data.filter((item) => {
const filterValue = filterKey ? (item as Record<string, unknown>)[filterKey] : item;
if (typeof filterValue === 'string') {
return filterValue.toLowerCase().includes(filter.toLowerCase());
}
return false;
})
: [...data];
const lastPage = Math.ceil(filteredData.length / pageSize);
const start = (page - 1) * pageSize;
const filteredTotal = filteredData.length;
const lastPage = Math.ceil(filteredTotal / pageSize);
// Verify that the page number does not go out of bounds, if so adjust to show
// the first page of results
const currentPage = page > lastPage ? 1 : page;
const start = (currentPage - 1) * pageSize;
const end = start + pageSize;
filteredData = filteredData.slice(start, end);
// add meta data previously from lazyPaginatedQuery since components expect it
Object.defineProperty(filteredData, 'meta', {
value: {
currentPage: page,
currentPage,
lastPage,
nextPage: page + 1,
prevPage: page - 1,
total: data.length,
filteredTotal: filteredData.length,
filteredTotal,
pageSize,
},
writable: false,

View File

@ -177,6 +177,36 @@ module('Integration | Component | list-table', function (hooks) {
.hasText('5', 'custom table item renders yielded badge');
});
test('it shows first page data and correct metadata when navigated page exceeds new data bounds', async function (assert) {
const moreData = [
{ island: 'Tahiti', visit_length: 12, trip_date: '2025-05-10T00:00:00.000Z' },
{ island: 'Barbados', visit_length: 6, trip_date: '2025-08-25T00:00:00.000Z' },
{ island: 'Cyprus', visit_length: 9, trip_date: '2026-03-12T00:00:00.000Z' },
{ island: 'Jamaica', visit_length: 7, trip_date: '2025-11-05T00:00:00.000Z' },
];
this.data = [...MOCK_DATA, ...moreData];
await this.renderComponent();
await fillIn(GENERAL.paginationSizeSelector, '5');
await click(GENERAL.nextPage);
assert.dom(GENERAL.paginationInfo).hasText(`610 of ${this.data.length}`, 'navigated to page 2');
// Replace data with fewer items than current page offset such that page 2 no longer exists
this.set('data', [
{ island: 'Palawan', visit_length: 9, trip_date: '2025-11-14T00:00:00.000Z' },
{ island: 'Mykonos', visit_length: 3, trip_date: '2026-02-28T00:00:00.000Z' },
]);
await waitFor(GENERAL.paginationInfo);
assert
.dom(GENERAL.paginationInfo)
.hasText(
`12 of ${this.data.length}`,
'falls back to page 1 when current page exceeds new data bounds'
);
assert.dom(GENERAL.tableData(0, 'island')).hasText('Palawan', 'first page data is shown after fallback');
});
test('it resets pagination when data changes', async function (assert) {
const moreData = [
{ island: 'Tahiti', visit_length: 12, trip_date: '2025-05-10T00:00:00.000Z' },

View File

@ -59,7 +59,7 @@ module('Unit | Utility | paginate-list', function (hooks) {
nextPage: 3,
prevPage: 1,
total: 20,
filteredTotal: 3,
filteredTotal: 20,
pageSize: 3,
};
assert.deepEqual(meta, expectedMeta, 'returns correct meta data');
@ -70,4 +70,37 @@ module('Unit | Utility | paginate-list', function (hooks) {
const expected = [18, 19];
assert.deepEqual(paginatedData, expected, 'returns correct items for last page');
});
test('filteredTotal reflects total matching items', function (assert) {
// 20 items, filter matches first 6 (0-5), paginate to page 1 with size 4
const data = Array.from({ length: 20 }, (_, i) => ({ id: i, name: i < 6 ? `match-${i}` : `skip-${i}` }));
const { meta } = paginate(data, { page: 1, pageSize: 4, filter: 'match', filterKey: 'name' });
assert.strictEqual(meta.filteredTotal, 6, 'filteredTotal is total matching items across all pages');
assert.strictEqual(meta.lastPage, 2, 'lastPage is based on filteredTotal');
});
test('it should reset to page 1 when page exceeds lastPage', function (assert) {
// 20 items, pageSize 10 = 2 pages; requesting page 5 should fall back to page 1
const paginatedData = paginate(this.items, { page: 5, pageSize: 10 });
assert.deepEqual(
paginatedData,
this.items.slice(0, 10),
'returns first page of items when page is out of bounds'
);
assert.strictEqual(
paginatedData.meta.currentPage,
1,
'currentPage in meta is 1, not the out-of-bounds page'
);
});
test('meta currentPage matches actual data shown when page is out of bounds', function (assert) {
const { meta } = paginate(this.items, { page: 99, pageSize: 5 });
assert.strictEqual(
meta.currentPage,
1,
'currentPage in meta reflects actual page shown, not requested page'
);
assert.strictEqual(meta.lastPage, 4, 'lastPage is computed correctly');
});
});