Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions .github/dependabot.yml

This file was deleted.

51 changes: 46 additions & 5 deletions convex/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,23 @@ import {
import { parseMetaApiError } from "./meta/authShared";
import { getDeliveryKind } from "./meta/deliveryPolicy";

const DELIVERY_ISSUE_STATUSES = new Set<Doc<"deliveryAttempts">["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<Doc<"deliveryAttempts">["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<T>) {
Expand Down Expand Up @@ -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),
),
]);

Expand Down Expand Up @@ -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) =>
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
);
},
});

Expand Down
50 changes: 50 additions & 0 deletions tests/dashboard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
);
});
});
Loading