fix(uplifts): show an unavailable count pill while a queue is loading or failed

This commit is contained in:
Alexandre Brandizzi 2026-09-25 13:43:11 -03:00
parent 70e52937aa
commit 70501693c8
2 changed files with 49 additions and 13 deletions

View file

@ -18,6 +18,7 @@ import { useAuthContext } from "@/providers/auth-context";
import { useUpliftApprovalController } from "@/domain/uplifts/use-cases/use-uplift-approval-controller"; import { useUpliftApprovalController } from "@/domain/uplifts/use-cases/use-uplift-approval-controller";
import { useUpliftsQueue } from "@/domain/uplifts/use-cases/use-uplifts-queue"; import { useUpliftsQueue } from "@/domain/uplifts/use-cases/use-uplifts-queue";
import { Text } from "@/components/ui/text"; import { Text } from "@/components/ui/text";
import type { UpliftsQueueResult } from "@/domain/uplifts/types/uplift";
const PAGE_SIZE = 25; const PAGE_SIZE = 25;
@ -47,13 +48,33 @@ const TAB_LABELS: Record<UpliftApprovalTab, string> = {
rejected: "Rejected", rejected: "Rejected",
}; };
// A tab's total is only known once its queue has loaded without error; until then the
// pill must not claim a count (a loading or failed queue is not an empty one).
function knownTotal(query: {
data?: UpliftsQueueResult | undefined;
error: unknown;
}): number | null {
if (query.error || !query.data) return null;
return query.data.totalCount;
}
function TabCountPill({ tab, count }: { tab: UpliftApprovalTab; count: number | null }) {
return (
<Chip
size="small"
label={count ?? "—"}
aria-label={count == null ? `${tab} uplift count unavailable` : `${count} ${tab} uplifts`}
/>
);
}
function UpliftApprovalsTabs({ function UpliftApprovalsTabs({
tab, tab,
counts, counts,
onChange, onChange,
}: { }: {
tab: UpliftApprovalTab; tab: UpliftApprovalTab;
counts: Record<UpliftApprovalTab, number>; counts: Record<UpliftApprovalTab, number | null>;
onChange: (_event: unknown, value: string) => void; onChange: (_event: unknown, value: string) => void;
}) { }) {
return ( return (
@ -65,11 +86,7 @@ function UpliftApprovalsTabs({
label={ label={
<Stack direction="row" spacing={1} sx={{ alignItems: "center" }}> <Stack direction="row" spacing={1} sx={{ alignItems: "center" }}>
<span>{TAB_LABELS[value]}</span> <span>{TAB_LABELS[value]}</span>
<Chip <TabCountPill tab={value} count={counts[value]} />
size="small"
label={counts[value]}
aria-label={`${counts[value]} ${value} uplifts`}
/>
</Stack> </Stack>
} }
/> />
@ -152,9 +169,9 @@ export default function UpliftQueuePage() {
const activeQuery = queries[tab]; const activeQuery = queries[tab];
const rows = activeQuery.data?.items ?? []; const rows = activeQuery.data?.items ?? [];
const counts = { const counts = {
pending: pendingQuery.data?.totalCount ?? 0, pending: knownTotal(pendingQuery),
approved: approvedQuery.data?.totalCount ?? 0, approved: knownTotal(approvedQuery),
rejected: rejectedQuery.data?.totalCount ?? 0, rejected: knownTotal(rejectedQuery),
}; };
const pendingExposureTotal = pendingQuery.data?.pendingExposureTotal ?? null; const pendingExposureTotal = pendingQuery.data?.pendingExposureTotal ?? null;

View file

@ -9,7 +9,8 @@ import { formatDateTime } from "@/lib/time-utils";
import { renderWithProviders } from "@/test/test-utils"; import { renderWithProviders } from "@/test/test-utils";
const queueState = vi.hoisted(() => ({ const queueState = vi.hoisted(() => ({
byStatus: {} as Record<string, UpliftsQueueResult>, byStatus: {} as Record<string, UpliftsQueueResult | undefined>,
errorByStatus: {} as Record<string, Error | undefined>,
requestedStatuses: [] as Array<string | undefined>, requestedStatuses: [] as Array<string | undefined>,
})); }));
@ -92,11 +93,13 @@ function page(items: UpliftQueueItem[], totalCount = items.length): UpliftsQueue
vi.mock("@/domain/uplifts/use-cases/use-uplifts-queue", () => ({ vi.mock("@/domain/uplifts/use-cases/use-uplifts-queue", () => ({
useUpliftsQueue: (params: UpliftsQueueParams) => { useUpliftsQueue: (params: UpliftsQueueParams) => {
queueState.requestedStatuses.push(params.status); queueState.requestedStatuses.push(params.status);
const data = queueState.byStatus[params.status ?? ""];
const error = queueState.errorByStatus[params.status ?? ""] ?? null;
return { return {
data: queueState.byStatus[params.status ?? ""] ?? null, data,
isLoading: false, isLoading: data === undefined && error === null,
isFetching: false, isFetching: false,
error: null, error,
refetch: vi.fn(), refetch: vi.fn(),
}; };
}, },
@ -127,6 +130,7 @@ async function openRejectedTab() {
describe("Uplift Approvals rejected tab", () => { describe("Uplift Approvals rejected tab", () => {
beforeEach(() => { beforeEach(() => {
queueState.requestedStatuses = []; queueState.requestedStatuses = [];
queueState.errorByStatus = {};
queueState.byStatus = { queueState.byStatus = {
Pending: page([pendingItem]), Pending: page([pendingItem]),
Approved: page([approvedItem]), Approved: page([approvedItem]),
@ -145,6 +149,21 @@ describe("Uplift Approvals rejected tab", () => {
expect(queueState.requestedStatuses).toContain("Rejected"); expect(queueState.requestedStatuses).toContain("Rejected");
}); });
it("never claims a count for a queue that is still loading or failed to load", async () => {
queueState.byStatus.Rejected = undefined;
queueState.errorByStatus.Rejected = new Error("Forbidden");
queueState.byStatus.Approved = undefined;
const { default: UpliftQueuePage } = await import("@/app/(protected)/uplifts/index");
renderWithProviders(<UpliftQueuePage />);
const rejectedPill = screen.getByLabelText("rejected uplift count unavailable");
expect(rejectedPill).toHaveTextContent("—");
expect(screen.getByLabelText("approved uplift count unavailable")).toHaveTextContent("—");
expect(screen.queryByLabelText("0 rejected uplifts")).not.toBeInTheDocument();
expect(screen.queryByLabelText("0 approved uplifts")).not.toBeInTheDocument();
expect(screen.getByLabelText("1 pending uplifts")).toBeInTheDocument();
});
it("lists only rejected uplifts, in the server's most-recently-rejected order", async () => { it("lists only rejected uplifts, in the server's most-recently-rejected order", async () => {
const table = await openRejectedTab(); const table = await openRejectedTab();