mirror of
https://github.com/SigNoz/signoz.git
synced 2026-06-09 02:20:26 +01:00
Compare commits
7 Commits
issue_5222
...
pagination
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4b9faad6a | ||
|
|
ef33f2a10a | ||
|
|
c2c693ede2 | ||
|
|
15596d6b63 | ||
|
|
fae41e7dea | ||
|
|
fec202727a | ||
|
|
b60e255475 |
@@ -110,6 +110,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot='drawer-footer']:has(.sa-drawer__keys-pagination) {
|
||||
--dialog-footer-padding: 0 var(--spacing-8);
|
||||
}
|
||||
|
||||
&__footer {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
@@ -123,11 +127,14 @@
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
padding: var(--padding-2) 0;
|
||||
min-height: var(--padding-10);
|
||||
}
|
||||
|
||||
.ant-pagination-total-text {
|
||||
margin-right: auto;
|
||||
}
|
||||
&__pagination-count {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
&__pagination-range {
|
||||
|
||||
@@ -5,7 +5,8 @@ import { Button } from '@signozhq/ui/button';
|
||||
import { DrawerWrapper } from '@signozhq/ui/drawer';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
|
||||
import { Pagination, Skeleton } from 'antd';
|
||||
import { Skeleton } from 'antd';
|
||||
import { Pagination } from '@signozhq/ui/pagination';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import {
|
||||
getListServiceAccountsQueryKey,
|
||||
@@ -208,15 +209,17 @@ function ServiceAccountDrawer({
|
||||
);
|
||||
const keys = keysData?.data ?? [];
|
||||
|
||||
const keysMaxPage = Math.max(1, Math.ceil(keys.length / PAGE_SIZE));
|
||||
const effectiveKeysPage = Math.min(keysPage, keysMaxPage);
|
||||
|
||||
useEffect(() => {
|
||||
if (keysLoading) {
|
||||
return;
|
||||
}
|
||||
const maxPage = Math.max(1, Math.ceil(keys.length / PAGE_SIZE));
|
||||
if (keysPage > maxPage) {
|
||||
void setKeysPage(maxPage);
|
||||
if (keysPage > keysMaxPage) {
|
||||
void setKeysPage(keysMaxPage);
|
||||
}
|
||||
}, [keysLoading, keys.length, keysPage, setKeysPage]);
|
||||
}, [keysLoading, keysMaxPage, keysPage, setKeysPage]);
|
||||
|
||||
// the retry for this mutation is safe due to the api being idempotent on backend
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateServiceAccount();
|
||||
@@ -513,7 +516,7 @@ function ServiceAccountDrawer({
|
||||
isDisabled={isDeleted}
|
||||
canUpdate={canUpdate}
|
||||
accountId={selectedAccountId}
|
||||
currentPage={keysPage}
|
||||
currentPage={effectiveKeysPage}
|
||||
pageSize={PAGE_SIZE}
|
||||
/>
|
||||
) : (
|
||||
@@ -529,25 +532,25 @@ function ServiceAccountDrawer({
|
||||
const footer = (
|
||||
<div className="sa-drawer__footer">
|
||||
{activeTab === ServiceAccountDrawerTab.Keys ? (
|
||||
<Pagination
|
||||
current={keysPage}
|
||||
pageSize={PAGE_SIZE}
|
||||
total={keys.length}
|
||||
showTotal={(total: number, range: number[]): JSX.Element => (
|
||||
<>
|
||||
<div className="sa-drawer__keys-pagination">
|
||||
{keys.length > 0 && (
|
||||
<div className="sa-drawer__pagination-count">
|
||||
<span className="sa-drawer__pagination-range">
|
||||
{range[0]} — {range[1]}
|
||||
{(effectiveKeysPage - 1) * PAGE_SIZE + 1} —{' '}
|
||||
{Math.min(effectiveKeysPage * PAGE_SIZE, keys.length)}
|
||||
</span>
|
||||
<span className="sa-drawer__pagination-total"> of {total}</span>
|
||||
</>
|
||||
<span className="sa-drawer__pagination-total">of {keys.length}</span>
|
||||
</div>
|
||||
)}
|
||||
showSizeChanger={false}
|
||||
hideOnSinglePage
|
||||
onChange={(page): void => {
|
||||
void setKeysPage(page);
|
||||
}}
|
||||
className="sa-drawer__keys-pagination"
|
||||
/>
|
||||
<Pagination
|
||||
current={effectiveKeysPage}
|
||||
pageSize={PAGE_SIZE}
|
||||
total={keys.length}
|
||||
onPageChange={(page): void => {
|
||||
void setKeysPage(page);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{!isDeleted && (
|
||||
|
||||
@@ -302,6 +302,58 @@ describe('ServiceAccountDrawer', () => {
|
||||
await screen.findByText(/No keys/i);
|
||||
});
|
||||
|
||||
it('Keys tab shows pagination count when keys exist', async () => {
|
||||
const keys = [
|
||||
{
|
||||
id: 'k-1',
|
||||
name: 'Key 1',
|
||||
expiresAt: 0,
|
||||
lastObservedAt: null as unknown as string,
|
||||
serviceAccountId: 'sa-1',
|
||||
},
|
||||
{
|
||||
id: 'k-2',
|
||||
name: 'Key 2',
|
||||
expiresAt: 0,
|
||||
lastObservedAt: null as unknown as string,
|
||||
serviceAccountId: 'sa-1',
|
||||
},
|
||||
{
|
||||
id: 'k-3',
|
||||
name: 'Key 3',
|
||||
expiresAt: 0,
|
||||
lastObservedAt: null as unknown as string,
|
||||
serviceAccountId: 'sa-1',
|
||||
},
|
||||
];
|
||||
|
||||
server.use(
|
||||
rest.get(SA_KEYS_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ data: keys })),
|
||||
),
|
||||
);
|
||||
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
renderDrawer();
|
||||
|
||||
await screen.findByDisplayValue('CI Bot');
|
||||
await user.click(screen.getByRole('radio', { name: /Keys/i }));
|
||||
await screen.findByText('Key 1');
|
||||
|
||||
// PAGE_SIZE=15, 3 keys on page 1 → range "1 — 3", total "of 3"
|
||||
const countEl = document.querySelector('.sa-drawer__pagination-count');
|
||||
expect(countEl).toBeInTheDocument();
|
||||
expect(
|
||||
countEl?.querySelector('.sa-drawer__pagination-total')?.textContent,
|
||||
).toBe('of 3');
|
||||
expect(
|
||||
countEl
|
||||
?.querySelector('.sa-drawer__pagination-range')
|
||||
?.textContent?.replace(/\s+/g, ' ')
|
||||
.trim(),
|
||||
).toBe('1 — 3');
|
||||
});
|
||||
|
||||
it('shows error state when account fetch fails', async () => {
|
||||
server.use(
|
||||
rest.get(SA_ENDPOINT, (_, res, ctx) =>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Pagination, Skeleton } from 'antd';
|
||||
import { Skeleton } from 'antd';
|
||||
import { Pagination } from '@signozhq/ui/pagination';
|
||||
import { useListRoles } from 'api/generated/services/role';
|
||||
import { AuthtypesRoleDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import ErrorInPlace from 'components/ErrorInPlace/ErrorInPlace';
|
||||
@@ -116,20 +117,22 @@ function RolesListingTable({
|
||||
|
||||
const totalRoleCount = managedRoles.length + customRoles.length;
|
||||
|
||||
// Ensure current page is valid; if out of bounds, redirect to last available page
|
||||
const maxPage = totalRoleCount > 0 ? Math.ceil(totalRoleCount / PAGE_SIZE) : 1;
|
||||
const effectivePage = Math.min(currentPage, maxPage);
|
||||
|
||||
// Sync URL when currentPage is out of bounds after data changes
|
||||
useEffect(() => {
|
||||
if (isLoading || totalRoleCount === 0) {
|
||||
return;
|
||||
}
|
||||
const maxPage = Math.ceil(totalRoleCount / PAGE_SIZE);
|
||||
if (currentPage > maxPage) {
|
||||
setCurrentPage(maxPage);
|
||||
}
|
||||
}, [isLoading, totalRoleCount, currentPage, setCurrentPage]);
|
||||
}, [isLoading, totalRoleCount, currentPage, maxPage, setCurrentPage]);
|
||||
|
||||
// Paginate: count only role items, but include section headers contextually
|
||||
const paginatedItems = useMemo((): DisplayItem[] => {
|
||||
const startRole = (currentPage - 1) * PAGE_SIZE;
|
||||
const startRole = (effectivePage - 1) * PAGE_SIZE;
|
||||
const endRole = startRole + PAGE_SIZE;
|
||||
let roleIndex = 0;
|
||||
let lastSection: DisplayItem | null = null;
|
||||
@@ -151,16 +154,7 @@ function RolesListingTable({
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}, [displayList, currentPage]);
|
||||
|
||||
const showPaginationItem = (total: number, range: number[]): JSX.Element => (
|
||||
<>
|
||||
<span className="numbers">
|
||||
{range[0]} — {range[1]}
|
||||
</span>
|
||||
<span className="total"> of {total}</span>
|
||||
</>
|
||||
);
|
||||
}, [displayList, effectivePage]);
|
||||
|
||||
if (!hasListPermission && listPerms !== null) {
|
||||
return <PermissionDeniedFullPage permissionName="role:list" />;
|
||||
@@ -280,16 +274,23 @@ function RolesListingTable({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Pagination
|
||||
current={currentPage}
|
||||
pageSize={PAGE_SIZE}
|
||||
total={totalRoleCount}
|
||||
showTotal={showPaginationItem}
|
||||
showSizeChanger={false}
|
||||
hideOnSinglePage
|
||||
onChange={(page): void => setCurrentPage(page)}
|
||||
className="roles-table-pagination"
|
||||
/>
|
||||
<div className="roles-table-pagination">
|
||||
{totalRoleCount > 0 && (
|
||||
<div className="roles-table-count">
|
||||
<span className="numbers">
|
||||
{(effectivePage - 1) * PAGE_SIZE + 1} —{' '}
|
||||
{Math.min(effectivePage * PAGE_SIZE, totalRoleCount)}
|
||||
</span>
|
||||
<span className="total">of {totalRoleCount}</span>
|
||||
</div>
|
||||
)}
|
||||
<Pagination
|
||||
current={effectivePage}
|
||||
pageSize={PAGE_SIZE}
|
||||
total={totalRoleCount}
|
||||
onPageChange={(page): void => setCurrentPage(page)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -212,7 +212,10 @@
|
||||
justify-content: flex-end;
|
||||
padding: 8px 16px;
|
||||
|
||||
.ant-pagination-total-text {
|
||||
.roles-table-count {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
margin-right: auto;
|
||||
|
||||
.numbers {
|
||||
|
||||
@@ -226,6 +226,20 @@ describe('RolesSettings', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('shows pagination count text with correct range and total', async () => {
|
||||
render(<RolesSettings />);
|
||||
|
||||
await expect(screen.findByText('signoz-admin')).resolves.toBeInTheDocument();
|
||||
|
||||
// 5 roles total: range shows "1 — 5", total shows "of 5"
|
||||
const countEl = document.querySelector('.roles-table-count');
|
||||
expect(countEl).toBeInTheDocument();
|
||||
expect(countEl?.querySelector('.total')?.textContent).toBe('of 5');
|
||||
expect(
|
||||
countEl?.querySelector('.numbers')?.textContent?.replace(/\s+/g, ' ').trim(),
|
||||
).toBe('1 — 5');
|
||||
});
|
||||
|
||||
it('handles invalid dates gracefully by showing fallback', async () => {
|
||||
const invalidRole = {
|
||||
id: 'edge-0009',
|
||||
|
||||
Reference in New Issue
Block a user