Compare commits

...

7 Commits

Author SHA1 Message Date
SagarRajput-7
f4b9faad6a chore(pagination): style updates 2026-06-09 02:23:16 +05:30
SagarRajput-7
ef33f2a10a chore(pagination): feedback fixes 2026-06-09 01:43:13 +05:30
SagarRajput-7
c2c693ede2 Merge branch 'main' into pagination-migration 2026-06-09 00:54:35 +05:30
SagarRajput-7
15596d6b63 chore(pagination): added test cases 2026-06-09 00:30:30 +05:30
SagarRajput-7
fae41e7dea chore(pagination): fix the styles 2026-06-08 18:43:45 +05:30
SagarRajput-7
fec202727a Merge branch 'main' into pagination-migration 2026-06-08 17:58:58 +05:30
SagarRajput-7
b60e255475 chore(pagination): upgrade pagination import to use from signozhq 2026-06-05 19:24:51 +05:30
6 changed files with 132 additions and 52 deletions

View File

@@ -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 {

View File

@@ -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]} &#8212; {range[1]}
{(effectiveKeysPage - 1) * PAGE_SIZE + 1} &#8212;{' '}
{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 && (

View File

@@ -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) =>

View File

@@ -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]} &#8212; {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} &#8212;{' '}
{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>
);
}

View File

@@ -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 {

View File

@@ -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',