Skip to content

Commit e639237

Browse files
CarinaWolliCarinaWolliAmit91848coderabbitai[bot]Udit-takkar
authored
feat: SMS workflow action for form triggers #3 (calcom#23673)
* allow routing forms for activeOn * use repository function to get routing forms * remove unnecessary code * adjust logic in update handler * add triggers to api v2 * remvoe unused file * rename to getAcitveOnOptions handler * remove routingFormOptions handler * clean up getActiveOnOptions * refactor WorkflowService * remove logs * remove unused * fix: type check * fix: missed before after events for recurring * fix: calendarEvent handleMarkNoShow * fix error message Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * don't query disabled routing forms * create tasker function * add tasker code * move isFormTrigger function * small adjustments + todo comments * remove email to host action for form triggers * throw trpc error if email to host is added as step * fix dialog on how to use form responses as variables * remove add variable dropdown for form triggers * remove form workfows in event workflows tab * improvements for workflow logic on form submission * review fixes * base setup for seperate schedule functions (evt and form) * add missing BOOKING_PAID workflow trigger * fix pathname * fix: test for BOOKING_REQUESTED * fix activeOn ids * pass hideBranding and smsReminderNumber * adjustments to reminderScheduler * create empty scheduelForForm functions * pass locale and timezone with form user * pass formData instead of responses * pass timeFormat and locale * reusable function for email sending and reminder creation * implement scheduleEmailReminderForForm * remove added editor field from merge conflict * don't support cal.ai action with form triggers * throw bad request if form trigger and cal.ai is combined * add tests for scheduleFormWorkflows * add form submission tests * remove form response varibe info * clean up workflow actions * fixes for getting template options * pass triggerType to getAllWorkflows * move reusable logic to scheduleSMSReminder * add formdata to param type * type fixes for text reminder managers * implement scheduleSMSReminderForForm * fix import * fix isAuthorizedToAddActiveOnIds * disble whatsapp action * implement triggerFormSubmittedNoEventWorkflow * code clean up * Merge branch 'devin/1755107037-add-workflow-triggers' into feat/routing-form-workflow-triggers * fix type errors * remove async from getSubmitterEmail * fix type errors * revert cal.ai changes * fix type error * add sublogger * code clean up * fix type errors * remove label for attendee whatsapp action * code clean up * fixes saving teams on org workflows * fix type error * code improvements for activeOn ids * Revert "code improvements for activeOn ids" This reverts commit 0a3590a. * improve variable name * fix unit tests * small fixes * type fixes * remove unused translation keys * fix merge conflict issues * code clean up * remove SMS action support * remove more SMS code * add missing imports * set custom template for form action * type fixes * fix tasker endpoint * fix duplicate check * fix workfows.test.ts * use repository funciton to getHideBranding * add back SMS action * add back changes to smsReminderManager * code clean up * fix hasDuplicateSubmission * code clean up * select only needed properties * remove repository functions * Revert "remove repository functions" This reverts commit 7aa47b1. * add scheduleWorkflows function * Revert "add scheduleWorkflows function" This reverts commit fe5db4f. * move type to /types * Revert "move type to /types" This reverts commit 91e0152. * revert changes causing type errors * remove import * remove unused import * Revert "remove unused import" This reverts commit 1916768. * revert changed from attempt to fix type errors * pass object to gt all workflows * fix isAuthorized check * trigger filtering * remove form submitted no event booked code * remove form submitted no event from schema * remove more code * remove test * fixes * add getSubmitterEmail function * add trigger * small fixes * add missing workflow DTOs * small fixes * use activeOnWithChildren * fix active on when switching trigger type * remove add variable dropdown * add getAllWorkflowsFromRoutingForm to WorkflowService * fix error caused by undefined evt * fix type error * fix type error * fix tests * code clean up * final fixes and clean up * remove console.log * remove template text form from triggers * add routing form repoditory function * fix bug with key * add comments * fix test * add missing await * use predefined FormSubmissionData type * add .trim() to sms message * pass contextData instead * add missing trigger in update-workflow.input.ts * ForEvt and ForForm function for aiPhoneCallManager * chore: add support for form workflows on api v2 * fixup! chore: add support for form workflows on api v2 * use only repository functions in update handler * move all prisma queries from list.handler * review suggestions * chore: handle workflows api v2 * chore: handle workflows api v2, split in 2 endpoints * fix workflow step creation * remove connect agent and fixes types * add type to workflow * chore: use workflow type in apiv2 WorkflowsOutputService * update worklfow type on update * chore: use workflow type in apiv2 WorkflowsOutputService * fix template body for torm trigger * some UI fixes for email subject/body * resetting email body when changing form triggers * use type field to query workflows * clean up all old active on values * remove responseId from all funciton calls * remove undefined from updateTemplate * refactor: split routing form and event-type workflows code * refactor: split routing form and event-type workflows code * fix template text when adding action * chore: don't rename WorkflowActivationDto to avoid ci blocking * refine update schedule to use only allowed actions * fix type error * don't allow whatsapp action with form trigger * fix type error * return early if activeOn array is empty * fix: from step type in BaseFormWorkflowStepDto * fixup! fix: from step type in BaseFormWorkflowStepDto * api v2 updates * move all prisma calls to repository (service/workflows.ts) * use FORM_TRIGGER_WORKFLOW_EVENTS for form queries * use userRepository * use FORM_TRIGGER_WORKFLOW_EVENTS in isFormTrigger * code clean up * code clean up * use repository functions in formSubmissionValidation.ts * remove action check in update handler * add back trpc import * fix agent repository functions * add SMS actions to allowed form action constants * remove unsued import * fixes for offset api v2 * add missing responseId * fix failing test * fix failing test * remove unused imports * chore: handle sms step action for form worklfow in dtos --------- Co-authored-by: CarinaWolli <wollencarina@gmail.com> Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Co-authored-by: Benny Joo <sldisek783@gmail.com> Co-authored-by: cal.com <morgan@cal.com> Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
1 parent 5bc1fbb commit e639237

