* fix: remove IS_PRODUCTION gate from BookingAuditProducer Remove the IS_PRODUCTION check that was preventing booking audits from being queued in production. Audits are still properly gated by: 1. Organization check: Audits are skipped for non-organization bookings (organizationId === null) 2. Feature flag: The BookingAuditTaskConsumer checks if the 'booking-audit' feature is enabled for the organization via featuresRepository.checkIfTeamHasFeature() The IS_PRODUCTION gate was intentionally added to prevent logs from being created in production while the action data versioning was being actively reviewed and finalized. Without proper versioning handling, the Booking History UI could crash when encountering unversioned data. Now that the versioning system is in place, this gate can be safely removed. Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: revert formatting, keep only IS_PRODUCTION removal Reverts the unintended formatting changes from the previous commit. Only removes the IS_PRODUCTION gate without changing indentation. Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * feat: add booking-audit feature flag check in producer to avoid unnecessary task creation - Query booking-audit and booking-email-sms-tasker flags in parallel before fireBookingEvents - Pass isBookingAuditEnabled through BookingEventHandler to producer's queueTask method - Add conditional check in queueTask with debug log when skipping audit - Reuse pre-queried isBookingEmailSmsTaskerEnabled flag instead of querying again Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * feat: make isBookingAuditEnabled required and pass it through all booking audit flows - Make isBookingAuditEnabled a required property in BookingAuditProducerService interface - Update all BookingEventHandler methods to require isBookingAuditEnabled - Add feature flag check in all flows that call booking audit: - handleSeats (seat booking/rescheduling) - RecurringBookingService (bulk bookings) - handleCancelBooking (booking cancellation) - handleConfirmation (booking acceptance) - roundRobinReassignment (automatic reassignment) - roundRobinManualReassignment (manual reassignment) - trpc handlers: addGuests, confirm, editLocation, requestReschedule - Skip queueing audit tasks when feature is disabled with debug logging Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: add featuresRepository dependency to RecurringBookingService DI module Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * refactor: make isBookingAuditEnabled optional for non-main flows Keep feature flag check only in main flows (handleSeats, RegularBookingService, RecurringBookingService) which are frequently triggered. For other flows (handleCancelBooking, handleConfirmation, roundRobinReassignment, etc.), rely on the existing consumer-level check. Changes: - Revert feature flag check from non-main flows - Make isBookingAuditEnabled optional in interface for non-main flow methods - Keep isBookingAuditEnabled required for main flow methods (queueCreatedAudit, queueRescheduledAudit, queueSeatBookedAudit, queueSeatRescheduledAudit, queueBulkCreatedAudit, queueBulkRescheduledAudit) - Update BookingEventHandlerService to use required params for main flows and optional for non-main flows Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * refactor: remove isBookingAuditEnabled from non-main flow methods Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * feat: make isBookingAuditEnabled required in all BookingEventHandlerService methods - Add isBookingAuditEnabled as required parameter in all BookingAuditProducerService interface methods - Update BookingAuditTaskerProducerService to use simplified check (!params.isBookingAuditEnabled) - Update BookingEventHandlerService to require isBookingAuditEnabled in all methods - Update all callers to query booking-audit feature flag and pass isBookingAuditEnabled: - handleCancelBooking - handleConfirmation - roundRobinReassignment - roundRobinManualReassignment - addGuests.handler - confirm.handler - editLocation.handler - requestReschedule.handler - Inject featuresRepository in API V2's booking-location.service.ts Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * test: update roundRobinReassignment tests to include isBookingAuditEnabled Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * test: update roundRobinManualReassignment tests to include isBookingAuditEnabled Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: inject featuresRepository in API V2 RecurringBookingService Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: import PrismaWorkerModule for PrismaFeaturesRepository dependency Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: use booking's organization for feature flag check in addGuests handler Use booking.user?.profiles?.[0]?.organizationId instead of user.organizationId to check the booking-audit feature flag. This ensures the feature flag is checked against the booking's organization rather than the actor's organization, which is consistent with other handlers in this PR. Addresses Cubic AI review feedback (confidence 9/10). Co-Authored-By: unknown <> * Add comment * fix: use user.organizationId for feature flag check in addGuests handler Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: use booking's organizationId for feature flag check in addGuests handler Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: revert to user.organizationId for feature flag check in addGuests handler Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: add isBookingAuditEnabled to onNoShowUpdated calls after main merge Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: add isBookingAuditEnabled to onNoShowUpdated calls and update tests Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Tasker
Tasker: "One who performs a task, as a day-laborer."
Task: "A function to be performed; an objective."
What is it?
Introduces a new pattern called Tasker which may be switched out in the future for other third party services.
Also introduces a base InternalTasker which doesn't require third party dependencies and should work out of the box (by configuring a proper cron).
Why is this needed?
The Tasker pattern is needed to streamline the execution of non-critical tasks in an application, providing a structured approach to task scheduling, execution, retrying, and cancellation. Here's why it's necessary:
-
Offloading non-critical tasks: There are tasks that don't need to be executed immediately on the main thread, such as sending emails, generating reports, or performing periodic maintenance tasks. Offloading these tasks to a separate queue or thread improves the responsiveness and efficiency of the main application.
-
Retry mechanism: Not all tasks succeed on the first attempt due to errors or external dependencies. This pattern incorporates a retry mechanism, which allows failed tasks to be retried automatically for a specified number of attempts. This improves the robustness of the system by handling temporary failures gracefully.
-
Scheduled task execution: Some tasks need to be executed at a specific time or after a certain delay. The Tasker pattern facilitates scheduling tasks for future execution, ensuring they are performed at the designated time without manual intervention.
-
Task cancellation: Occasionally, it's necessary to cancel a scheduled task due to changing requirements or user actions. The Tasker pattern supports task cancellation, enabling previously scheduled tasks to be revoked or removed from the queue before execution.
-
Flexible implementation: The Tasker pattern allows for flexibility in implementation by providing a base structure (
InternalTasker) that can be extended or replaced with third-party services (TriggerDevTasker,AwsSqsTasker, etc.). This modularity ensures that the task execution mechanism can be adapted to suit different application requirements or environments.
Overall, the Tasker pattern enhances the reliability, performance, and maintainability by managing non-critical tasks in a systematic and efficient manner. It abstracts away the complexities of task execution, allowing developers to focus on core application logic while ensuring timely and reliable execution of background tasks.
How does it work?
Since the Tasker is a pattern on itself, it will depend on the actual implementation. For example, a TriggerDevTasker will work very differently from an AwsSqsTasker.
For simplicity sake will explain how the InternalTasker works:
-
Instead of running a non-critical task you schedule using the tasker:
const examplePayload = { example: "payload" }; - await sendWebhook(examplePayload); + await tasker.create("sendWebhook", JSON.stringify(examplePayload)); -
This will create a new task to be run on the next processing of the task queue.
-
Then on the next cron run it will be picked up and executed:
// /app/api/tasks/cron/route.ts import { TaskProcessor } from "@calcom/features/tasker/task-processor"; export async function GET() { // authenticate the call... const processor = new TaskProcessor(); await processor.processQueue(); return Response.json({ success: true }); } -
By default, the cron will run each minute and will pick the next 100 tasks to be executed.
-
If the tasks succeeds, it will be marked as
suceededAt: new Date(). If if fails, theattemptsprop will increase by 1 and will be retried on the next cron run. -
If
attemptsreachesmaxAttemps, it will be considered a failed and won't be retried again. -
By default, tasks will be attempted up to 3 times. This can be overridden when creating a task.
-
From here we can either keep a record of executed tasks, or we can setup another cron to cleanup all successful and failed tasks:
// /app/api/tasks/cleanup/route.ts import { TaskProcessor } from "@calcom/features/tasker/task-processor"; export async function GET() { // authenticate the call... const processor = new TaskProcessor(); await processor.cleanup(); return Response.json({ success: true }); } -
This will delete all failed and successful tasks.
-
A task is just a simple function receives a payload:
type TaskHandler = (payload: string) => Promise<void>;
How to contribute?
You can contribute by either expanding the InternalTasker or creating new Taskers. To see how to add new Taskers, see the tasker-factory.ts file.
You can also take some inspiration by looking into previous attempts to add various Message Queue pull requests: