From 0550bc7eddff22c1c994ed8b90be0d8f5f53564a Mon Sep 17 00:00:00 2001 From: MaikoCode <71674307+MaikoCode@users.noreply.github.com> Date: Fri, 8 May 2026 23:16:04 +0100 Subject: [PATCH 1/2] chore: disable dependabot updates --- .github/dependabot.yml | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 9a19f45..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,13 +0,0 @@ -version: 2 -updates: - - package-ecosystem: npm - directory: / - schedule: - interval: weekly - open-pull-requests-limit: 10 - - - package-ecosystem: github-actions - directory: / - schedule: - interval: weekly - open-pull-requests-limit: 5 From b674b8917a0715a031795e31fb345a4fd0d2dee6 Mon Sep 17 00:00:00 2001 From: MaikoCode <71674307+MaikoCode@users.noreply.github.com> Date: Mon, 11 May 2026 13:33:09 +0100 Subject: [PATCH 2/2] Keep failure logs visible --- convex/dashboard.ts | 51 +++++++++++++++++++++++++++++++++++++---- tests/dashboard.test.ts | 50 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 5 deletions(-) diff --git a/convex/dashboard.ts b/convex/dashboard.ts index f0cc4e4..a17dc41 100644 --- a/convex/dashboard.ts +++ b/convex/dashboard.ts @@ -11,15 +11,23 @@ import { import { parseMetaApiError } from "./meta/authShared"; import { getDeliveryKind } from "./meta/deliveryPolicy"; -const DELIVERY_ISSUE_STATUSES = new Set["status"]>([ +const DELIVERY_ISSUE_STATUSES = [ "failed", "blocked_auth", "skipped", "skipped_expired", -]); +] as const satisfies readonly Doc<"deliveryAttempts">["status"][]; + +const DELIVERY_ISSUE_STATUS_SET = new Set["status"]>( + DELIVERY_ISSUE_STATUSES, +); type DashboardLogStatus = Doc<"deliveryAttempts">["status"] | "received"; +function isDeliveryIssueStatus(status: DashboardLogStatus) { + return status !== "received" && DELIVERY_ISSUE_STATUS_SET.has(status); +} + async function summarizeByInstagramAccount< T extends { instagramAccountId: Doc<"instagramAccounts">["_id"] }, >(items: AsyncIterable) { @@ -235,7 +243,7 @@ export const getOverview = query({ .eq("instagramAccountId", selectedAccountId) .gte("eventTime", failureThreshold), ), - (delivery) => DELIVERY_ISSUE_STATUSES.has(delivery.status), + (delivery) => DELIVERY_ISSUE_STATUS_SET.has(delivery.status), ), ]); @@ -436,6 +444,28 @@ export const listLogs = query({ ) .order("desc") .take(50); + const issueDeliveriesByStatus = await Promise.all( + DELIVERY_ISSUE_STATUSES.map((status) => + ctx.db + .query("deliveryAttempts") + .withIndex("by_instagram_account_id_and_status_and_event_time", (q) => + q.eq("instagramAccountId", args.accountId).eq("status", status), + ) + .order("desc") + .take(50), + ), + ); + const deliveryIds = new Set(deliveries.map((delivery) => delivery._id)); + const issueDeliveries = issueDeliveriesByStatus + .flat() + .filter((delivery) => { + if (deliveryIds.has(delivery._id)) { + return false; + } + deliveryIds.add(delivery._id); + return true; + }); + const deliveriesForLogs = [...deliveries, ...issueDeliveries]; const webhooks = await ctx.db .query("webhookEvents") .withIndex("by_instagram_account_id_and_received_at", (q) => @@ -474,7 +504,7 @@ export const listLogs = query({ } | null; rawPayload: string | null; }> = []; - for (const delivery of deliveries) { + for (const delivery of deliveriesForLogs) { const contact = await ctx.db.get(delivery.contactId); const rule = delivery.automationRuleId ? await ctx.db.get(delivery.automationRuleId) @@ -579,7 +609,18 @@ export const listLogs = query({ } entries.sort((a, b) => b.time - a.time); - return entries.slice(0, 100); + const recentEntries = entries.slice(0, 100); + const recentEntryIds = new Set(recentEntries.map((entry) => entry.id)); + const preservedIssueEntries = entries + .filter( + (entry) => + isDeliveryIssueStatus(entry.status) && !recentEntryIds.has(entry.id), + ) + .slice(0, 50); + + return [...recentEntries, ...preservedIssueEntries].sort( + (a, b) => b.time - a.time, + ); }, }); diff --git a/tests/dashboard.test.ts b/tests/dashboard.test.ts index 6de8204..de584fe 100644 --- a/tests/dashboard.test.ts +++ b/tests/dashboard.test.ts @@ -528,4 +528,54 @@ describe("dashboard", () => { expect(logs[0]?.details).toContain("permission"); expect(logs[0]?.rawPayload).toContain('"error_subcode":2534015'); }); + + it("keeps issue delivery attempts visible when newer deliveries fill the recent log window", async () => { + const t = convexTest({ schema, modules }); + const fixture = await seedWorkspace(t); + const authT = t.withIdentity({ subject: fixture.userId }); + + const connected = await connectAccount(t, fixture, { + state: "dashboard-log-window", + externalId: "ig_dashboard_log_window", + username: "account_log_window", + }); + const thread = await seedThread(t, { + workspaceId: fixture.workspaceId, + instagramAccountId: connected.accountId, + instagramAccountExternalId: "ig_dashboard_log_window", + suffix: "window", + }); + + await seedDeliveryAttempt(t, { + workspaceId: fixture.workspaceId, + instagramAccountId: connected.accountId, + contactId: thread.contactId, + conversationId: thread.conversationId, + status: "failed", + messageText: "This older failure should stay inspectable", + reason: "Meta rejected the message.", + eventTime: BASE_TIME, + }); + + for (let index = 1; index <= 55; index += 1) { + await seedDeliveryAttempt(t, { + workspaceId: fixture.workspaceId, + instagramAccountId: connected.accountId, + contactId: thread.contactId, + conversationId: thread.conversationId, + status: "sent", + messageText: `Successful reply ${index}`, + eventTime: BASE_TIME + index, + }); + } + + const logs = await authT.query(api.dashboard.listLogs, { + accountId: connected.accountId, + }); + + expect(logs.some((log) => log.status === "failed")).toBe(true); + expect(logs.find((log) => log.status === "failed")?.details).toContain( + "Meta rejected the message.", + ); + }); });