13 files changed

Lines changed: 285 additions & 166 deletions

File tree

apps/api/v2/src/modules/organizations/teams/workflows/controllers/org-team-workflows.controller.e2e-spec.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ describe("OrganizationsTeamsWorkflowsController (E2E)", () => {
404404
invalidWorkflow.steps = [
405405
{
406406
stepNumber: 1,
407-
action: "sms_number",
407+
action: "cal_ai_phone_call",
408408
recipient: PHONE_NUMBER,
409409
template: REMINDER,
410410
verifiedPhoneId: verifiedPhoneId,
@@ -439,6 +439,18 @@ describe("OrganizationsTeamsWorkflowsController (E2E)", () => {
439439
html: "<p>Reminder for your event {EVENT_NAME}.</p>",
440440
},
441441
},
442+
{
443+
stepNumber: 2,
444+
action: "sms_attendee",
445+
recipient: EMAIL,
446+
template: REMINDER,
447+
phoneRequired: false,
448+
sender: "updatedSender",
449+
message: {
450+
subject: "Update Upcoming: {EVENT_NAME}",
451+
text: "Update Reminder for your event {EVENT_NAME}.</p>",
452+
},
453+
},
442454
];
443455
return request(app.getHttpServer())
444456
.post(`${basePath}/routing-form`)
@@ -459,12 +471,15 @@ describe("OrganizationsTeamsWorkflowsController (E2E)", () => {
459471
}
460472

461473
expect(responseBody.data.trigger.type).toEqual(sampleCreateWorkflowRoutingFormDto.trigger.type);
462-
expect(responseBody.data.steps).toHaveLength(sampleCreateWorkflowRoutingFormDto.steps.length);
474+
expect(responseBody.data.steps).toHaveLength(validWorkflow.steps.length);
463475
expect(responseBody.data.steps.find((step) => step.stepNumber === 1)?.id).toBeDefined();
464476
expect(responseBody.data.steps.find((step) => step.stepNumber === 1)?.sender).toEqual(
465477
"CalcomE2EStep1"
466478
);
467479

480+
expect(responseBody.data.steps.find((step) => step.stepNumber === 2)?.id).toBeDefined();
481+
expect(responseBody.data.steps.find((step) => step.action === "sms_attendee")).toBeDefined();
482+
468483
const trigger = sampleCreateWorkflowRoutingFormDto.trigger as OnFormSubmittedTriggerDto;
469484
expect(responseBody.data.trigger?.type).toEqual(trigger.type);
470485

apps/api/v2/src/modules/workflows/inputs/create-form-workflow.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ import {
77
EMAIL_ADDRESS,
88
EMAIL_ATTENDEE,
99
FORM_ALLOWED_STEP_ACTIONS,
10+
SMS_NUMBER,
1011
WorkflowEmailAddressStepDto,
1112
WorkflowEmailAttendeeStepDto,
13+
WorkflowPhoneNumberStepDto,
14+
WorkflowPhoneAttendeeStepDto,
15+
SMS_ATTENDEE,
1216
} from "./workflow-step.input";
1317
import {
1418
RoutingFormWorkflowTriggerDto,
@@ -85,6 +89,8 @@ export class CreateFormWorkflowDto {
8589
oneOf: [
8690
{ $ref: getSchemaPath(WorkflowEmailAddressStepDto) },
8791
{ $ref: getSchemaPath(WorkflowEmailAttendeeStepDto) },
92+
{ $ref: getSchemaPath(WorkflowPhoneAttendeeStepDto) },
93+
{ $ref: getSchemaPath(WorkflowPhoneNumberStepDto) },
8894
],
8995
type: "array",
9096
})
@@ -99,8 +105,15 @@ export class CreateFormWorkflowDto {
99105
subTypes: [
100106
{ value: WorkflowEmailAddressStepDto, name: EMAIL_ADDRESS },
101107
{ value: WorkflowEmailAttendeeStepDto, name: EMAIL_ATTENDEE },
108+
{ value: WorkflowPhoneAttendeeStepDto, name: SMS_ATTENDEE },
109+
{ value: WorkflowPhoneNumberStepDto, name: SMS_NUMBER },
102110
],
103111
},
104112
})
105-
steps!: (WorkflowEmailAddressStepDto | WorkflowEmailAttendeeStepDto)[];
113+
steps!: (
114+
| WorkflowEmailAddressStepDto
115+
| WorkflowEmailAttendeeStepDto
116+
| WorkflowPhoneAttendeeStepDto
117+
| WorkflowPhoneNumberStepDto
118+
)[];
106119
}

apps/api/v2/src/modules/workflows/inputs/update-form-workflow.input.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
EMAIL_ADDRESS,
99
EMAIL_ATTENDEE,
1010
FORM_ALLOWED_STEP_ACTIONS,
11+
SMS_ATTENDEE,
12+
SMS_NUMBER,
1113
UpdateEmailAddressWorkflowStepDto,
1214
UpdateEmailAttendeeWorkflowStepDto,
1315
UpdateEmailHostWorkflowStepDto,
@@ -75,6 +77,8 @@ export class UpdateFormWorkflowDto {
7577
oneOf: [
7678
{ $ref: getSchemaPath(UpdateEmailAddressWorkflowStepDto) },
7779
{ $ref: getSchemaPath(UpdateEmailAttendeeWorkflowStepDto) },
80+
{ $ref: getSchemaPath(UpdatePhoneAttendeeWorkflowStepDto) },
81+
{ $ref: getSchemaPath(UpdatePhoneNumberWorkflowStepDto) },
7882
],
7983
type: "array",
8084
})
@@ -90,8 +94,15 @@ export class UpdateFormWorkflowDto {
9094
subTypes: [
9195
{ value: UpdateEmailAddressWorkflowStepDto, name: EMAIL_ADDRESS },
9296
{ value: UpdateEmailAttendeeWorkflowStepDto, name: EMAIL_ATTENDEE },
97+
{ value: UpdatePhoneAttendeeWorkflowStepDto, name: SMS_ATTENDEE },
98+
{ value: UpdatePhoneNumberWorkflowStepDto, name: SMS_NUMBER },
9399
],
94100
},
95101
})
96-
steps?: (UpdateEmailAddressWorkflowStepDto | UpdateEmailAttendeeWorkflowStepDto)[];
102+
steps?: (
103+
| UpdateEmailAddressWorkflowStepDto
104+
| UpdateEmailAttendeeWorkflowStepDto
105+
| UpdatePhoneNumberWorkflowStepDto
106+
| UpdatePhoneAttendeeWorkflowStepDto
107+
)[];
97108
}

