From d7fc11df5ed3648faa0f5f7fef19e8126927e5b3 Mon Sep 17 00:00:00 2001 From: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com> Date: Thu, 13 Nov 2025 16:34:44 +0100 Subject: [PATCH] feat: use form responses as workflow variables (#24716) * remove add variable dropdown * feat: lang support * fix: type errors * feat: select voice agent * refactor: address feedback * refactor: address feedback * refactor: missing import * fix: types * add getAllWorkflowsFromRoutingForm to WorkflowService * fix error caused by undefined evt * fix type error * fix type error * fix tests * feat: add inbound calls * chore: formatting * chore * feat: finish inbound call * chore: formatting * fix: update bug * fix: types * code clean up * final fixes and clean up * remove console.log * remove template text form from triggers * add routing form repoditory function * refactor: Agent Configuration Sheet (#23930) * refactor: agent configuration sheet * chore: use default phone numbre * refactor: improvements * refactor: improvements * fix: types * fix: feedback * fix bug with key * chore: * fix: feedback * fix: prompt * add comments * fix: review * fix: review * refactor: class * refactor: class * fix test * allow cal ai action on form triggers * move any reusable code to scheduleAIPhoneCall * add missing await * use predefined FormSubmissionData type * add .trim() to sms message * pass contextData instead * finish base setup * add missing trigger in update-workflow.input.ts * allow cal.ai action for form triggers in handler * chore: add support for form workflows on api v2 * fixup! chore: add support for form workflows on api v2 * ai phone call on form submissions (WIP) * use existing type for Option array * pass chosen event type id * refactor: rename * Update apps/web/public/static/locales/en/common.json * Update apps/web/public/static/locales/en/common.json * add missing imports * chore: update set value * fix: remove index * fix: type error * fix: update tetss * use only repository functions in update handler * move all prisma queries from list.handler * review suggestions * fix: use logger * 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: don't use static * fix: type * 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 * fix: schema * refactor: * remove action check in update handler * add event type selection * event type selector improvements * adjust update.handler * set outboundEventTypeId * add back trpc import * fix agent repository functions * clean up * fix bugs caused by merge * pass eventTypeId to updateToolsFromAgentId * add migration for outboundEventTypeId * add SMS actions to allowed form action constants * add cal ai to allowed form actions * pick correct event type for web call * pass correct routed event type id * remove unsued import * fixes for offset api v2 * add missing responseId * fix failing test * fix failing test * improve error message * remove unused imports * chore: handle sms step action for form worklfow in dtos * fix typo * missing missing newStep * minor fixes * remove changes * add routedEventTypeId * fix type error * fix type error * fix typ error in executAPIPhoneCall.tsx * add back inboundEventTypeId * remove console.log * remove outdated code * small fixes * don't throw error for missing phone number * add back filtered triggerOptions * fix eventTypeId in testCall handler * fix type error * update migration * fix trigger is not defined * convert eventTypeId to string * only use outboundEventTypeId for FORM_SUBMITTED trigger * show toast when no event type selected * fix type errors * add missing translation * fix type error * remove callType * fix tests * small fixes * clean up AgentConfigurationSheet * remove EventTypeSelector file * code clean up * clean up * clean up * use resusable function for TestPhoneCallDialog and WebCallDialog * rename result * fix types for event type id * use repository runction in workflowReminder.ts * fix type error * pass eventTypeIds correctly * fix typo * custom variables from form responses * remove comment * Update apps/web/public/static/locales/en/common.json Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> * use watch instead of getValues * change to z.record(z.unknown()) instead of any() * fix type of eventTypeId * check permissinon for outBoundEventTypeId * add isNaN check * improve function name * add tests * fixes for custom variables * improve test * update tools when outbound agent event type id changes * handle undefined eventDate in customTemplate * add info how to use form responses as variables * rename responses to routingFormResponses * remove cal ai from allowed steps api v2 * remove old migration file --------- Co-authored-by: CarinaWolli Co-authored-by: Udit Takkar Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Co-authored-by: Benny Joo Co-authored-by: cal.com Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Co-authored-by: Peer Richelsen --- apps/web/public/static/locales/en/common.json | 4 + .../components/WorkflowStepContainer.tsx | 188 +++++++------ .../lib/reminders/emailReminderManager.ts | 28 +- .../lib/reminders/smsReminderManager.ts | 15 +- .../lib/reminders/templates/customTemplate.ts | 101 +++++-- .../ee/workflows/lib/reminders/utils.ts | 4 +- .../ee/workflows/lib/test/workflows.test.ts | 250 +++++++++++++++++- .../tasker/tasks/executeAIPhoneCall.ts | 22 +- 8 files changed, 476 insertions(+), 136 deletions(-) diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 6c4ca136e2..f7d93cbbbc 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -1622,12 +1622,14 @@ "example_1": "Example 1", "example_2": "Example 2", "booking_question_identifier": "Booking Question Identifier", + "form_field_identifier": "Form Field Identifier", "company_size": "Company size", "what_help_needed": "What do you need help with?", "variable_format": "Variable format", "webhook_subscriber_url_reserved": "Webhook subscriber url is already defined", "custom_input_as_variable_info": "Ignore all special characters of the additional input label (use only letters and numbers), use uppercase for all letters and replace whitespaces with underscores.", "using_booking_questions_as_variables": "How do I use booking questions as variables?", + "using_form_responses_as_variables": "How do I use form responses as variables?", "download_desktop_app": "Download desktop app", "set_ping_link": "Set Ping link", "rate_limit_exceeded": "Rate limit exceeded", @@ -1923,6 +1925,7 @@ "show_attendees": "Share attendee information between guests", "show_available_seats_count": "Show the number of available seats", "how_booking_questions_as_variables": "How to use booking questions as variables?", + "how_form_responses_as_variables": "How to use form responses as variables?", "format": "Format", "questions": "Questions", "all_info_your_booker_provide": "All the info your booker should provide before booking with you.", @@ -1939,6 +1942,7 @@ "billing_portal": "Billing portal", "billing_help_cta": "Contact support", "ignore_special_characters_booking_questions": "Ignore special characters in your booking question identifier. Use only letters and numbers", + "ignore_special_characters_form_responses": "Ignore special characters in your form field identifier. Use only letters and numbers", "retry": "Retry", "fetching_calendars_error": "There was a problem fetching your calendars. Please <1>try again or reach out to customer support.", "calendar_connection_fail": "Calendar connection failed", diff --git a/packages/features/ee/workflows/components/WorkflowStepContainer.tsx b/packages/features/ee/workflows/components/WorkflowStepContainer.tsx index 8938afec89..d41eb01db2 100644 --- a/packages/features/ee/workflows/components/WorkflowStepContainer.tsx +++ b/packages/features/ee/workflows/components/WorkflowStepContainer.tsx @@ -123,19 +123,19 @@ const getTimeSectionText = (trigger: WorkflowTriggerEvents, t: TFunction) => { const CalAIAgentDataSkeleton = () => { return ( -
-
+
+
- -
- - - + +
+ + +
-
- - +
+ +
@@ -518,7 +518,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { return ( <>
- +
{!!timeSectionText && ( -
- +
+
)} @@ -610,7 +610,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { {selectedOptions && setSelectedOptions && allOptions && (
{isOrganization ? ( -
+
@@ -677,7 +677,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
)} {!!timeSectionText && ( -
+

{t("testing_sms_workflow_info_message")}

@@ -824,12 +824,12 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { />
-
{t("sender_id_info")}
+
{t("sender_id_info")}
{form.formState.errors.steps && form.formState?.errors?.steps[step.stepNumber - 1]?.sender && ( -

{t("sender_id_error_message")}

+

{t("sender_id_error_message")}

)} ) : ( @@ -848,23 +848,23 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
)} {isCalAIAction(form.getValues(`steps.${step.stepNumber - 1}.action`)) && !stepAgentId && ( -
-
+
+
-

+

{t("cal_ai_agent")} - + {t("set_up_required")}

-

+

{t("no_phone_number_connected")}.

{form.formState.errors.steps && form.formState?.errors?.steps[step.stepNumber - 1]?.sendTo && ( -

+

{form.formState?.errors?.steps[step.stepNumber - 1]?.sendTo?.message || ""}

)} @@ -1185,9 +1185,9 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { ) : ( !props.readOnly && ( <> -
+
-
+
{form.formState.errors.steps && form.formState?.errors?.steps[step.stepNumber - 1]?.sendTo && ( -

+

{form.formState?.errors?.steps[step.stepNumber - 1]?.sendTo?.message || ""}

)} @@ -1304,7 +1304,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
)} {!isCalAIAction(form.getValues(`steps.${step.stepNumber - 1}.action`)) && ( -
+
{isEmailSubjectNeeded && (
@@ -1337,14 +1337,14 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { /> {form.formState.errors.steps && form.formState?.errors?.steps[step.stepNumber - 1]?.emailSubject && ( -

+

{form.formState?.errors?.steps[step.stepNumber - 1]?.emailSubject?.message || ""}

)}
)} -
-
)} - {!props.readOnly && !isFormTrigger(trigger) && ( -
+ {!props.readOnly && ( +
@@ -1511,24 +1515,32 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
-

{t("how_booking_questions_as_variables")}

-
-

{t("format")}

-
    +

    + {isFormTrigger(trigger) + ? t("how_form_responses_as_variables") + : t("how_booking_questions_as_variables")} +

    +
    +

    {t("format")}

    +
    • {t("uppercase_for_letters")}
    • {t("replace_whitespaces_underscores")}
    • -
    • {t("ignore_special_characters_booking_questions")}
    • +
    • + {isFormTrigger(trigger) + ? t("ignore_special_characters_form_responses") + : t("ignore_special_characters_booking_questions")} +
    -

    {t("example_1")}

    -
    -
    - {t("booking_question_identifier")} +

    {t("example_1")}

    +
    +
    + {isFormTrigger(trigger) ? t("form_field_identifier") : t("booking_question_identifier")}
    -
    {t("company_size")}
    -
    {t("variable")}
    +
    {t("company_size")}
    +
    {t("variable")}
    -
    +
    {" "} {`{${t("company_size") .replace(/[^a-zA-Z0-9 ]/g, "") @@ -1539,14 +1551,14 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
    -

    {t("example_2")}

    -
    -
    - {t("booking_question_identifier")} +

    {t("example_2")}

    +
    +
    + {isFormTrigger(trigger) ? t("form_field_identifier") : t("booking_question_identifier")}
    -
    {t("what_help_needed")}
    -
    {t("variable")}
    -
    +
    {t("what_help_needed")}
    +
    {t("variable")}
    +
    {" "} {`{${t("what_help_needed") .replace(/[^a-zA-Z0-9 ]/g, "") @@ -1620,17 +1632,17 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
    -

    {t("do_you_still_want_to_unsubscribe")}

    +

    {t("do_you_still_want_to_unsubscribe")}

    {getActivePhoneNumbers( agentData?.outboundPhoneNumbers?.map((phone) => ({ ...phone, subscriptionStatus: phone.subscriptionStatus ?? undefined, })) ).length > 0 && ( -
    -
    - - +
    +
    + + {formatPhoneNumber( getActivePhoneNumbers( agentData?.outboundPhoneNumbers?.map((phone) => ({ @@ -1643,7 +1655,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
    )} -

    {t("the_action_will_disconnect_phone_number")}

    +

    {t("the_action_will_disconnect_phone_number")}