mirror of
https://github.com/warmbly/warmbly.git
synced 2026-10-20 08:03:09 +00:00
feat: show failed or partial Accounts mailbox inventory as unavailable instead of measured zero counts or an empty list
This commit is contained in:
committed by
Devin AI
parent
a8ddfc65ec
commit
5a542e1222
@@ -7,7 +7,7 @@ Browsing filters, searches and view choices are [remembered in your browser tab]
|
||||
|
||||
A mailbox is a sender account you connect to Warmbly. Every warmup message and campaign email goes out through one you own.
|
||||
|
||||
The Accounts list checks mailbox health and, on linked self-hosted instances, Cloud warmup standing separately from the mailbox inventory. While those checks are loading or refreshing, it shows **Checking…** rather than treating missing results as Idle, Healthy, or zero sent. A failed or incomplete check shows **Unavailable**, not a measured zero or a claim that the mailbox recovered. A previously reported issue may remain marked **Last observed** for up to five minutes during a refresh; this is historical evidence, not a claim that it is still failing. A newly measured healthy result replaces it. A displayed health score is an account-health assessment, not proof that the provider confirmed a send; check send records and the mailbox's error details when investigating a stopped mailbox.
|
||||
The Accounts list checks mailbox health and, on linked self-hosted instances, Cloud warmup standing separately from the mailbox inventory. While those checks are loading or refreshing, it shows **Checking…** rather than treating missing results as Idle, Healthy, or zero sent. A failed or incomplete check shows **Unavailable**, not a measured zero or a claim that the mailbox recovered. If the mailbox list itself cannot be loaded, or only part of it loaded, the summary counts show **Unavailable** too, rather than an empty account list. A previously reported issue may remain marked **Last observed** for up to five minutes during a refresh; this is historical evidence, not a claim that it is still failing. A newly measured healthy result replaces it. A displayed health score is an account-health assessment, not proof that the provider confirmed a send; check send records and the mailbox's error details when investigating a stopped mailbox.
|
||||
|
||||
### Diagnostic participation and stopping
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import type AccountStatus from "@/lib/api/models/app/analytics/AccountStatus";
|
||||
import AddressesPage from "./page";
|
||||
|
||||
const { tags, rows, statusState } = vi.hoisted(() => ({
|
||||
statusState: { data: [] as AccountStatus[], observations: [] as { status: AccountStatus; observedAt: number; current: boolean }[], isLoading: false, isFetching: false, isError: false, cloudLoading: false, cloudUnavailable: false, emailsLoading: false },
|
||||
statusState: { data: [] as AccountStatus[], observations: [] as { status: AccountStatus; observedAt: number; current: boolean }[], isLoading: false, isFetching: false, isError: false, cloudLoading: false, cloudUnavailable: false, emailsLoading: false, emailsError: false, emailsIncomplete: false },
|
||||
tags: [
|
||||
{ id: "sending", title: "Sending account", color: "#0088cc", position: 0 },
|
||||
{ id: "serveblink", title: "Serveblink.com", color: "#008800", position: 1 },
|
||||
@@ -30,7 +30,7 @@ vi.mock("@/hooks/context/user", async (original) => ({
|
||||
vi.mock("@/hooks/context/confirm", () => ({ useConfirm: () => ({ show: vi.fn() }) }));
|
||||
vi.mock("@/hooks/usePermission", () => ({ usePermission: () => true }));
|
||||
vi.mock("@/lib/api/hooks/app/emails/useEmails", () => ({
|
||||
default: ({ tag }: { tag: string }) => ({ emails: statusState.emailsLoading ? [] : tag ? rows.filter((row) => row.tags.includes(tag)) : rows, isLoading: statusState.emailsLoading, isPending: statusState.emailsLoading }),
|
||||
default: ({ tag }: { tag: string }) => ({ emails: statusState.emailsLoading || statusState.emailsError ? [] : tag ? rows.filter((row) => row.tags.includes(tag)) : rows, isLoading: statusState.emailsLoading, isPending: statusState.emailsLoading, isError: statusState.emailsError || statusState.emailsIncomplete, isIncomplete: statusState.emailsIncomplete, refetch: vi.fn() }),
|
||||
}));
|
||||
vi.mock("@/lib/api/hooks/app/analytics/useAccountStatuses", () => ({ default: () => statusState }));
|
||||
vi.mock("@/lib/api/hooks/app/subscription/useFeatureStatus", () => ({ default: () => ({ data: { can_use_warmup: true } }) }));
|
||||
@@ -149,6 +149,8 @@ describe("mailbox status while account and Cloud checks settle", () => {
|
||||
statusState.cloudLoading = false;
|
||||
statusState.cloudUnavailable = false;
|
||||
statusState.emailsLoading = false;
|
||||
statusState.emailsError = false;
|
||||
statusState.emailsIncomplete = false;
|
||||
});
|
||||
afterEach(() => { cleanup(); vi.restoreAllMocks(); });
|
||||
|
||||
@@ -159,6 +161,22 @@ describe("mailbox status while account and Cloud checks settle", () => {
|
||||
expect(screen.queryByText("Healthy 100")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows failed or partial mailbox inventory as unavailable rather than measured zeros", async () => {
|
||||
statusState.emailsError = true;
|
||||
await renderPage();
|
||||
expect(screen.getAllByText("Unavailable").length).toBeGreaterThanOrEqual(4);
|
||||
expect(screen.getAllByText("Mailboxes unavailable").length).toBeGreaterThan(0);
|
||||
expect(screen.queryByText("No email accounts yet")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("0 mailboxes")).not.toBeInTheDocument();
|
||||
cleanup();
|
||||
|
||||
statusState.emailsError = false;
|
||||
statusState.emailsIncomplete = true;
|
||||
statusState.data = rows.map(measuredStatus);
|
||||
await renderPage();
|
||||
expect(screen.getAllByText("Unavailable").length).toBeGreaterThanOrEqual(4);
|
||||
});
|
||||
|
||||
it("does not show Idle or Healthy while Cloud and account status are loading, then shows the real error", async () => {
|
||||
statusState.cloudLoading = true;
|
||||
statusState.data = rows.map(measuredStatus);
|
||||
|
||||
@@ -206,7 +206,8 @@ export default function AddressesPage() {
|
||||
const row = cloud.rowFor(box.id);
|
||||
return row?.enrolled && !row.cloud?.health;
|
||||
});
|
||||
const statusUnavailable = cloud.unavailable || cloudStandingUnavailable || statuses.isError || (!statusChecking && visibleEmailIds.some((id) => !statusById.has(id)));
|
||||
const inventoryUnavailable = !emailsData.isLoading && (emailsData.isError || emailsData.isIncomplete);
|
||||
const statusUnavailable = inventoryUnavailable || cloud.unavailable || cloudStandingUnavailable || statuses.isError || (!statusChecking && visibleEmailIds.some((id) => !statusById.has(id)));
|
||||
const [lastReportedIssues, setLastReportedIssues] = React.useState<Map<string, { status: AccountStatus; observedAt: number }>>(new Map());
|
||||
useEffect(() => {
|
||||
setLastReportedIssues((previous) => {
|
||||
@@ -443,7 +444,9 @@ export default function AddressesPage() {
|
||||
subtitle={
|
||||
emailsData.isPending
|
||||
? "Loading…"
|
||||
: `${stats.total.toLocaleString()}${emailsData.isLoadingRest || emailsData.isIncomplete ? "+" : ""} mailbox${stats.total === 1 ? "" : "es"}`
|
||||
: emailsData.isError && emailsData.emails.length === 0
|
||||
? "Mailboxes unavailable"
|
||||
: `${stats.total.toLocaleString()}${emailsData.isLoadingRest || emailsData.isIncomplete ? "+" : ""} mailbox${stats.total === 1 ? "" : "es"}`
|
||||
}
|
||||
>
|
||||
<MailboxImportsMenu />
|
||||
@@ -459,7 +462,7 @@ export default function AddressesPage() {
|
||||
</PageTopbar>
|
||||
|
||||
<StatStrip cols={4}>
|
||||
<Stat label="Total" value={emailsData.isLoading ? "Checking…" : <AnimatedNumber value={stats.total} />} sub="connected" />
|
||||
<Stat label="Total" value={emailsData.isLoading ? "Checking…" : inventoryUnavailable ? "Unavailable" : <AnimatedNumber value={stats.total} />} sub="connected" />
|
||||
<Stat label="Healthy" value={statusChecking ? "Checking…" : statusUnavailable ? "Unavailable" : <AnimatedNumber value={stats.healthy} />} sub="no reported health issues" accent={!statusChecking && !statusUnavailable && stats.healthy > 0} />
|
||||
<Stat label="Warming" value={statusChecking ? "Checking…" : statusUnavailable ? "Unavailable" : <AnimatedNumber value={stats.warming} />} sub="ramping up" />
|
||||
<Stat label="Needs attention" value={statusChecking ? "Checking…" : statusUnavailable ? "Unavailable" : <AnimatedNumber value={stats.issues} />} sub="paused or failing" last />
|
||||
@@ -540,6 +543,16 @@ export default function AddressesPage() {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : emailsData.isError && emailsData.emails.length === 0 ? (
|
||||
<EmptyBlock
|
||||
title="Mailboxes unavailable"
|
||||
body="The mailbox list could not be loaded, so no counts are shown."
|
||||
cta={
|
||||
<TopbarAction onClick={() => void emailsData.refetch()}>
|
||||
Try again
|
||||
</TopbarAction>
|
||||
}
|
||||
/>
|
||||
) : !emailsData.emails || emailsData.emails.length === 0 ? (
|
||||
cloud.selfHosted || authConfigLoading ? (
|
||||
<EmptyBlock
|
||||
|
||||
Reference in New Issue
Block a user