fix(uplifts): keep a failed queue from rendering as an empty archive

This commit is contained in:
Alexandre Brandizzi 2026-09-25 16:48:46 -03:00
parent 70501693c8
commit 3ed45547db
3 changed files with 51 additions and 11 deletions

View file

@ -295,9 +295,31 @@ function UpliftApprovalsEmptyState({ tab }: { tab: UpliftApprovalTab }) {
); );
} }
// A failed queue is not an empty one: the page's error alert explains it, so the
// empty-state copy must not also claim the list has nothing in it.
function UpliftApprovalsEmptyRow({
tab,
colSpan,
hasError,
}: {
tab: UpliftApprovalTab;
colSpan: number;
hasError: boolean;
}) {
if (hasError) return null;
return (
<TableRow>
<TableCell colSpan={colSpan} align="center" className="py-10">
<UpliftApprovalsEmptyState tab={tab} />
</TableCell>
</TableRow>
);
}
export function UpliftApprovalsTable({ export function UpliftApprovalsTable({
tab, tab,
isLoading, isLoading,
hasError,
rows, rows,
canRevoke, canRevoke,
onOpenDetail, onOpenDetail,
@ -309,6 +331,7 @@ export function UpliftApprovalsTable({
}: { }: {
tab: UpliftApprovalTab; tab: UpliftApprovalTab;
isLoading: boolean; isLoading: boolean;
hasError: boolean;
rows: UpliftQueueItem[]; rows: UpliftQueueItem[];
canRevoke: boolean; canRevoke: boolean;
onOpenDetail: (row: UpliftQueueItem) => void; onOpenDetail: (row: UpliftQueueItem) => void;
@ -339,11 +362,7 @@ export function UpliftApprovalsTable({
</TableCell> </TableCell>
</TableRow> </TableRow>
) : rows.length === 0 ? ( ) : rows.length === 0 ? (
<TableRow> <UpliftApprovalsEmptyRow tab={tab} colSpan={headerCells.length} hasError={hasError} />
<TableCell colSpan={headerCells.length} align="center" className="py-10">
<UpliftApprovalsEmptyState tab={tab} />
</TableCell>
</TableRow>
) : ( ) : (
rows.map((row) => ( rows.map((row) => (
<UpliftApprovalRow <UpliftApprovalRow

View file

@ -173,6 +173,7 @@ export default function UpliftQueuePage() {
approved: knownTotal(approvedQuery), approved: knownTotal(approvedQuery),
rejected: knownTotal(rejectedQuery), rejected: knownTotal(rejectedQuery),
}; };
const activeTotal = counts[tab];
const pendingExposureTotal = pendingQuery.data?.pendingExposureTotal ?? null; const pendingExposureTotal = pendingQuery.data?.pendingExposureTotal ?? null;
const handleTabChange = (_event: unknown, value: string) => { const handleTabChange = (_event: unknown, value: string) => {
@ -189,6 +190,7 @@ export default function UpliftQueuePage() {
<UpliftApprovalsTable <UpliftApprovalsTable
tab={tab} tab={tab}
isLoading={activeQuery.isLoading} isLoading={activeQuery.isLoading}
hasError={Boolean(activeQuery.error)}
rows={rows} rows={rows}
canRevoke={canRevokeUplifts} canRevoke={canRevokeUplifts}
onOpenDetail={openDetail} onOpenDetail={openDetail}
@ -198,11 +200,13 @@ export default function UpliftQueuePage() {
onOpenAttachment={openUpliftEvidence} onOpenAttachment={openUpliftEvidence}
isDecisionPending={approvePending || rejectPending || revokePending} isDecisionPending={approvePending || rejectPending || revokePending}
/> />
<UpliftApprovalsPagination {activeTotal != null && (
count={activeQuery.data?.totalCount ?? 0} <UpliftApprovalsPagination
page={pages[tab]} count={activeTotal}
onPageChange={(page) => setPages((current) => ({ ...current, [tab]: page }))} page={pages[tab]}
/> onPageChange={(page) => setPages((current) => ({ ...current, [tab]: page }))}
/>
)}
{detailItem != null && ( {detailItem != null && (
<UpliftDetailModal <UpliftDetailModal
item={detailItem} item={detailItem}

View file

@ -1,5 +1,5 @@
import { fireEvent, screen, within } from "@testing-library/react"; import { fireEvent, screen, within } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest"; import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { import type {
UpliftQueueItem, UpliftQueueItem,
UpliftsQueueParams, UpliftsQueueParams,
@ -128,6 +128,12 @@ async function openRejectedTab() {
} }
describe("Uplift Approvals rejected tab", () => { describe("Uplift Approvals rejected tab", () => {
// Load the page module once up front so the first test's budget is not spent on a
// cold import of the whole route.
beforeAll(async () => {
await import("@/app/(protected)/uplifts/index");
}, 60_000);
beforeEach(() => { beforeEach(() => {
queueState.requestedStatuses = []; queueState.requestedStatuses = [];
queueState.errorByStatus = {}; queueState.errorByStatus = {};
@ -164,6 +170,17 @@ describe("Uplift Approvals rejected tab", () => {
expect(screen.getByLabelText("1 pending uplifts")).toBeInTheDocument(); expect(screen.getByLabelText("1 pending uplifts")).toBeInTheDocument();
}); });
it("shows the load error instead of an empty archive when the rejected queue fails", async () => {
queueState.byStatus.Rejected = undefined;
queueState.errorByStatus.Rejected = new Error("Failed to load uplift queue");
const table = await openRejectedTab();
expect(screen.getByRole("alert")).toHaveTextContent("Failed to load uplift queue");
expect(within(table).queryByText("No rejected uplifts.")).not.toBeInTheDocument();
expect(within(table).queryByTestId("HighlightOffOutlinedIcon")).not.toBeInTheDocument();
expect(screen.queryByText(/of 0/)).not.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();