Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
162 commits
Select commit Hold shift + click to select a range
b0d048a
feat: add 5 new workflow triggers for booking events
devin-ai-integration[bot] Aug 13, 2025
a327a38
fix: type check, remove as any
Amit91848 Aug 13, 2025
a2fef5f
feat: add workflow trigger for BOOKING_REQUESTED in handleNewBooking.ts
devin-ai-integration[bot] Aug 13, 2025
1a86cb0
Merge branch 'devin/1755107037-add-workflow-triggers' of https://git-…
devin-ai-integration[bot] Aug 13, 2025
b161f8f
fix: resolve type errors in workflow trigger implementations
devin-ai-integration[bot] Aug 13, 2025
3f4f743
feat: add workflow test configurations for new booking triggers
devin-ai-integration[bot] Aug 13, 2025
a4e3015
fix: add missing mockSuccessfulVideoMeetingCreation import to confirm…
devin-ai-integration[bot] Aug 13, 2025
dd791ff
add new triggers
Aug 14, 2025
94ebb23
Merge branch 'main' into devin/1755107037-add-workflow-triggers
Amit91848 Aug 18, 2025
1062d1a
refactor: improve _scheduleWorkflowReminders readability and add miss…
devin-ai-integration[bot] Aug 18, 2025
9025eaa
Merge branch 'devin/1755107037-add-workflow-triggers' of https://git-…
devin-ai-integration[bot] Aug 18, 2025
190eb0b
only show customt emplate for form triggers
Aug 18, 2025
3cd2d7e
filter outside scheduleWorkflowReminder
Amit91848 Aug 18, 2025
a39ccf4
fix type check
Amit91848 Aug 18, 2025
265f852
chore: add more tests
Amit91848 Aug 18, 2025
7642992
test: add comprehensive unit tests for handleMarkNoShow with webhook …
devin-ai-integration[bot] Aug 18, 2025
53fe81e
Revert "test: add comprehensive unit tests for handleMarkNoShow with …
Amit91848 Aug 19, 2025
32910d8
fix: add new workflow triggers to api/v2
Amit91848 Aug 19, 2025
e037154
update swagger docs
Amit91848 Aug 19, 2025
2a187ea
fix: e2e
Amit91848 Aug 19, 2025
d0e0fee
fix type check
Amit91848 Aug 19, 2025
505ff8e
fix tests, add test for before after events
Amit91848 Aug 19, 2025
37c78b0
fix unit tests
Amit91848 Aug 19, 2025
b945b88
Merge branch 'main' into devin/1755107037-add-workflow-triggers
Amit91848 Aug 19, 2025
ac54670
revert confirm.handler.test
Amit91848 Aug 19, 2025
32a26e5
Merge branch 'devin/1755107037-add-workflow-triggers' of https://gith…
Amit91848 Aug 19, 2025
0f0e2fa
fix: unit tests
Amit91848 Aug 19, 2025
75d1235
dummy form variables
Aug 20, 2025
edcbca5
add routing forms to active on dropdown
Aug 20, 2025
2feb0ea
add migration file
Aug 20, 2025
1278b63
Ui fixes for variables dropdown
Aug 20, 2025
7825c4a
remove other translation keys
Aug 20, 2025
2168142
Merge branch 'fix/add-variables-dropdown' into feat/routing-form-work…
Aug 20, 2025
8d58550
review fixes
Amit91848 Aug 20, 2025
2ea8c1e
allow routing forms for activeOn
Aug 21, 2025
61d231a
use repository function to get routing forms
Aug 21, 2025
985b777
remove unnecessary code
Aug 21, 2025
c9ae4f5
adjust logic in update handler
Aug 22, 2025
6ff24a9
add triggers to api v2
Aug 22, 2025
99e2ac7
remvoe unused file
Aug 22, 2025
87f8857
Merge branch 'main' of https://github.com/calcom/cal.com into devin/1…
Amit91848 Aug 26, 2025
d27e45f
rename to getAcitveOnOptions handler
Aug 26, 2025
59a4c9c
remove routingFormOptions handler
Aug 26, 2025
fdc18cb
clean up getActiveOnOptions
Aug 26, 2025
a9ab403
refactor WorkflowService
Amit91848 Aug 26, 2025
4874a6f
remove logs
Amit91848 Aug 26, 2025
24dd93f
remove unused
Amit91848 Aug 26, 2025
4429ced
Merge branch 'devin/1755107037-add-workflow-triggers' of https://gith…
Amit91848 Aug 26, 2025
3b5e530
fix: type check
Amit91848 Aug 26, 2025
c13dcbe
fix: missed before after events for recurring
Amit91848 Aug 26, 2025
e2768cc
fix: calendarEvent handleMarkNoShow
Amit91848 Aug 26, 2025
b957355
fix error message
Amit91848 Aug 26, 2025
3672d04
Merge branch 'main' into feat/routing-form-workflow-triggers
Aug 26, 2025
57a7d9b
don't query disabled routing forms
Aug 26, 2025
72422b0
merge devin/1755107037-add-workflow-triggers
Aug 27, 2025
a659afc
create tasker function
Aug 27, 2025
a35e1ec
add tasker code
Aug 28, 2025
1b9962f
move isFormTrigger function
Aug 28, 2025
6be759d
Merge branch 'main' into devin/1755107037-add-workflow-triggers
CarinaWolli Aug 28, 2025
4fdd9b6
Merge branch 'main' into feat/routing-form-workflow-triggers
Aug 28, 2025
9c6b365
small adjustments + todo comments
Aug 28, 2025
078df04
remove email to host action for form triggers
Aug 28, 2025
97c5572
throw trpc error if email to host is added as step
Aug 28, 2025
805f8be
Merge branch 'main' of https://github.com/calcom/cal.com into devin/1…
Amit91848 Aug 29, 2025
1ab36bd
fix dialog on how to use form responses as variables
Aug 29, 2025
ab1a14c
remove add variable dropdown for form triggers
Aug 29, 2025
200f3fa
remove form workfows in event workflows tab
Aug 29, 2025
239f3db
improvements for workflow logic on form submission
Aug 29, 2025
952cf34
review fixes
Amit91848 Aug 29, 2025
b57f4a4
base setup for seperate schedule functions (evt and form)
Aug 29, 2025
026762f
add missing BOOKING_PAID workflow trigger
Amit91848 Sep 1, 2025
737b643
fix pathname
Amit91848 Sep 1, 2025
19a9aaf
fix: test for BOOKING_REQUESTED
Amit91848 Sep 1, 2025
50a994c
fix activeOn ids
Sep 1, 2025
024c888
pass hideBranding and smsReminderNumber
Sep 1, 2025
42fe72c
adjustments to reminderScheduler
Sep 1, 2025
a702b71
create empty scheduelForForm functions
Sep 1, 2025
0dc6b08
pass locale and timezone with form user
Sep 1, 2025
672c1d7
pass formData instead of responses
Sep 1, 2025
bc4e630
pass timeFormat and locale
Sep 1, 2025
d7b0117
reusable function for email sending and reminder creation
Sep 2, 2025
e14adee
implement scheduleEmailReminderForForm
Sep 2, 2025
4d6356c
remove added editor field from merge conflict
Sep 2, 2025
1c03eed
don't support cal.ai action with form triggers
Sep 2, 2025
1603f44
throw bad request if form trigger and cal.ai is combined
Sep 2, 2025
8362a7f
add tests for scheduleFormWorkflows
Sep 2, 2025
c8ccd40
add form submission tests
Sep 2, 2025
f82dfd6
remove form response varibe info
Sep 3, 2025
3494c61
clean up workflow actions
Sep 3, 2025
557f0f4
fixes for getting template options
Sep 3, 2025
6b4aaf7
pass triggerType to getAllWorkflows
Sep 3, 2025
23751d0
move reusable logic to scheduleSMSReminder
Sep 3, 2025
e1a234f
add formdata to param type
Sep 3, 2025
a5c3be2
type fixes for text reminder managers
Sep 3, 2025
a69f86d
implement scheduleSMSReminderForForm
Sep 3, 2025
4e48e9c
fix import
Sep 3, 2025
4c65847
fix isAuthorizedToAddActiveOnIds
Sep 3, 2025
82e7d41
disble whatsapp action
Sep 3, 2025
cfe2068
implement triggerFormSubmittedNoEventWorkflow
Sep 3, 2025
6ba946b
code clean up
Sep 3, 2025
4c557e7
Merge branch 'devin/1755107037-add-workflow-triggers' into feat/routi…
Sep 3, 2025
fe627db
Merge branch 'main' into feat/routing-form-workflow-triggers
Sep 3, 2025
af86d6b
fix type errors
Sep 3, 2025
2931206
remove async from getSubmitterEmail
Sep 4, 2025
7e0e572
fix type errors
Sep 4, 2025
1d3f76f
Merge branch 'main' into feat/routing-form-workflow-triggers
Sep 4, 2025
0e38ce9
revert cal.ai changes
Sep 4, 2025
7451a79
fix type error
Sep 4, 2025
9303773
add sublogger
Sep 4, 2025
eac7306
Merge branch 'main' into feat/routing-form-workflow-triggers
Sep 4, 2025
91253e3
code clean up
Sep 4, 2025
43a788c
Merge branch 'main' into feat/routing-form-workflow-triggers
Sep 4, 2025
32de973
Merge branch 'main' into feat/routing-form-workflow-triggers
Sep 4, 2025
ddd343e
fix type errors
Sep 4, 2025
6b8e602
remove label for attendee whatsapp action
Sep 4, 2025
d20accd
merge main into feat/routing-form-workflow-triggers
Sep 4, 2025
3554cdd
code clean up
Sep 5, 2025
07a15f2
fixes saving teams on org workflows
Sep 5, 2025
25ff5a7
merge main
Sep 5, 2025
6801f3f
fix type error
Sep 5, 2025
0a3590a
code improvements for activeOn ids
Sep 5, 2025
b294e22
Revert "code improvements for activeOn ids"
Sep 5, 2025
520f3bc
improve variable name
Sep 5, 2025
2e115ea
fix unit tests
Sep 5, 2025
380f9fa
small fixes
Sep 5, 2025
d9ed5b3
type fixes
Sep 5, 2025
b159476
remove unused translation keys
Sep 7, 2025
b5b6878
Merge branch 'main' into feat/routing-form-workflow-triggers
Sep 8, 2025
6a90b6a
fix merge conflict issues
Sep 8, 2025
01ead92
code clean up
Sep 8, 2025
6eac654
remove SMS action support
Sep 8, 2025
2cc85dc
remove more SMS code
Sep 8, 2025
d551320
add missing imports
Sep 8, 2025
eadb532
set custom template for form action
Sep 8, 2025
d0ca589
type fixes
Sep 8, 2025
594e162
fix tasker endpoint
Sep 8, 2025
069ae98
fix duplicate check
Sep 8, 2025
fec95f3
merge main
Sep 8, 2025
8766ae4
fix workfows.test.ts
Sep 8, 2025
78ad4b1
use repository funciton to getHideBranding
Sep 8, 2025
46e0c23
code clean up
Sep 8, 2025
25e05c0
fix hasDuplicateSubmission
Sep 8, 2025
ec9795e
code clean up
Sep 8, 2025
c6a7919
select only needed properties
Sep 8, 2025
7c77d9a
Merge branch 'main' into feat/routing-form-workflow-triggers
CarinaWolli Sep 8, 2025
7aa47b1
remove repository functions
Sep 8, 2025
46da699
Revert "remove repository functions"
Sep 8, 2025
fe5db4f
add scheduleWorkflows function
Sep 8, 2025
9ab0a5a
Revert "add scheduleWorkflows function"
Sep 8, 2025
91e0152
move type to /types
Sep 8, 2025
3b1533e
Revert "move type to /types"
Sep 8, 2025
f43e786
revert changes causing type errors
Sep 8, 2025
af9fab6
remove import
Sep 8, 2025
1916768
remove unused import
Sep 8, 2025
d437af5
Revert "remove unused import"
Sep 8, 2025
763023f
revert changed from attempt to fix type errors
Sep 8, 2025
a120784
pass object to gt all workflows
Sep 8, 2025
894dbb4
fix isAuthorized check
Sep 8, 2025
51db2d0
trigger filtering
Sep 8, 2025
a3d4349
merge main
Sep 10, 2025
146cffc
Merge branch 'devin/1755107037-add-workflow-triggers' into feat/routi…
Sep 25, 2025
f4da96d
ForEvt and ForForm function for aiPhoneCallManager
Sep 2, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,17 @@ import {
BOOKING_REJECTED,
BOOKING_REQUESTED,
EVENT_CANCELLED,
FORM_SUBMITTED,
FORM_SUBMITTED_NO_EVENT,
NEW_EVENT,
OnAfterCalVideoGuestsNoShowTriggerDto,
OnAfterCalVideoHostsNoShowTriggerDto,
OnAfterEventTriggerDto,
OnBeforeEventTriggerDto,
OnCancelTriggerDto,
OnCreationTriggerDto,
OnFormSubmittedNoEventTriggerDto,
OnFormSubmittedTriggerDto,
OnNoShowUpdateTriggerDto,
OnPaidTriggerDto,
OnPaymentInitiatedTriggerDto,
Expand Down Expand Up @@ -83,6 +87,8 @@ export type TriggerDtoType =
| OnRescheduleTriggerDto
| OnCancelTriggerDto
| OnAfterCalVideoGuestsNoShowTriggerDto
| OnFormSubmittedTriggerDto
| OnFormSubmittedNoEventTriggerDto
| OnRejectedTriggerDto
| OnRequestedTriggerDto
| OnPaymentInitiatedTriggerDto
Expand All @@ -93,6 +99,8 @@ export type TriggerDtoType =
@ApiExtraModels(
OnBeforeEventTriggerDto,
OnAfterEventTriggerDto,
OnFormSubmittedTriggerDto,
OnFormSubmittedNoEventTriggerDto,
OnCancelTriggerDto,
OnCreationTriggerDto,
OnRescheduleTriggerDto,
Expand Down Expand Up @@ -132,6 +140,8 @@ export class CreateWorkflowDto {
{ $ref: getSchemaPath(OnRescheduleTriggerDto) },
{ $ref: getSchemaPath(OnAfterCalVideoGuestsNoShowTriggerDto) },
{ $ref: getSchemaPath(OnAfterCalVideoHostsNoShowTriggerDto) },
{ $ref: getSchemaPath(OnFormSubmittedTriggerDto) },
{ $ref: getSchemaPath(OnFormSubmittedNoEventTriggerDto) },
{ $ref: getSchemaPath(OnRejectedTriggerDto) },
{ $ref: getSchemaPath(OnRequestedTriggerDto) },
{ $ref: getSchemaPath(OnPaidTriggerDto) },
Expand All @@ -151,6 +161,8 @@ export class CreateWorkflowDto {
{ value: OnRescheduleTriggerDto, name: RESCHEDULE_EVENT },
{ value: OnAfterCalVideoGuestsNoShowTriggerDto, name: AFTER_GUESTS_CAL_VIDEO_NO_SHOW },
{ value: OnAfterCalVideoHostsNoShowTriggerDto, name: AFTER_HOSTS_CAL_VIDEO_NO_SHOW },
{ value: OnFormSubmittedTriggerDto, name: FORM_SUBMITTED },
{ value: OnFormSubmittedNoEventTriggerDto, name: FORM_SUBMITTED_NO_EVENT },
{ value: OnRequestedTriggerDto, name: BOOKING_REQUESTED },
{ value: OnRejectedTriggerDto, name: BOOKING_REJECTED },
{ value: OnPaymentInitiatedTriggerDto, name: BOOKING_PAYMENT_INITIATED },
Expand All @@ -171,7 +183,8 @@ export class CreateWorkflowDto {
| OnPaymentInitiatedTriggerDto
| OnNoShowUpdateTriggerDto
| OnAfterCalVideoGuestsNoShowTriggerDto
| OnAfterCalVideoHostsNoShowTriggerDto;
| OnFormSubmittedTriggerDto
| OnFormSubmittedNoEventTriggerDto;

@ApiProperty({
description: "Steps to execute as part of the workflow",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ import {
AFTER_GUESTS_CAL_VIDEO_NO_SHOW,
OnAfterCalVideoHostsNoShowTriggerDto,
AFTER_HOSTS_CAL_VIDEO_NO_SHOW,
OnFormSubmittedNoEventTriggerDto,
OnFormSubmittedTriggerDto,
FORM_SUBMITTED_NO_EVENT,
FORM_SUBMITTED,
OnNoShowUpdateTriggerDto,
OnRejectedTriggerDto,
OnRequestedTriggerDto,
Expand Down Expand Up @@ -126,6 +130,8 @@ export class UpdateWhatsAppAttendeePhoneWorkflowStepDto extends WorkflowPhoneWha
@ApiExtraModels(
OnBeforeEventTriggerDto,
OnAfterEventTriggerDto,
OnFormSubmittedTriggerDto,
OnFormSubmittedNoEventTriggerDto,
OnCancelTriggerDto,
OnCreationTriggerDto,
OnRescheduleTriggerDto,
Expand Down Expand Up @@ -170,6 +176,8 @@ export class UpdateWorkflowDto {
{ $ref: getSchemaPath(OnRescheduleTriggerDto) },
{ $ref: getSchemaPath(OnAfterCalVideoGuestsNoShowTriggerDto) },
{ $ref: getSchemaPath(OnAfterCalVideoHostsNoShowTriggerDto) },
{ $ref: getSchemaPath(OnFormSubmittedTriggerDto) },
{ $ref: getSchemaPath(OnFormSubmittedNoEventTriggerDto) },
{ $ref: getSchemaPath(OnRejectedTriggerDto) },
{ $ref: getSchemaPath(OnRequestedTriggerDto) },
{ $ref: getSchemaPath(OnPaidTriggerDto) },
Expand All @@ -190,6 +198,8 @@ export class UpdateWorkflowDto {
{ value: OnRescheduleTriggerDto, name: RESCHEDULE_EVENT },
{ value: OnAfterCalVideoGuestsNoShowTriggerDto, name: AFTER_GUESTS_CAL_VIDEO_NO_SHOW },
{ value: OnAfterCalVideoHostsNoShowTriggerDto, name: AFTER_HOSTS_CAL_VIDEO_NO_SHOW },
{ value: OnFormSubmittedTriggerDto, name: FORM_SUBMITTED },
{ value: OnFormSubmittedNoEventTriggerDto, name: FORM_SUBMITTED_NO_EVENT },
{ value: OnRequestedTriggerDto, name: BOOKING_REQUESTED },
{ value: OnRejectedTriggerDto, name: BOOKING_REJECTED },
{ value: OnPaymentInitiatedTriggerDto, name: BOOKING_PAYMENT_INITIATED },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export const AFTER_EVENT = "afterEvent";
export const RESCHEDULE_EVENT = "rescheduleEvent";
export const AFTER_HOSTS_CAL_VIDEO_NO_SHOW = "afterHostsCalVideoNoShow";
export const AFTER_GUESTS_CAL_VIDEO_NO_SHOW = "afterGuestsCalVideoNoShow";
export const FORM_SUBMITTED = "formSubmitted";
export const FORM_SUBMITTED_NO_EVENT = "formSubmittedNoEvent";
export const BOOKING_REJECTED = "bookingRejected";
export const BOOKING_REQUESTED = "bookingRequested";
export const BOOKING_PAYMENT_INITIATED = "bookingPaymentInitiated";
Expand All @@ -24,6 +26,8 @@ export const WORKFLOW_TRIGGER_TYPES = [
RESCHEDULE_EVENT,
AFTER_HOSTS_CAL_VIDEO_NO_SHOW,
AFTER_GUESTS_CAL_VIDEO_NO_SHOW,
FORM_SUBMITTED,
FORM_SUBMITTED_NO_EVENT,
BOOKING_REJECTED,
BOOKING_REQUESTED,
BOOKING_PAYMENT_INITIATED,
Expand All @@ -39,6 +43,8 @@ export const WORKFLOW_TRIGGER_TO_ENUM = {
[RESCHEDULE_EVENT]: WorkflowTriggerEvents.RESCHEDULE_EVENT,
[AFTER_HOSTS_CAL_VIDEO_NO_SHOW]: WorkflowTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW,
[AFTER_GUESTS_CAL_VIDEO_NO_SHOW]: WorkflowTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW,
[FORM_SUBMITTED]: WorkflowTriggerEvents.FORM_SUBMITTED,
[FORM_SUBMITTED_NO_EVENT]: WorkflowTriggerEvents.FORM_SUBMITTED_NO_EVENT,
[BOOKING_REJECTED]: WorkflowTriggerEvents.BOOKING_REJECTED,
[BOOKING_REQUESTED]: WorkflowTriggerEvents.BOOKING_REQUESTED,
[BOOKING_PAYMENT_INITIATED]: WorkflowTriggerEvents.BOOKING_PAYMENT_INITIATED,
Expand All @@ -54,6 +60,8 @@ export const ENUM_TO_WORKFLOW_TRIGGER = {
[WorkflowTriggerEvents.RESCHEDULE_EVENT]: RESCHEDULE_EVENT,
[WorkflowTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW]: AFTER_HOSTS_CAL_VIDEO_NO_SHOW,
[WorkflowTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW]: AFTER_GUESTS_CAL_VIDEO_NO_SHOW,
[WorkflowTriggerEvents.FORM_SUBMITTED]: FORM_SUBMITTED,
[WorkflowTriggerEvents.FORM_SUBMITTED_NO_EVENT]: FORM_SUBMITTED_NO_EVENT,
[WorkflowTriggerEvents.BOOKING_REJECTED]: BOOKING_REJECTED,
[WorkflowTriggerEvents.BOOKING_REQUESTED]: BOOKING_REQUESTED,
[WorkflowTriggerEvents.BOOKING_PAYMENT_INITIATED]: BOOKING_PAYMENT_INITIATED,
Expand Down Expand Up @@ -213,3 +221,22 @@ export class OnAfterCalVideoHostsNoShowTriggerDto extends TriggerOffsetDTO {
@IsIn([AFTER_HOSTS_CAL_VIDEO_NO_SHOW])
type: typeof AFTER_HOSTS_CAL_VIDEO_NO_SHOW = AFTER_HOSTS_CAL_VIDEO_NO_SHOW;
}
export class OnFormSubmittedTriggerDto {
@ApiProperty({
description: "Trigger type for the workflow",
example: FORM_SUBMITTED,
})
@IsString()
@IsIn([FORM_SUBMITTED])
type: typeof FORM_SUBMITTED = FORM_SUBMITTED;
}

export class OnFormSubmittedNoEventTriggerDto extends TriggerOffsetDTO {
@ApiProperty({
description: "Trigger type for the workflow",
example: FORM_SUBMITTED_NO_EVENT,
})
@IsString()
@IsIn([FORM_SUBMITTED_NO_EVENT])
type: typeof FORM_SUBMITTED_NO_EVENT = FORM_SUBMITTED_NO_EVENT;
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
import {
OnAfterEventTriggerDto,
OnBeforeEventTriggerDto,
OnFormSubmittedNoEventTriggerDto,
TIME_UNIT_TO_ENUM,
WORKFLOW_TRIGGER_TO_ENUM,
} from "../inputs/workflow-trigger.input";
Expand Down Expand Up @@ -136,22 +137,25 @@ export class WorkflowsInputService {
: currentData.trigger;
const timeUnitForZod =
updateDto.trigger instanceof OnBeforeEventTriggerDto ||
updateDto.trigger instanceof OnAfterEventTriggerDto
updateDto.trigger instanceof OnAfterEventTriggerDto ||
updateDto.trigger instanceof OnFormSubmittedNoEventTriggerDto
? updateDto?.trigger?.offset?.unit ?? currentData.timeUnit ?? null
: undefined;

Comment on lines 139 to 144
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Don’t null-out time/timeUnit when trigger isn’t provided

If updateDto.trigger is undefined, current code sets time/timeUnit to null, unintentionally clearing persisted offsets and triggering reschedules.

Apply this diff to preserve existing values unless the trigger changes to a non-offset variant:

-    const timeUnitForZod =
-      updateDto.trigger instanceof OnBeforeEventTriggerDto ||
-      updateDto.trigger instanceof OnAfterEventTriggerDto ||
-      updateDto.trigger instanceof OnFormSubmittedNoEventTriggerDto
-        ? updateDto?.trigger?.offset?.unit ?? currentData.timeUnit ?? null
-        : undefined;
+    const isDtoOffsetBased =
+      updateDto.trigger instanceof OnBeforeEventTriggerDto ||
+      updateDto.trigger instanceof OnAfterEventTriggerDto ||
+      updateDto.trigger instanceof OnFormSubmittedNoEventTriggerDto;
+
+    const timeUnitForZod =
+      updateDto.trigger === undefined
+        ? currentData.timeUnit ?? null
+        : isDtoOffsetBased
+          ? updateDto.trigger.offset?.unit ?? currentData.timeUnit ?? null
+          : null;
@@
-      time:
-        updateDto.trigger instanceof OnBeforeEventTriggerDto ||
-        updateDto.trigger instanceof OnAfterEventTriggerDto ||
-        updateDto.trigger instanceof OnFormSubmittedNoEventTriggerDto
-          ? updateDto?.trigger?.offset?.value ?? currentData?.time ?? null
-          : null,
+      time:
+        updateDto.trigger === undefined
+          ? currentData.time ?? null
+          : isDtoOffsetBased
+            ? updateDto.trigger.offset?.value ?? currentData.time ?? null
+            : null,

Also applies to: 156-160

🤖 Prompt for AI Agents
In apps/api/v2/src/modules/workflows/services/workflows.input.service.ts around
lines 139-144 (and similarly at 156-160), the ternary unconditionally evaluates
to null when updateDto.trigger is undefined, which clears persisted offset
values; change the logic so undefined trigger leaves the existing value
untouched: first check whether updateDto.trigger is strictly undefined and
return undefined (no change); otherwise, if the provided trigger is one of the
offset-bearing DTOs (OnBeforeEventTriggerDto, OnAfterEventTriggerDto,
OnFormSubmittedNoEventTriggerDto) compute the unit/value using the updateDto
trigger or fallback to currentData, and if the trigger is a non-offset variant
explicitly set null to clear offsets when the trigger actually changed to a
non-offset type.

const updateData: TUpdateInputSchema = {
id: workflowIdToUse,
name: updateDto.name ?? currentData.name,
activeOn:
activeOnEventTypeIds:
updateDto?.activation?.activeOnEventTypeIds ??
currentData?.activeOn.map((active) => active.eventTypeId) ??
[],
activeOnRoutingFormIds: updateDto?.activation?.activeOnRoutingFormIds ?? [],
steps: mappedSteps,
trigger: triggerForZod,
time:
updateDto.trigger instanceof OnBeforeEventTriggerDto ||
updateDto.trigger instanceof OnAfterEventTriggerDto
updateDto.trigger instanceof OnAfterEventTriggerDto ||
updateDto.trigger instanceof OnFormSubmittedNoEventTriggerDto
? updateDto?.trigger?.offset?.value ?? currentData?.time ?? null
: null,
timeUnit: timeUnitForZod ? TIME_UNIT_TO_ENUM[timeUnitForZod] : null,
Expand Down
8 changes: 8 additions & 0 deletions apps/web/public/static/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -1432,6 +1432,7 @@
"new_event_trigger": "when new event is booked",
"email_host_action": "send email to host",
"email_attendee_action": "send email to attendees",
"email_attendee_action_form": "Send email to submitted email address",
"sms_attendee_action": "Send SMS to attendee",
"sms_number_action": "send SMS to a specific number",
"send_reminder_sms": "Easily send meeting reminders via SMS to your attendees",
Expand Down Expand Up @@ -1484,6 +1485,8 @@
"may_require_confirmation": "May require confirmation",
"nr_event_type_one": "{{count}} event type",
"nr_event_type_other": "{{count}} event types",
"nr_routing_form_one": "{{count}} routing form",
"nr_routing_form_other": "{{count}} routing forms",
"count_team_one": "{{count}} team",
"count_team_other": "{{count}} teams",
"add_action": "Add action",
Expand Down Expand Up @@ -1600,7 +1603,9 @@
"2fa_required": "Two factor authentication required",
"incorrect_2fa": "Incorrect two factor authentication code",
"which_event_type_apply": "Which event type will this apply to?",
"which_routing_form_apply": "Which routing form will this apply to?",
"apply_to_all_event_types": "Apply to all, including future event types",
"apply_to_all_routing_forms": "Apply to all, including future routing forms",
"apply_to_all_teams": "Apply to all team and user event types",
"which_team_apply": "Which team will this apply to?",
"no_workflows_description": "Workflows enable simple automation to send notifications & reminders enabling you to build processes around your events.",
Expand Down Expand Up @@ -3602,6 +3607,9 @@
"webhook_metadata": "Metadata",
"stats": "Stats",
"booking_status": "Booking status",
"form_submitted_trigger": "When routing form is submitted",
"form_submitted_no_event_trigger": "When routing form is submitted and no booking is created",
"how_long_after_form_submitted_no_event": "How long after the form was submitted?",
"visit": "Visit",
"location_custom_label_input_label": "Custom label on booking page",
"meeting_link": "Meeting link",
Expand Down
129 changes: 126 additions & 3 deletions packages/app-store/routing-forms/lib/formSubmissionUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import { describe, it, vi, expect, beforeEach, afterEach } from "vitest";

import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
import { sendGenericWebhookPayload } from "@calcom/features/webhooks/lib/sendPayload";
import { WebhookTriggerEvents } from "@calcom/prisma/enums";
import { WorkflowService } from "@calcom/lib/server/service/workflows";
import {
WebhookTriggerEvents,
WorkflowTriggerEvents,
WorkflowActions,
WorkflowTemplates,
} from "@calcom/prisma/enums";
import { getAllWorkflowsFromRoutingForm } from "@calcom/trpc/server/routers/viewer/workflows/util";

import { _onFormSubmission } from "./formSubmissionUtils";

Expand All @@ -25,6 +32,16 @@ vi.mock("@calcom/features/tasker", () => {
return { default: Promise.resolve(tasker) };
});

// Mock workflow dependencies
vi.mock("@calcom/lib/server/service/workflows", () => ({
WorkflowService: {
scheduleFormWorkflows: vi.fn(() => Promise.resolve()),
},
}));
vi.mock("@calcom/trpc/server/routers/viewer/workflows/util", () => ({
getAllWorkflowsFromRoutingForm: vi.fn(() => Promise.resolve([])),
}));

const mockSendEmail = vi.fn(() => Promise.resolve());
const mockResponseEmailConstructor = vi.fn();
vi.mock("../emails/templates/response-email", () => ({
Expand All @@ -44,7 +61,7 @@ describe("_onFormSubmission", () => {
{ id: "field-1", identifier: "email", label: "Email", type: "email" },
{ id: "field-2", identifier: "name", label: "Name", type: "text" },
],
user: { id: 1, email: "test@example.com" },
user: { id: 1, email: "test@example.com", timeFormat: 12, locale: "en" },
teamId: null,
settings: { emailOwnerOnSubmission: true },
};
Expand Down Expand Up @@ -122,13 +139,119 @@ describe("_onFormSubmission", () => {
});
});

describe("Workflows", () => {
it("should call WorkflowService.scheduleFormWorkflows for FORM_SUBMITTED workflows", async () => {
const mockWorkflows = [
{
id: 1,
name: "Form Submitted Workflow",
userId: 1,
teamId: null,
trigger: WorkflowTriggerEvents.FORM_SUBMITTED,
time: null,
timeUnit: null,
steps: [
{
id: 1,
action: WorkflowActions.EMAIL_ATTENDEE,
sendTo: null,
reminderBody: "Thank you for your submission!",
emailSubject: "Form Received",
template: WorkflowTemplates.CUSTOM,
verifiedAt: new Date(),
includeCalendarEvent: false,
numberVerificationPending: false,
numberRequired: false,
},
],
},
];

vi.mocked(getAllWorkflowsFromRoutingForm).mockResolvedValueOnce(mockWorkflows as any);

await _onFormSubmission(mockForm as any, mockResponse, responseId);

expect(getAllWorkflowsFromRoutingForm).toHaveBeenCalledWith(mockForm);
expect(WorkflowService.scheduleFormWorkflows).toHaveBeenCalledWith({
workflows: mockWorkflows,
responses: {
email: {
value: "test@response.com",
response: "test@response.com",
},
name: { value: "Test Name", response: "Test Name" },
},
responseId,
form: {
...mockForm,
fields: mockForm.fields.map((field) => ({
type: field.type,
identifier: field.identifier,
})),
},
});
});

it("should call WorkflowService.scheduleFormWorkflows for FORM_SUBMITTED_NO_EVENT workflows", async () => {
const mockWorkflows = [
{
id: 2,
name: "Form Follow-up Workflow",
userId: 1,
teamId: null,
trigger: WorkflowTriggerEvents.FORM_SUBMITTED_NO_EVENT,
time: 30,
timeUnit: "MINUTE",
steps: [
{
id: 2,
action: WorkflowActions.EMAIL_ATTENDEE,
sendTo: null,
reminderBody: "Follow up on your form submission",
emailSubject: "Follow Up",
template: WorkflowTemplates.CUSTOM,
verifiedAt: new Date(),
includeCalendarEvent: false,
numberVerificationPending: false,
numberRequired: false,
},
],
},
];

vi.mocked(getAllWorkflowsFromRoutingForm).mockResolvedValueOnce(mockWorkflows as any);

await _onFormSubmission(mockForm as any, mockResponse, responseId);

expect(getAllWorkflowsFromRoutingForm).toHaveBeenCalledWith(mockForm);
expect(WorkflowService.scheduleFormWorkflows).toHaveBeenCalledWith({
workflows: mockWorkflows,
responses: {
email: {
value: "test@response.com",
response: "test@response.com",
},
name: { value: "Test Name", response: "Test Name" },
},
responseId,
form: {
...mockForm,
fields: mockForm.fields.map((field) => ({
type: field.type,
identifier: field.identifier,
})),
},
});
});
});

describe("Response Email", () => {
it("should send response email to team members for a team form", async () => {
const teamForm = {
...mockForm,
teamId: 1,
userWithEmails: ["team-member1@example.com", "team-member2@example.com"],
user: { id: 1, email: "test@example.com" },
user: { id: 1, email: "test@example.com", timeFormat: 12, locale: "en" },
};

await _onFormSubmission(teamForm as any, mockResponse, responseId);
Expand Down
Loading
Loading