+
+
+
{formatPhoneNumber(phone.phoneNumber)}
{phone.subscriptionStatus === PhoneNumberSubscriptionStatus.ACTIVE && (
diff --git a/packages/features/ee/workflows/lib/actionHelperFunctions.ts b/packages/features/ee/workflows/lib/actionHelperFunctions.ts
index 47d30fd31fb66d..dc0d1f79f9d429 100644
--- a/packages/features/ee/workflows/lib/actionHelperFunctions.ts
+++ b/packages/features/ee/workflows/lib/actionHelperFunctions.ts
@@ -1,8 +1,7 @@
import type { TFunction } from "i18next";
import type { TimeFormat } from "@calcom/lib/timeFormat";
-import type { WorkflowTriggerEvents } from "@calcom/prisma/client";
-import { WorkflowActions, WorkflowTemplates } from "@calcom/prisma/enums";
+import { WorkflowActions, WorkflowTemplates, WorkflowTriggerEvents } from "@calcom/prisma/enums";
import {
whatsappEventCancelledTemplate,
@@ -10,9 +9,11 @@ import {
whatsappEventRescheduledTemplate,
whatsappReminderTemplate,
} from "../lib/reminders/templates/whatsapp";
+import { FORM_TRIGGER_WORKFLOW_EVENTS } from "./constants";
import emailRatingTemplate from "./reminders/templates/emailRatingTemplate";
import emailReminderTemplate from "./reminders/templates/emailReminderTemplate";
import smsReminderTemplate from "./reminders/templates/smsReminderTemplate";
+import type { WorkflowStep } from "./types";
export function shouldScheduleEmailReminder(action: WorkflowActions) {
return action === WorkflowActions.EMAIL_ATTENDEE || action === WorkflowActions.EMAIL_HOST;
@@ -141,3 +142,11 @@ export function getTemplateBodyForAction({
const templateFunction = getEmailTemplateFunction(template);
return templateFunction({ isEditingMode: true, locale, t, action, timeFormat }).emailBody;
}
+
+export function isFormTrigger(trigger: WorkflowTriggerEvents) {
+ return FORM_TRIGGER_WORKFLOW_EVENTS.includes(trigger);
+}
+
+export function hasCalAIAction(steps: WorkflowStep[]) {
+ return steps.some((step) => isCalAIAction(step.action));
+}
diff --git a/packages/features/ee/workflows/lib/constants.ts b/packages/features/ee/workflows/lib/constants.ts
index 617e21adeb28b1..a9703887b0b3b7 100644
--- a/packages/features/ee/workflows/lib/constants.ts
+++ b/packages/features/ee/workflows/lib/constants.ts
@@ -8,6 +8,8 @@ export const WORKFLOW_TRIGGER_EVENTS = [
WorkflowTriggerEvents.RESCHEDULE_EVENT,
WorkflowTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW,
WorkflowTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW,
+ WorkflowTriggerEvents.FORM_SUBMITTED,
+ WorkflowTriggerEvents.FORM_SUBMITTED_NO_EVENT,
WorkflowTriggerEvents.BOOKING_REJECTED,
WorkflowTriggerEvents.BOOKING_REQUESTED,
WorkflowTriggerEvents.BOOKING_PAYMENT_INITIATED,
@@ -94,4 +96,16 @@ export const IMMEDIATE_WORKFLOW_TRIGGER_EVENTS: WorkflowTriggerEvents[] = [
WorkflowTriggerEvents.BOOKING_PAYMENT_INITIATED,
WorkflowTriggerEvents.BOOKING_REJECTED,
WorkflowTriggerEvents.BOOKING_REQUESTED,
+ WorkflowTriggerEvents.FORM_SUBMITTED,
+ WorkflowTriggerEvents.FORM_SUBMITTED_NO_EVENT, // no real immediate workflow but it's scheduled with tasker
];
+
+export const FORM_TRIGGER_WORKFLOW_EVENTS: WorkflowTriggerEvents[] = [
+ WorkflowTriggerEvents.FORM_SUBMITTED,
+ WorkflowTriggerEvents.FORM_SUBMITTED_NO_EVENT,
+];
+
+export const ALLOWED_FORM_WORKFLOW_ACTIONS = [
+ WorkflowActions.EMAIL_ATTENDEE,
+ WorkflowActions.EMAIL_ADDRESS,
+] as const;
diff --git a/packages/features/ee/workflows/lib/getAllWorkflows.ts b/packages/features/ee/workflows/lib/getAllWorkflows.ts
index 569194255c26fe..6f84733a387947 100644
--- a/packages/features/ee/workflows/lib/getAllWorkflows.ts
+++ b/packages/features/ee/workflows/lib/getAllWorkflows.ts
@@ -1,4 +1,5 @@
import prisma from "@calcom/prisma";
+import type { WorkflowType } from "@calcom/prisma/client";
import type { Workflow } from "./types";
@@ -27,20 +28,31 @@ export const workflowSelect = {
},
};
-export const getAllWorkflows = async (
- eventTypeWorkflows: Workflow[],
- userId?: number | null,
- teamId?: number | null,
- orgId?: number | null,
- workflowsLockedForUser = true
-) => {
- const allWorkflows = eventTypeWorkflows;
+export const getAllWorkflows = async ({
+ entityWorkflows,
+ userId,
+ teamId,
+ orgId,
+ workflowsLockedForUser = true,
+ type,
+}: {
+ entityWorkflows: Workflow[];
+ userId?: number | null;
+ teamId?: number | null;
+ orgId?: number | null;
+ workflowsLockedForUser?: boolean;
+ type: WorkflowType;
+}) => {
+ const allWorkflows = entityWorkflows;
if (orgId) {
if (teamId) {
const orgTeamWorkflowsRel = await prisma.workflowsOnTeams.findMany({
where: {
teamId: teamId,
+ workflow: {
+ type,
+ },
},
select: {
workflow: {
@@ -54,6 +66,9 @@ export const getAllWorkflows = async (
} else if (userId) {
const orgUserWorkflowsRel = await prisma.workflowsOnTeams.findMany({
where: {
+ workflow: {
+ type,
+ },
team: {
members: {
some: {
@@ -79,6 +94,7 @@ export const getAllWorkflows = async (
where: {
teamId: orgId,
isActiveOnAll: true,
+ type,
},
select: workflowSelect,
});
@@ -90,6 +106,7 @@ export const getAllWorkflows = async (
where: {
teamId,
isActiveOnAll: true,
+ type,
},
select: workflowSelect,
});
@@ -102,6 +119,7 @@ export const getAllWorkflows = async (
userId,
teamId: null,
isActiveOnAll: true,
+ type,
},
select: workflowSelect,
});
diff --git a/packages/features/ee/workflows/lib/getOptions.ts b/packages/features/ee/workflows/lib/getOptions.ts
index f7cf2856048fd5..e83b1dceb5c8c7 100644
--- a/packages/features/ee/workflows/lib/getOptions.ts
+++ b/packages/features/ee/workflows/lib/getOptions.ts
@@ -8,6 +8,7 @@ import {
isWhatsappAction,
isEmailToAttendeeAction,
isCalAIAction,
+ isFormTrigger,
} from "./actionHelperFunctions";
import {
WHATSAPP_WORKFLOW_TEMPLATES,
@@ -45,22 +46,36 @@ export function getWorkflowTriggerOptions(t: TFunction) {
});
}
+function convertToTemplateOptions(
+ t: TFunction,
+ hasPaidPlan: boolean,
+ templates: readonly WorkflowTemplates[]
+) {
+ return templates.map((template) => {
+ return {
+ label: t(`${template.toLowerCase()}`),
+ value: template,
+ needsTeamsUpgrade: !hasPaidPlan,
+ } as { label: string; value: any; needsTeamsUpgrade: boolean };
+ });
+}
+
export function getWorkflowTemplateOptions(
t: TFunction,
action: WorkflowActions | undefined,
- hasPaidPlan: boolean
+ hasPaidPlan: boolean,
+ trigger: WorkflowTriggerEvents
) {
+ if (isFormTrigger(trigger)) {
+ return convertToTemplateOptions(t, hasPaidPlan, [WorkflowTemplates.CUSTOM]);
+ }
+
const TEMPLATES =
action && isWhatsappAction(action)
? WHATSAPP_WORKFLOW_TEMPLATES
: action && isEmailToAttendeeAction(action)
? ATTENDEE_WORKFLOW_TEMPLATES
: BASIC_WORKFLOW_TEMPLATES;
- return TEMPLATES.map((template) => {
- return {
- label: t(`${template.toLowerCase()}`),
- value: template,
- needsTeamsUpgrade: !hasPaidPlan && template == WorkflowTemplates.CUSTOM,
- };
- }) as { label: string; value: any; needsTeamsUpgrade: boolean }[];
+
+ return convertToTemplateOptions(t, hasPaidPlan, TEMPLATES);
}
diff --git a/packages/features/ee/workflows/lib/reminders/aiPhoneCallManager.ts b/packages/features/ee/workflows/lib/reminders/aiPhoneCallManager.ts
index 027535f615da20..791f03fbc38b0c 100644
--- a/packages/features/ee/workflows/lib/reminders/aiPhoneCallManager.ts
+++ b/packages/features/ee/workflows/lib/reminders/aiPhoneCallManager.ts
@@ -1,7 +1,7 @@
import { v4 as uuidv4 } from "uuid";
import dayjs from "@calcom/dayjs";
-import { CAL_AI_AGENT_PHONE_NUMBER_FIELD } from "@calcom/features/bookings/lib/SystemField";
+import { CAL_AI_AGENT_PHONE_NUMBER_FIELD } from "@calcom/lib/bookings/SystemField";
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
import tasker from "@calcom/features/tasker";
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
diff --git a/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts b/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts
index 09664e448f6d9d..cb1e4c9eae1637 100644
--- a/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts
+++ b/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts
@@ -8,6 +8,7 @@ import tasker from "@calcom/features/tasker";
import { WEBSITE_URL } from "@calcom/lib/constants";
import logger from "@calcom/lib/logger";
import { getTranslation } from "@calcom/lib/server/i18n";
+import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import prisma from "@calcom/prisma";
import type { TimeUnit } from "@calcom/prisma/enums";
import {
@@ -20,6 +21,7 @@ import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
import { getWorkflowRecipientEmail } from "../getWorkflowReminders";
import { sendOrScheduleWorkflowEmails } from "./providers/emailProvider";
+import type { FormSubmissionData } from "./reminderScheduler";
import type { AttendeeInBookingInfo, BookingInfo, timeUnitLowerCase } from "./smsReminderManager";
import type { VariablesType } from "./templates/customTemplate";
import customTemplate from "./templates/customTemplate";
@@ -33,8 +35,7 @@ type ScheduleEmailReminderAction = Extract<
"EMAIL_HOST" | "EMAIL_ATTENDEE" | "EMAIL_ADDRESS"
>;
-export interface ScheduleReminderArgs {
- evt: BookingInfo;
+export type ScheduleReminderArgs = {
triggerEvent: WorkflowTriggerEvents;
timeSpan: {
time: number | null;
@@ -44,10 +45,15 @@ export interface ScheduleReminderArgs {
sender?: string | null;
workflowStepId?: number;
seatReferenceUid?: string;
-}
+} & (
+ | { evt: BookingInfo; formData?: never }
+ | {
+ evt?: never;
+ formData: FormSubmissionData;
+ }
+);
-interface scheduleEmailReminderArgs extends ScheduleReminderArgs {
- evt: BookingInfo;
+type scheduleEmailReminderArgs = ScheduleReminderArgs & {
sendTo: string[];
action: ScheduleEmailReminderAction;
emailSubject?: string;
@@ -55,9 +61,64 @@ interface scheduleEmailReminderArgs extends ScheduleReminderArgs {
hideBranding?: boolean;
includeCalendarEvent?: boolean;
verifiedAt: Date | null;
-}
+};
+
+type SendEmailReminderParams = {
+ mailData: {
+ subject: string;
+ html: string;
+ replyTo?: string;
+ attachments?: any[];
+ sender?: string | null;
+ };
+ sendTo: string[];
+ triggerEvent: WorkflowTriggerEvents;
+ scheduledDate?: dayjs.Dayjs | null;
+ uid?: string;
+ workflowStepId?: number;
+ seatReferenceUid?: string;
+};
+
+const sendOrScheduleWorkflowEmailWithReminder = async (params: SendEmailReminderParams) => {
+ const { mailData, sendTo, scheduledDate, uid, workflowStepId } = params;
+
+ let reminderUid = undefined;
+ if (scheduledDate) {
+ const reminder = await prisma.workflowReminder.create({
+ data: {
+ bookingUid: uid,
+ workflowStepId,
+ method: WorkflowMethods.EMAIL,
+ scheduledDate: scheduledDate.toDate(),
+ scheduled: true,
+ },
+ });
+ reminderUid = reminder.uuid;
+ }
+
+ await sendOrScheduleWorkflowEmails({
+ ...mailData,
+ to: sendTo,
+ sendAt: scheduledDate?.toDate(),
+ referenceUid: reminderUid ?? undefined,
+ });
+};
export const scheduleEmailReminder = async (args: scheduleEmailReminderArgs) => {
+ const { verifiedAt, workflowStepId } = args;
+ if (!verifiedAt) {
+ log.warn(`Workflow step ${workflowStepId} not yet verified`);
+ return;
+ }
+
+ if (args.evt) {
+ await scheduleEmailReminderForEvt(args);
+ } else {
+ await scheduleEmailReminderForForm(args);
+ }
+};
+
+const scheduleEmailReminderForEvt = async (args: scheduleEmailReminderArgs & { evt: BookingInfo }) => {
const {
evt,
triggerEvent,
@@ -72,14 +133,8 @@ export const scheduleEmailReminder = async (args: scheduleEmailReminderArgs) =>
hideBranding,
includeCalendarEvent,
action,
- verifiedAt,
} = args;
- if (!verifiedAt) {
- log.warn(`Workflow step ${workflowStepId} not yet verified`);
- return;
- }
-
const { startTime, endTime } = evt;
const uid = evt.uid as string;
const timeUnit: timeUnitLowerCase | undefined = timeSpan.timeUnit?.toLocaleLowerCase() as timeUnitLowerCase;
@@ -274,25 +329,67 @@ export const scheduleEmailReminder = async (args: scheduleEmailReminderArgs) =>
const mailData = await prepareEmailData();
- let reminderUid = undefined;
- if (scheduledDate) {
- const reminder = await prisma.workflowReminder.create({
- data: {
- bookingUid: uid,
- workflowStepId,
- method: WorkflowMethods.EMAIL,
- scheduledDate: scheduledDate.toDate(),
- scheduled: true,
- },
- });
- reminderUid = reminder.uuid;
+ await sendOrScheduleWorkflowEmailWithReminder({
+ mailData,
+ sendTo,
+ triggerEvent,
+ scheduledDate,
+ uid,
+ workflowStepId,
+ seatReferenceUid,
+ });
+};
+
+const scheduleEmailReminderForForm = async (
+ args: scheduleEmailReminderArgs & {
+ formData: FormSubmissionData;
}
+) => {
+ const {
+ formData,
+ triggerEvent,
+ sender,
+ workflowStepId,
+ sendTo,
+ emailSubject = "",
+ emailBody = "",
+ hideBranding,
+ } = args;
- await sendOrScheduleWorkflowEmails({
- ...mailData,
- to: sendTo,
- sendAt: scheduledDate?.toDate(),
- referenceUid: reminderUid ?? undefined,
+ const emailContent = {
+ emailSubject,
+ emailBody: `${emailBody}`,
+ };
+
+ if (emailBody) {
+ const timeFormat = getTimeFormatStringFromUserTimeFormat(formData.user.timeFormat);
+ //todo: add variables
+ const emailSubjectTemplate = customTemplate(emailSubject, {}, formData.user.locale, timeFormat);
+ emailContent.emailSubject = emailSubjectTemplate.text;
+ emailContent.emailBody = customTemplate(
+ emailBody,
+ {},
+ formData.user.locale,
+ timeFormat,
+ hideBranding
+ ).html;
+ }
+
+ // Allows debugging generated email content without waiting for sendgrid to send emails
+ log.debug(`Sending Email for trigger ${triggerEvent}`, JSON.stringify(emailContent));
+
+ const mailData = {
+ subject: emailContent.emailSubject,
+ html: emailContent.emailBody,
+ sender,
+ };
+
+ await sendOrScheduleWorkflowEmailWithReminder({
+ mailData,
+ sendTo,
+ triggerEvent,
+ workflowStepId,
+ scheduledDate: null,
});
};
diff --git a/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts b/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts
index 9e2f28e401b190..d1f2609127e705 100644
--- a/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts
+++ b/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts
@@ -1,3 +1,4 @@
+import type { FORM_SUBMITTED_WEBHOOK_RESPONSES } from "@calcom/app-store/routing-forms/lib/formSubmissionUtils";
import {
isAttendeeAction,
isSMSAction,
@@ -8,6 +9,8 @@ import {
import { sendOrScheduleWorkflowEmails } from "@calcom/features/ee/workflows/lib/reminders/providers/emailProvider";
import * as twilio from "@calcom/features/ee/workflows/lib/reminders/providers/twilioProvider";
import type { Workflow, WorkflowStep } from "@calcom/features/ee/workflows/lib/types";
+import { getSubmitterEmail } from "@calcom/features/tasker/tasks/triggerFormSubmittedNoEvent/formSubmissionValidation";
+import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
import { checkSMSRateLimit } from "@calcom/lib/checkRateLimitAndThrowError";
import { SENDER_NAME } from "@calcom/lib/constants";
import { formatCalEventExtended } from "@calcom/lib/formatCalendarEvent";
@@ -20,10 +23,19 @@ import type { CalendarEvent } from "@calcom/types/Calendar";
import { scheduleAIPhoneCall } from "./aiPhoneCallManager";
import { scheduleEmailReminder } from "./emailReminderManager";
-import { scheduleSMSReminder } from "./smsReminderManager";
-import type { ScheduleTextReminderAction } from "./smsReminderManager";
+import type { BookingInfo } from "./smsReminderManager";
+import { scheduleSMSReminder, type ScheduleTextReminderAction } from "./smsReminderManager";
import { scheduleWhatsappReminder } from "./whatsappReminderManager";
+export type FormSubmissionData = {
+ responses: FORM_SUBMITTED_WEBHOOK_RESPONSES;
+ user: {
+ email: string;
+ timeFormat: number | null;
+ locale: string;
+ };
+};
+
export type ExtendedCalendarEvent = Omit & {
metadata?: { videoCallUrl: string | undefined };
eventType: {
@@ -36,18 +48,23 @@ export type ExtendedCalendarEvent = Omit & {
bookerUrl: string;
};
-type ProcessWorkflowStepParams = {
+type ProcessWorkflowStepParams = (
+ | { calendarEvent: ExtendedCalendarEvent; formData?: never }
+ | {
+ calendarEvent?: never;
+ formData: FormSubmissionData;
+ }
+) & {
smsReminderNumber: string | null;
- calendarEvent: ExtendedCalendarEvent;
emailAttendeeSendToOverride?: string;
hideBranding?: boolean;
seatReferenceUid?: string;
};
-export interface ScheduleWorkflowRemindersArgs extends ProcessWorkflowStepParams {
+export type ScheduleWorkflowRemindersArgs = ProcessWorkflowStepParams & {
workflows: Workflow[];
isDryRun?: boolean;
-}
+};
const processWorkflowStep = async (
workflow: Workflow,
@@ -58,11 +75,21 @@ const processWorkflowStep = async (
emailAttendeeSendToOverride,
hideBranding,
seatReferenceUid,
+ formData,
}: ProcessWorkflowStepParams
) => {
if (!step?.verifiedAt) return;
- const evt = formatCalEventExtended(calendarEvent);
+ const evt = calendarEvent ? formatCalEventExtended(calendarEvent) : undefined;
+
+ if (!evt && !formData) return;
+
+ const contextData:
+ | { evt: BookingInfo; formData?: never }
+ | {
+ evt?: never;
+ formData: FormSubmissionData;
+ } = evt ? { evt } : { formData: formData as FormSubmissionData };
if (isSMSOrWhatsappAction(step.action)) {
await checkSMSRateLimit({
@@ -71,26 +98,36 @@ const processWorkflowStep = async (
});
}
+ // Common parameters for all scheduling functions
+ const scheduleFunctionParams = {
+ triggerEvent: workflow.trigger,
+ timeSpan: {
+ time: workflow.time,
+ timeUnit: workflow.timeUnit,
+ },
+ workflowStepId: step.id,
+ template: step.template,
+ userId: workflow.userId,
+ teamId: workflow.teamId,
+ seatReferenceUid,
+ verifiedAt: step.verifiedAt,
+ };
+
if (isSMSAction(step.action)) {
+ if (!evt) {
+ // SMS action not not yet supported for form triggers
+ return;
+ }
const sendTo = step.action === WorkflowActions.SMS_ATTENDEE ? smsReminderNumber : step.sendTo;
+
await scheduleSMSReminder({
- evt,
+ ...scheduleFunctionParams,
reminderPhone: sendTo,
- triggerEvent: workflow.trigger,
action: step.action as ScheduleTextReminderAction,
- timeSpan: {
- time: workflow.time,
- timeUnit: workflow.timeUnit,
- },
message: step.reminderBody || "",
- workflowStepId: step.id,
- template: step.template,
sender: step.sender,
- userId: workflow.userId,
- teamId: workflow.teamId,
isVerificationPending: step.numberVerificationPending,
- seatReferenceUid,
- verifiedAt: step.verifiedAt,
+ evt,
});
} else if (
step.action === WorkflowActions.EMAIL_ATTENDEE ||
@@ -103,7 +140,12 @@ const processWorkflowStep = async (
case WorkflowActions.EMAIL_ADDRESS:
sendTo = [step.sendTo || ""];
break;
- case WorkflowActions.EMAIL_HOST:
+ case WorkflowActions.EMAIL_HOST: {
+ if (!evt) {
+ // EMAIL_HOST is not supported for form triggers
+ return;
+ }
+
sendTo = [evt.organizer?.email || ""];
const schedulingType = evt.eventType.schedulingType;
@@ -113,74 +155,71 @@ const processWorkflowStep = async (
sendTo = sendTo.concat(evt.team.members.map((member) => member.email));
}
break;
+ }
case WorkflowActions.EMAIL_ATTENDEE:
- const attendees = !!emailAttendeeSendToOverride
- ? [emailAttendeeSendToOverride]
- : evt.attendees?.map((attendee) => attendee.email);
-
- const limitGuestsDate = new Date("2025-01-13");
-
- if (workflow.userId) {
- const user = await prisma.user.findUnique({
- where: {
- id: workflow.userId,
- },
- select: {
- createdDate: true,
- },
- });
- if (user?.createdDate && user.createdDate > limitGuestsDate) {
- sendTo = attendees.slice(0, 1);
+ if (evt) {
+ const attendees = emailAttendeeSendToOverride
+ ? [emailAttendeeSendToOverride]
+ : evt.attendees?.map((attendee) => attendee.email);
+
+ const limitGuestsDate = new Date("2025-01-13");
+
+ if (workflow.userId) {
+ const userRepository = new UserRepository(prisma);
+ const user = await userRepository.findById({ id: workflow.userId });
+ if (user?.createdDate && user.createdDate > limitGuestsDate) {
+ sendTo = attendees.slice(0, 1);
+ } else {
+ sendTo = attendees;
+ }
} else {
sendTo = attendees;
}
- } else {
- sendTo = attendees;
}
- break;
+ if (formData) {
+ const submitterEmail = getSubmitterEmail(formData.responses);
+ if (submitterEmail) {
+ sendTo = [submitterEmail];
+ }
+ }
}
- await scheduleEmailReminder({
- evt,
- triggerEvent: workflow.trigger,
+ const emailParams = {
+ ...scheduleFunctionParams,
action: step.action,
- timeSpan: {
- time: workflow.time,
- timeUnit: workflow.timeUnit,
- },
sendTo,
emailSubject: step.emailSubject || "",
emailBody: step.reminderBody || "",
- template: step.template,
sender: step.sender || SENDER_NAME,
- workflowStepId: step.id,
hideBranding,
- seatReferenceUid,
includeCalendarEvent: step.includeCalendarEvent,
+ ...contextData,
verifiedAt: step.verifiedAt,
- });
+ } as const;
+
+ await scheduleEmailReminder(emailParams);
} else if (isWhatsappAction(step.action)) {
+ if (!evt) {
+ // Whatsapp action not not yet supported for form triggers
+ return;
+ }
+
const sendTo = step.action === WorkflowActions.WHATSAPP_ATTENDEE ? smsReminderNumber : step.sendTo;
+
await scheduleWhatsappReminder({
- evt,
+ ...scheduleFunctionParams,
reminderPhone: sendTo,
- triggerEvent: workflow.trigger,
action: step.action as ScheduleTextReminderAction,
- timeSpan: {
- time: workflow.time,
- timeUnit: workflow.timeUnit,
- },
message: step.reminderBody || "",
- workflowStepId: step.id,
- template: step.template,
- userId: workflow.userId,
- teamId: workflow.teamId,
isVerificationPending: step.numberVerificationPending,
- seatReferenceUid,
- verifiedAt: step.verifiedAt,
+ evt,
});
} else if (isCalAIAction(step.action)) {
+ if (!evt) {
+ // cal.ai not yet supported for form triggers
+ return;
+ }
await scheduleAIPhoneCall({
evt,
triggerEvent: workflow.trigger,
@@ -206,6 +245,7 @@ const _scheduleWorkflowReminders = async (args: ScheduleWorkflowRemindersArgs) =
hideBranding,
seatReferenceUid,
isDryRun = false,
+ formData,
} = args;
if (isDryRun || !workflows.length) return;
@@ -214,11 +254,11 @@ const _scheduleWorkflowReminders = async (args: ScheduleWorkflowRemindersArgs) =
for (const step of workflow.steps) {
await processWorkflowStep(workflow, step, {
- calendarEvent: evt,
emailAttendeeSendToOverride,
smsReminderNumber,
hideBranding,
seatReferenceUid,
+ ...(evt ? { calendarEvent: evt } : { formData }),
});
}
}
diff --git a/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts b/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts
index de1542a0c82556..6a037864f4477f 100644
--- a/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts
+++ b/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts
@@ -76,7 +76,8 @@ export type ScheduleTextReminderAction = Extract<
WorkflowActions,
"SMS_ATTENDEE" | "SMS_NUMBER" | "WHATSAPP_ATTENDEE" | "WHATSAPP_NUMBER"
>;
-export interface ScheduleTextReminderArgs extends ScheduleReminderArgs {
+
+export type ScheduleTextReminderArgs = ScheduleReminderArgs & {
reminderPhone: string | null;
message: string;
action: ScheduleTextReminderAction;
@@ -85,9 +86,8 @@ export interface ScheduleTextReminderArgs extends ScheduleReminderArgs {
isVerificationPending?: boolean;
prisma?: PrismaClient;
verifiedAt: Date | null;
-}
-
-export const scheduleSMSReminder = async (args: ScheduleTextReminderArgs) => {
+};
+export const scheduleSMSReminder = async (args: ScheduleTextReminderArgs & { evt: BookingInfo }) => {
const {
evt,
reminderPhone,
diff --git a/packages/features/ee/workflows/lib/reminders/whatsappReminderManager.ts b/packages/features/ee/workflows/lib/reminders/whatsappReminderManager.ts
index 644056dafa3e0c..ca1f8f72b3e7e1 100644
--- a/packages/features/ee/workflows/lib/reminders/whatsappReminderManager.ts
+++ b/packages/features/ee/workflows/lib/reminders/whatsappReminderManager.ts
@@ -16,7 +16,7 @@ import {
getContentVariablesForTemplate,
} from "../reminders/templates/whatsapp/ContentSidMapping";
import { scheduleSmsOrFallbackEmail, sendSmsOrFallbackEmail } from "./messageDispatcher";
-import type { ScheduleTextReminderArgs, timeUnitLowerCase } from "./smsReminderManager";
+import type { BookingInfo, ScheduleTextReminderArgs, timeUnitLowerCase } from "./smsReminderManager";
import {
whatsappEventCancelledTemplate,
whatsappEventCompletedTemplate,
@@ -26,7 +26,7 @@ import {
const log = logger.getSubLogger({ prefix: ["[whatsappReminderManager]"] });
-export const scheduleWhatsappReminder = async (args: ScheduleTextReminderArgs) => {
+export const scheduleWhatsappReminder = async (args: ScheduleTextReminderArgs & { evt: BookingInfo }) => {
const {
evt,
reminderPhone,
diff --git a/packages/features/ee/workflows/lib/repository/workflowReminder.ts b/packages/features/ee/workflows/lib/repository/workflowReminder.ts
index e235d7084c2401..6f5d468c9ca4f1 100644
--- a/packages/features/ee/workflows/lib/repository/workflowReminder.ts
+++ b/packages/features/ee/workflows/lib/repository/workflowReminder.ts
@@ -31,4 +31,20 @@ export class WorkflowReminderRepository {
},
});
}
+
+ static async findWorkflowRemindersByStepId(workflowStepId: number) {
+ return await prisma.workflowReminder.findMany({
+ where: { workflowStepId },
+ select: {
+ id: true,
+ referenceId: true,
+ method: true,
+ booking: {
+ select: {
+ eventTypeId: true,
+ },
+ },
+ },
+ });
+ }
}
diff --git a/packages/features/ee/workflows/lib/service/WorkflowService.test.ts b/packages/features/ee/workflows/lib/service/WorkflowService.test.ts
new file mode 100644
index 00000000000000..04c6a03bd68bdb
--- /dev/null
+++ b/packages/features/ee/workflows/lib/service/WorkflowService.test.ts
@@ -0,0 +1,209 @@
+import { describe, expect, vi, beforeEach } from "vitest";
+
+import { scheduleWorkflowReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler";
+import { tasker } from "@calcom/features/tasker";
+import { WorkflowTriggerEvents, WorkflowActions, WorkflowTemplates, TimeUnit } from "@calcom/prisma/enums";
+import { test } from "@calcom/web/test/fixtures/fixtures";
+
+import { WorkflowService } from "./WorkflowService";
+
+vi.mock("@calcom/features/ee/workflows/lib/reminders/reminderScheduler");
+vi.mock("@calcom/features/tasker");
+
+const mockScheduleWorkflowReminders = vi.mocked(scheduleWorkflowReminders);
+const mockTasker = vi.mocked(tasker);
+
+// Mock the getHideBranding function to return false
+vi.mock("@calcom/features/profile/lib/hideBranding", () => ({
+ getHideBranding: vi.fn().mockResolvedValue(false),
+}));
+
+describe("WorkflowService.scheduleFormWorkflows", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ const mockForm = {
+ id: "form-123",
+ userId: 101,
+ teamId: null,
+ fields: [
+ { type: "email", identifier: "email" },
+ { type: "phone", identifier: "phone" },
+ ],
+ user: {
+ email: "formowner@example.com",
+ timeFormat: 12,
+ locale: "en",
+ },
+ };
+
+ const mockResponses = {
+ email: {
+ value: "submitter@example.com",
+ response: "submitter@example.com",
+ },
+ phone: {
+ value: "+1234567890",
+ response: "+1234567890",
+ },
+ };
+
+ test("should call scheduleWorkflowReminders for FORM_SUBMITTED triggers with correct phone number", async () => {
+ const workflows = [
+ {
+ id: 1,
+ name: "Form Submitted",
+ userId: 101,
+ teamId: null,
+ trigger: WorkflowTriggerEvents.FORM_SUBMITTED,
+ time: null,
+ timeUnit: null,
+ steps: [
+ {
+ id: 1,
+ action: WorkflowActions.SMS_ATTENDEE,
+ sendTo: null,
+ reminderBody: "Thank you!",
+ emailSubject: "Form Received",
+ template: WorkflowTemplates.CUSTOM,
+ verifiedAt: new Date(),
+ includeCalendarEvent: false,
+ numberVerificationPending: false,
+ numberRequired: false,
+ },
+ ],
+ },
+ ];
+
+ await WorkflowService.scheduleFormWorkflows({
+ workflows,
+ responses: mockResponses,
+ form: mockForm,
+ });
+
+ expect(mockScheduleWorkflowReminders).toHaveBeenCalledWith({
+ smsReminderNumber: "+1234567890",
+ formData: {
+ responses: mockResponses,
+ user: { email: "formowner@example.com", timeFormat: 12, locale: "en" },
+ },
+ hideBranding: false,
+ workflows: [workflows[0]],
+ });
+ });
+
+ test("should create task for FORM_SUBMITTED_NO_EVENT triggers", async () => {
+ const workflows = [
+ {
+ id: 2,
+ name: "Form Follow-up",
+ userId: 101,
+ teamId: null,
+ trigger: WorkflowTriggerEvents.FORM_SUBMITTED_NO_EVENT,
+ time: 30,
+ timeUnit: TimeUnit.MINUTE,
+ steps: [
+ {
+ id: 2,
+ action: WorkflowActions.EMAIL_ATTENDEE,
+ sendTo: null,
+ reminderBody: "Follow up message",
+ emailSubject: "Follow Up",
+ template: WorkflowTemplates.CUSTOM,
+ verifiedAt: new Date(),
+ includeCalendarEvent: false,
+ numberVerificationPending: false,
+ numberRequired: false,
+ sender: null,
+ },
+ ],
+ },
+ ];
+
+ mockTasker.create.mockResolvedValue({ id: "task-123" });
+
+ await WorkflowService.scheduleFormWorkflows({
+ workflows,
+ responses: mockResponses,
+ responseId: 123,
+ form: mockForm,
+ });
+
+ expect(mockTasker.create).toHaveBeenCalledWith(
+ "triggerFormSubmittedNoEventWorkflow",
+ {
+ responseId: 123,
+ responses: mockResponses,
+ smsReminderNumber: "+1234567890",
+ hideBranding: false,
+ submittedAt: expect.any(Date),
+ form: {
+ id: "form-123",
+ userId: 101,
+ teamId: undefined,
+ user: {
+ email: "formowner@example.com",
+ timeFormat: 12,
+ locale: "en",
+ },
+ },
+ workflow: workflows[0],
+ },
+ { scheduledAt: expect.any(Date) }
+ );
+ });
+
+ test("should handle forms without phone fields by passing null smsReminderNumber", async () => {
+ const formWithoutPhone = {
+ ...mockForm,
+ fields: [
+ { type: "email", identifier: "email" },
+ { type: "text", identifier: "name" },
+ ],
+ };
+
+ const workflows = [
+ {
+ id: 1,
+ name: "Form Submitted",
+ userId: 101,
+ teamId: null,
+ trigger: WorkflowTriggerEvents.FORM_SUBMITTED,
+ time: null,
+ timeUnit: null,
+ steps: [
+ {
+ id: 1,
+ action: WorkflowActions.EMAIL_ATTENDEE,
+ sendTo: null,
+ reminderBody: "Thank you!",
+ emailSubject: "Form Received",
+ template: WorkflowTemplates.CUSTOM,
+ verifiedAt: new Date(),
+ includeCalendarEvent: false,
+ numberVerificationPending: false,
+ numberRequired: false,
+ sender: null,
+ },
+ ],
+ },
+ ];
+
+ await WorkflowService.scheduleFormWorkflows({
+ workflows,
+ responses: mockResponses,
+ form: formWithoutPhone,
+ });
+
+ expect(mockScheduleWorkflowReminders).toHaveBeenCalledWith({
+ smsReminderNumber: null,
+ formData: {
+ responses: mockResponses,
+ user: { email: "formowner@example.com", timeFormat: 12, locale: "en" },
+ },
+ hideBranding: false,
+ workflows: [workflows[0]],
+ });
+ });
+});
diff --git a/packages/features/ee/workflows/lib/service/WorkflowService.ts b/packages/features/ee/workflows/lib/service/WorkflowService.ts
new file mode 100644
index 00000000000000..c57529e9078ea3
--- /dev/null
+++ b/packages/features/ee/workflows/lib/service/WorkflowService.ts
@@ -0,0 +1,234 @@
+import dayjs from "@calcom/dayjs";
+import { getAllWorkflows } from "@calcom/ee/workflows/lib/getAllWorkflows";
+import type { ScheduleWorkflowRemindersArgs } from "@calcom/ee/workflows/lib/reminders/reminderScheduler";
+import { scheduleWorkflowReminders } from "@calcom/ee/workflows/lib/reminders/reminderScheduler";
+import type { timeUnitLowerCase } from "@calcom/ee/workflows/lib/reminders/smsReminderManager";
+import type { Workflow } from "@calcom/ee/workflows/lib/types";
+import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository";
+import { WorkflowRepository } from "@calcom/features/ee/workflows/repositories/WorkflowRepository";
+import { getHideBranding } from "@calcom/features/profile/lib/hideBranding";
+import { tasker } from "@calcom/features/tasker";
+import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId";
+import { prisma } from "@calcom/prisma";
+import { WorkflowTriggerEvents, WorkflowType } from "@calcom/prisma/enums";
+import type { FORM_SUBMITTED_WEBHOOK_RESPONSES } from "@calcom/routing-forms/lib/formSubmissionUtils";
+
+// TODO (Sean): Move most of the logic migrated in 16861 to this service
+export class WorkflowService {
+ static _beforeAfterEventTriggers: WorkflowTriggerEvents[] = [
+ WorkflowTriggerEvents.AFTER_EVENT,
+ WorkflowTriggerEvents.BEFORE_EVENT,
+ ];
+
+ static async getAllWorkflowsFromRoutingForm(routingForm: {
+ id: string;
+ userId: number | null;
+ teamId: number | null;
+ }) {
+ const routingFormWorkflows = await WorkflowRepository.findWorkflowsActiveOnRoutingForm({
+ routingFormId: routingForm.id,
+ });
+
+ const teamId = routingForm.teamId;
+ const userId = routingForm.userId;
+ const orgId = await getOrgIdFromMemberOrTeamId({ memberId: userId, teamId });
+
+ const allWorkflows = await getAllWorkflows({
+ entityWorkflows: routingFormWorkflows,
+ userId,
+ teamId,
+ orgId,
+ workflowsLockedForUser: false,
+ type: WorkflowType.ROUTING_FORM,
+ });
+
+ return allWorkflows;
+ }
+
+ static async deleteWorkflowRemindersOfRemovedTeam(teamId: number) {
+ const teamRepository = new TeamRepository(prisma);
+ const team = await teamRepository.findById({ id: teamId });
+
+ if (team?.parentId) {
+ const activeWorkflowsOnTeam = await WorkflowRepository.findActiveWorkflowsOnTeam({
+ parentTeamId: team.parentId,
+ teamId: team.id,
+ });
+
+ for (const workflow of activeWorkflowsOnTeam) {
+ const workflowSteps = workflow.steps;
+ let remainingActiveOnIds = [];
+
+ if (workflow.isActiveOnAll) {
+ const teamRepository = new TeamRepository(prisma);
+ const allRemainingOrgTeams = await teamRepository.findOrgTeamsExcludingTeam({
+ parentId: team.parentId,
+ excludeTeamId: team.id,
+ });
+ remainingActiveOnIds = allRemainingOrgTeams.map((team) => team.id);
+ } else {
+ remainingActiveOnIds = workflow.activeOnTeams
+ .filter((activeOn) => activeOn.teamId !== team.id)
+ .map((activeOn) => activeOn.teamId);
+ }
+ const remindersToDelete = await WorkflowRepository.getRemindersFromRemovedTeams(
+ [team.id],
+ workflowSteps,
+ remainingActiveOnIds
+ );
+ await WorkflowRepository.deleteAllWorkflowReminders(remindersToDelete);
+ }
+ }
+ }
+
+ static async scheduleFormWorkflows({
+ workflows,
+ responses,
+ form,
+ responseId,
+ }: {
+ responseId: number;
+ workflows: Workflow[];
+ responses: FORM_SUBMITTED_WEBHOOK_RESPONSES;
+ form: {
+ id: string;
+ userId: number;
+ teamId?: number | null;
+ fields?: { type: string; identifier?: string }[];
+ user: {
+ email: string;
+ timeFormat: number | null;
+ locale: string | null;
+ };
+ };
+ }) {
+ if (workflows.length <= 0) return;
+
+ const workflowsToTrigger: Workflow[] = [];
+
+ workflowsToTrigger.push(
+ ...workflows.filter((workflow) => workflow.trigger === WorkflowTriggerEvents.FORM_SUBMITTED)
+ );
+
+ let smsReminderNumber: string | null = null;
+ if (form.fields) {
+ const phoneField = form.fields.find((field) => field.type === "phone");
+ if (phoneField && phoneField.identifier) {
+ const phoneResponse = responses[phoneField.identifier];
+ if (phoneResponse?.response && typeof phoneResponse.response === "string") {
+ smsReminderNumber = phoneResponse.response as string;
+ }
+ }
+ }
+
+ const hideBranding = await getHideBranding({
+ userId: form.userId,
+ teamId: form.teamId ?? undefined,
+ });
+
+ await scheduleWorkflowReminders({
+ smsReminderNumber,
+ formData: {
+ responses,
+ user: { email: form.user.email, timeFormat: form.user.timeFormat, locale: form.user.locale ?? "en" },
+ },
+ hideBranding,
+ workflows: workflowsToTrigger,
+ });
+
+ const workflowsToSchedule: Workflow[] = [];
+
+ workflowsToSchedule.push(
+ ...workflows.filter((workflow) => workflow.trigger === WorkflowTriggerEvents.FORM_SUBMITTED_NO_EVENT)
+ );
+
+ const promisesFormSubmittedNoEvent = workflowsToSchedule.map((workflow) => {
+ const timeUnit: timeUnitLowerCase = (workflow.timeUnit?.toLowerCase() as timeUnitLowerCase) ?? "minute";
+
+ const scheduledAt = dayjs()
+ .add(workflow.time ?? 15, timeUnit)
+ .toDate();
+
+ return tasker.create(
+ "triggerFormSubmittedNoEventWorkflow",
+ {
+ responseId,
+ responses,
+ smsReminderNumber,
+ hideBranding,
+ form: {
+ id: form.id,
+ userId: form.userId,
+ teamId: form.teamId ?? undefined,
+ user: {
+ email: form.user.email,
+ timeFormat: form.user.timeFormat,
+ locale: form.user.locale ?? "en",
+ },
+ },
+ workflow,
+ submittedAt: new Date(),
+ },
+ { scheduledAt }
+ );
+ });
+ await Promise.all(promisesFormSubmittedNoEvent);
+ }
+
+ static async scheduleWorkflowsForNewBooking({
+ isNormalBookingOrFirstRecurringSlot,
+ isConfirmedByDefault,
+ isRescheduleEvent,
+ workflows,
+ ...args
+ }: ScheduleWorkflowRemindersArgs & {
+ isConfirmedByDefault: boolean;
+ isRescheduleEvent: boolean;
+ isNormalBookingOrFirstRecurringSlot: boolean;
+ }) {
+ if (workflows.length <= 0) return;
+
+ const workflowsToTrigger: Workflow[] = [];
+
+ if (isRescheduleEvent) {
+ workflowsToTrigger.push(
+ ...workflows.filter(
+ (workflow) =>
+ workflow.trigger === WorkflowTriggerEvents.RESCHEDULE_EVENT ||
+ this._beforeAfterEventTriggers.includes(workflow.trigger)
+ )
+ );
+ } else if (!isConfirmedByDefault) {
+ workflowsToTrigger.push(
+ ...workflows.filter((workflow) => workflow.trigger === WorkflowTriggerEvents.BOOKING_REQUESTED)
+ );
+ } else if (isConfirmedByDefault) {
+ workflowsToTrigger.push(
+ ...workflows.filter(
+ (workflow) =>
+ this._beforeAfterEventTriggers.includes(workflow.trigger) ||
+ (isNormalBookingOrFirstRecurringSlot && workflow.trigger === WorkflowTriggerEvents.NEW_EVENT)
+ )
+ );
+ }
+
+ if (workflowsToTrigger.length === 0) return;
+
+ await scheduleWorkflowReminders({
+ ...args,
+ workflows: workflowsToTrigger,
+ });
+ }
+
+ static async scheduleWorkflowsFilteredByTriggerEvent({
+ workflows,
+ triggers,
+ ...args
+ }: ScheduleWorkflowRemindersArgs & { triggers: WorkflowTriggerEvents[] }) {
+ if (workflows.length <= 0) return;
+ await scheduleWorkflowReminders({
+ ...args,
+ workflows: workflows.filter((workflow) => triggers.includes(workflow.trigger)),
+ });
+ }
+}
diff --git a/packages/features/ee/workflows/pages/index.tsx b/packages/features/ee/workflows/pages/index.tsx
index a18ca7a098ee9d..fceaf53303d211 100644
--- a/packages/features/ee/workflows/pages/index.tsx
+++ b/packages/features/ee/workflows/pages/index.tsx
@@ -10,7 +10,7 @@ import Shell, { ShellMain } from "@calcom/features/shell/Shell";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
-import type { WorkflowRepository } from "@calcom/lib/server/repository/workflow";
+import type { WorkflowRepository } from "@calcom/features/ee/workflows/repositories/WorkflowRepository";
import { trpc } from "@calcom/trpc/react";
import classNames from "@calcom/ui/classNames";
import { Avatar } from "@calcom/ui/components/avatar";
diff --git a/packages/features/ee/workflows/pages/workflow.tsx b/packages/features/ee/workflows/pages/workflow.tsx
index 1b6d64fb643639..9293f2411cb017 100644
--- a/packages/features/ee/workflows/pages/workflow.tsx
+++ b/packages/features/ee/workflows/pages/workflow.tsx
@@ -4,13 +4,13 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { useSession } from "next-auth/react";
import { useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
-import { useForm } from "react-hook-form";
+import { useForm, useWatch } from "react-hook-form";
import { Toaster } from "sonner";
import { SENDER_ID } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
-import type { WorkflowRepository } from "@calcom/lib/server/repository/workflow";
+import type { WorkflowRepository } from "@calcom/features/ee/workflows/repositories/WorkflowRepository";
import type { WorkflowPermissions } from "@calcom/lib/server/repository/workflow-permissions";
import type { WorkflowStep } from "@calcom/prisma/client";
import type { TimeUnit, WorkflowTriggerEvents } from "@calcom/prisma/enums";
@@ -31,7 +31,12 @@ import LicenseRequired from "../../common/components/LicenseRequired";
import { DeleteDialog } from "../components/DeleteDialog";
import SkeletonLoader from "../components/SkeletonLoaderEdit";
import WorkflowDetailsPage from "../components/WorkflowDetailsPage";
-import { isSMSAction, isSMSOrWhatsappAction, isCalAIAction } from "../lib/actionHelperFunctions";
+import {
+ isSMSAction,
+ isSMSOrWhatsappAction,
+ isCalAIAction,
+ isFormTrigger,
+} from "../lib/actionHelperFunctions";
import { formSchema } from "../lib/schema";
import { getTranslatedText, translateVariablesToEnglish } from "../lib/variableTranslations";
@@ -80,6 +85,11 @@ function WorkflowPage({
const utils = trpc.useUtils();
+ const watchedTrigger = useWatch({
+ control: form.control,
+ name: "trigger",
+ });
+
const userQuery = useMeQuery();
const user = userQuery.data;
@@ -119,12 +129,13 @@ function WorkflowPage({
const teamId = workflow?.teamId ?? undefined;
- const { data, isPending: isPendingEventTypes } = trpc.viewer.eventTypes.getTeamAndEventTypeOptions.useQuery(
+ const { data, isPending: isPendingEventTypes } = trpc.viewer.eventTypes.getActiveOnOptions.useQuery(
{ teamId, isOrg },
{ enabled: !isPendingWorkflow }
);
const teamOptions = data?.teamOptions ?? [];
+ const routingFormOptions = data?.routingFormOptions ?? [];
let allEventTypeOptions = data?.eventTypeOptions ?? [];
const distinctEventTypes = new Set();
@@ -223,7 +234,11 @@ function WorkflowPage({
let activeOn;
if (workflowData.isActiveOnAll) {
- activeOn = isOrg ? teamOptions : allEventTypeOptions;
+ activeOn = isOrg
+ ? teamOptions
+ : isFormTrigger(workflowData.trigger)
+ ? routingFormOptions
+ : allEventTypeOptions;
} else {
if (isOrg) {
activeOn = workflowData.activeOnTeams.flatMap((active) => {
@@ -233,6 +248,14 @@ function WorkflowPage({
};
});
setSelectedOptions(activeOn || []);
+ } else if (isFormTrigger(workflowData.trigger)) {
+ activeOn = workflowData.activeOnRoutingForms?.flatMap((active) => {
+ return {
+ value: String(active.routingForm.id) || "",
+ label: active.routingForm.name || "",
+ };
+ });
+ setSelectedOptions(activeOn || []);
} else {
setSelectedOptions(
workflowData.activeOn?.flatMap((active) => {
@@ -309,7 +332,6 @@ function WorkflowPage({
});
const validateAndSubmitWorkflow = async (values: FormValues): Promise => {
- let activeOnIds: number[] = [];
let isEmpty = false;
let isVerified = true;
@@ -367,18 +389,26 @@ function WorkflowPage({
});
if (!isEmpty && isVerified) {
- if (values.activeOn) {
- activeOnIds = values.activeOn
+ let activeOnEventTypeOrTeamIds: number[] = [];
+ let activeOnRoutingFormIds: string[] = [];
+ if (isOrg || !isFormTrigger(values.trigger)) {
+ activeOnEventTypeOrTeamIds = values.activeOn
.filter((option) => option.value !== "all")
.map((option) => {
return parseInt(option.value, 10);
});
+ } else {
+ // Form triggers, activeOn contains routing form IDs (strings)
+ activeOnRoutingFormIds = values.activeOn
+ .filter((option) => option.value !== "all")
+ .map((option) => option.value);
}
await updateMutation.mutateAsync({
id: workflowId,
name: values.name,
- activeOn: activeOnIds,
+ activeOnEventTypeIds: activeOnEventTypeOrTeamIds,
+ activeOnRoutingFormIds,
steps: values.steps,
trigger: values.trigger,
time: values.time || null,
@@ -497,7 +527,7 @@ function WorkflowPage({