From f4e728766b266a0119ac3946dbf5c5bcb43c16ea Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 16 Oct 2025 08:32:59 +0000 Subject: [PATCH] feat: Add option to skip empty summary notifications Co-authored-by: tobiasz.gleba24 --- .../src/workflows/schedules/send-summaries.ts | 89 +++++++++++++++++++ .../action-form-content.tsx | 28 ++++++ packages/db/src/schedule-config-schema.ts | 5 ++ 3 files changed, 122 insertions(+) diff --git a/apps/api/src/workflows/schedules/send-summaries.ts b/apps/api/src/workflows/schedules/send-summaries.ts index c449b87..290dc38 100644 --- a/apps/api/src/workflows/schedules/send-summaries.ts +++ b/apps/api/src/workflows/schedules/send-summaries.ts @@ -507,6 +507,95 @@ export class SendSummariesWorkflow extends WorkflowEntrypoint< }, ); + // Step 2.5: Check if we should skip sending due to empty data + const shouldSkip = await step.do("check-if-should-skip", async () => { + const scheduleConfig = initData.scheduleConfig as schema.ScheduleConfigSendSummaries; + const skipEmptyNotifications = scheduleConfig.skipEmptyNotifications ?? true; + + if (!skipEmptyNotifications) { + return false; // Don't skip if the option is disabled + } + + // Check if all summaries are empty (no generalSummary and no items in arrays) + const hasAnyData = generatedSummaries.some((summary) => { + const content = summary.content as any; + + // Check if there's a general summary with content + if (content.generalSummary && content.generalSummary.trim().length > 0) { + return true; + } + + // Check for any array content (userSummaries, repoSummaries, channelSummaries, etc.) + const arrayFields = [ + "userSummaries", + "items", + "repoSummaries", + "projectSummaries", + "channelSummaries", + "teamSummaries", + ]; + + for (const field of arrayFields) { + if (Array.isArray(content[field]) && content[field].length > 0) { + return true; + } + } + + return false; + }); + + return !hasAnyData; // Skip if there's no data + }); + + // If we should skip, mark as completed and exit early + if (shouldSkip) { + await step.do("finalize-skipped-execution", async () => { + await db + .update(schema.scheduleRun) + .set({ + status: "completed", + executionCount: initData.scheduleRunExecutionCount + 1, + executionMetadata: { + skipped: true, + reason: "No updates or activity found", + completedAt: new Date().toISOString(), + }, + updatedAt: new Date(), + }) + .where(eq(schema.scheduleRun.id, scheduleRunId)); + + // Schedule next execution if schedule is still active + if (initData.scheduleIsActive) { + const scheduleForNextExecution = { + id: initData.scheduleId, + config: initData.scheduleConfig, + name: initData.scheduleName, + isActive: initData.scheduleIsActive, + }; + + const nextExecutionTime = calculateNextScheduleExecution(scheduleForNextExecution as any); + + if (nextExecutionTime) { + await db.insert(schema.scheduleRun).values({ + id: generateId(), + scheduleId: initData.scheduleId, + createdByMemberId: initData.scheduleRunCreatedByMemberId, + status: "pending", + nextExecutionAt: nextExecutionTime, + executionCount: 0, + createdAt: new Date(), + updatedAt: new Date(), + }); + + console.log(`✅ Scheduled next execution for ${nextExecutionTime.toISOString()}`); + } + } + + console.log("⏭️ Skipped sending summaries: No updates or activity found"); + }); + return; // Exit the workflow early + } + // Step 3: Resolve delivery targets const deliveryTargets = await step.do("resolve-delivery-targets", async () => { const config = initData.scheduleConfig as schema.ScheduleConfigSendSummaries; diff --git a/apps/web-app/src/components/update-schedule-form/action-form-content.tsx b/apps/web-app/src/components/update-schedule-form/action-form-content.tsx index 9808cc6..067259a 100644 --- a/apps/web-app/src/components/update-schedule-form/action-form-content.tsx +++ b/apps/web-app/src/components/update-schedule-form/action-form-content.tsx @@ -6,6 +6,8 @@ import type { updateScheduleContract, } from "@asyncstatus/api/typed-handlers/schedule"; import { Button } from "@asyncstatus/ui/components/button"; +import { Checkbox } from "@asyncstatus/ui/components/checkbox"; +import { Label } from "@asyncstatus/ui/components/label"; import { Select, SelectContent, @@ -178,6 +180,28 @@ export function ActionFormContent(props: ActionFormContentProps) { )} + {configName === "sendSummaries" && ( + ( +
+ + +
+ )} + /> + )} + {!isAddingGenerateFor && ( ; @@ -574,6 +575,10 @@ export const ScheduleConfigSendSummariesV3 = z3 .array(ScheduleConfigDeliveryMethodV3) .default([]) .describe("Where summaries should be delivered (email, Slack, Discord, etc.)"), + skipEmptyNotifications: z3 + .boolean() + .default(true) + .describe("Skip sending notifications if no data/updates are found"), }) .strict() .describe("Configuration for sending team summaries");