apps/api/v2/src/modules/workflows/inputs/workflow-step.input.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const STEP_ACTIONS = [
2424
CAL_AI_PHONE_CALL,
2525
] as const;
2626

27-
export const FORM_ALLOWED_STEP_ACTIONS = [EMAIL_ATTENDEE, EMAIL_ADDRESS] as const;
27+
export const FORM_ALLOWED_STEP_ACTIONS = [EMAIL_ATTENDEE, EMAIL_ADDRESS, SMS_ATTENDEE, SMS_NUMBER] as const;
2828

2929
export const STEP_ACTIONS_TO_ENUM = {
3030
[EMAIL_HOST]: WorkflowActions.EMAIL_HOST,

apps/web/public/static/locales/en/common.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,6 +1460,7 @@
14601460
"email_host_action": "send email to host",
14611461
"email_attendee_action": "send email to attendees",
14621462
"email_attendee_action_form": "Send email to submitted email address",
1463+
"sms_attendee_action_form": "Send SMS to submitted phone number",
14631464
"sms_attendee_action": "Send SMS to attendee",
14641465
"sms_number_action": "send SMS to a specific number",
14651466
"send_reminder_sms": "Easily send meeting reminders via SMS to your attendees",

packages/features/ee/workflows/api/scheduleSMSReminders.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,11 +171,7 @@ export async function handler(req: NextRequest) {
171171
}
172172

173173
if (message?.length && message?.length > 0 && sendTo) {
174-
const smsMessageWithoutOptOut = message;
175-
176-
if (process.env.TWILIO_OPT_OUT_ENABLED === "true") {
177-
message = await WorkflowOptOutService.addOptOutMessage(message, locale || "en");
178-
}
174+
const smsMessageWithoutOptOut = await WorkflowOptOutService.addOptOutMessage(message, locale || "en");
179175

180176
const scheduledNotification = await scheduleSmsOrFallbackEmail({
181177
twilioData: {

packages/features/ee/workflows/components/WorkflowDetailsPage.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ export default function WorkflowDetailsPage(props: Props) {
7979
if (isFormTrigger(form.getValues("trigger"))) {
8080
if (option.value === WorkflowActions.EMAIL_ATTENDEE) {
8181
label = t("email_attendee_action_form");
82+
} else if (option.value === WorkflowActions.SMS_ATTENDEE) {
83+
label = t("sms_attendee_action_form");
8284
}
8385
}
8486

@@ -167,11 +169,11 @@ export default function WorkflowDetailsPage(props: Props) {
167169
<>
168170
<div>
169171
<FormCard
170-
className="border-muted mb-0"
172+
className="mb-0 border-muted"
171173
collapsible={false}
172174
label={
173-
<div className="flex items-center gap-2 pb-2 pt-1">
174-
<div className="border-subtle text-subtle ml-1 rounded-lg border p-1">
175+
<div className="flex gap-2 items-center pt-1 pb-2">
176+
<div className="p-1 ml-1 rounded-lg border border-subtle text-subtle">
175177
<Icon name="zap" size="16" />
176178
</div>
177179
<div className="text-sm font-medium leading-none">{t("trigger")}</div>
@@ -208,11 +210,11 @@ export default function WorkflowDetailsPage(props: Props) {
208210
<div key={index}>
209211
<FormCard
210212
key={step.id}
211-
className="bg-muted border-muted mb-0"
213+
className="mb-0 bg-muted border-muted"
212214
collapsible={false}
213215
label={
214-
<div className="flex items-center gap-2 pb-2 pt-1">
215-
<div className="border-subtle text-subtle rounded-lg border p-1">
216+
<div className="flex gap-2 items-center pt-1 pb-2">
217+
<div className="p-1 rounded-lg border border-subtle text-subtle">
216218
<Icon name="arrow-right" size="16" />
217219
</div>
218220
<div className="text-sm font-medium leading-none">{t("action")}</div>

packages/features/ee/workflows/components/WorkflowStepContainer.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,11 +380,10 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
380380
});
381381

382382
const hasAiAction = hasCalAIAction(steps);
383-
const hasSMSAction = steps.some((s) => isSMSAction(s.action));
384-
const hasWhatsappAction = steps.some((s) => isWhatsappAction(s.action));
385383
const hasEmailToHostAction = steps.some((s) => s.action === WorkflowActions.EMAIL_HOST);
384+
const hasWhatsappAction = steps.some((s) => isWhatsappAction(s.action));
386385

387-
const disallowFormTriggers = hasAiAction || hasSMSAction || hasEmailToHostAction || hasWhatsappAction;
386+
const disallowFormTriggers = hasAiAction || hasEmailToHostAction || hasWhatsappAction;
388387

389388
const filteredTriggerOptions = triggerOptions.filter(
390389
(option) => !(isFormTrigger(option.value) && disallowFormTriggers)

packages/features/ee/workflows/lib/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,4 +108,6 @@ export const FORM_TRIGGER_WORKFLOW_EVENTS: WorkflowTriggerEvents[] = [
108108
export const ALLOWED_FORM_WORKFLOW_ACTIONS = [
109109
WorkflowActions.EMAIL_ATTENDEE,
110110
WorkflowActions.EMAIL_ADDRESS,
111+
WorkflowActions.SMS_ATTENDEE,
112+
WorkflowActions.SMS_NUMBER,
111113
] as const;

packages/features/ee/workflows/lib/reminders/emailReminderManager.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ const scheduleEmailReminderForEvt = async (args: scheduleEmailReminderArgs & { e
340340
});
341341
};
342342

343+
// sends all immediately, no scheduling needed
343344
const scheduleEmailReminderForForm = async (
344345
args: scheduleEmailReminderArgs & {
345346
formData: FormSubmissionData;

0 commit comments

Comments
 (0)