mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-05 04:16:31 +02:00
Merge remote-tracking branch 'remotes/from/ce/main'
This commit is contained in:
commit
8416951dd7
3
changelog/_13794.txt
Normal file
3
changelog/_13794.txt
Normal file
@ -0,0 +1,3 @@
|
||||
```release-note:bug
|
||||
ui: Fix secrets table pagination when switching page sizes.
|
||||
```
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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(`6–10 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(
|
||||
`1–2 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' },
|
||||
|
||||
@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user