From 9445fa9c656dc51679294eb10ad880393083cff1 Mon Sep 17 00:00:00 2001 From: Dima Grossman Date: Thu, 19 Dec 2024 18:38:16 +0000 Subject: [PATCH 1/5] feat: provider overrides --- .../events/dtos/trigger-event-request.dto.ts | 3 +- apps/api/src/app/events/events.controller.ts | 2 +- .../parse-event-request.command.ts | 3 +- .../process-bulk-trigger.usecase.ts | 2 +- .../trigger-event-to-all.usecase.ts | 8 +- .../steps/controls/array-field-template.tsx | 36 ++++--- .../steps/controls/custom-step-controls.tsx | 11 +- .../steps/controls/json-form.tsx | 2 + .../steps/controls/object-field-template.tsx | 42 ++++---- .../steps/controls/provider-controls.tsx | 102 ++++++++++++++++++ .../steps/controls/select-widget.tsx | 4 +- .../steps/controls/switch-widget.tsx | 3 - .../steps/controls/text-widget.tsx | 22 +++- .../steps/description-field.tsx | 5 + .../steps/email/email-tabs.tsx | 2 + apps/dashboard/src/main.tsx | 5 + .../send-message/send-message-chat.usecase.ts | 5 +- .../send-message-email.usecase.ts | 9 +- .../send-message/send-message-push.usecase.ts | 14 +-- .../send-message/send-message-sms.usecase.ts | 13 +-- .../send-message/send-message.command.ts | 4 +- packages/framework/src/index.ts | 1 + .../shared/src/dto/events/event.interface.ts | 12 ++- 23 files changed, 230 insertions(+), 80 deletions(-) create mode 100644 apps/dashboard/src/components/workflow-editor/steps/controls/provider-controls.tsx create mode 100644 apps/dashboard/src/components/workflow-editor/steps/description-field.tsx diff --git a/apps/api/src/app/events/dtos/trigger-event-request.dto.ts b/apps/api/src/app/events/dtos/trigger-event-request.dto.ts index 75145e2b0b7..8ec80599976 100644 --- a/apps/api/src/app/events/dtos/trigger-event-request.dto.ts +++ b/apps/api/src/app/events/dtos/trigger-event-request.dto.ts @@ -6,6 +6,7 @@ import { TriggerRecipientsTypeEnum, TriggerRecipientSubscriber, TriggerTenantContext, + TriggerOverrides, } from '@novu/shared'; import { CreateSubscriberRequestDto } from '../../subscribers/dtos'; import { UpdateTenantRequestDto } from '../../tenant/dtos'; @@ -99,7 +100,7 @@ export class TriggerEventRequestDto { }) @IsObject() @IsOptional() - overrides?: Record>; + overrides?: TriggerOverrides; @ApiProperty({ description: 'The recipients list of people who will receive the notification.', diff --git a/apps/api/src/app/events/events.controller.ts b/apps/api/src/app/events/events.controller.ts index 3437769e161..4e9ff0ccfb2 100644 --- a/apps/api/src/app/events/events.controller.ts +++ b/apps/api/src/app/events/events.controller.ts @@ -80,7 +80,7 @@ export class EventsController { organizationId: user.organizationId, identifier: body.name, payload: body.payload || {}, - overrides: body.overrides || {}, + overrides: body.overrides || ({} as any), to: body.to, actor: body.actor, tenant: body.tenant, diff --git a/apps/api/src/app/events/usecases/parse-event-request/parse-event-request.command.ts b/apps/api/src/app/events/usecases/parse-event-request/parse-event-request.command.ts index bb2af9cde75..66ff5a57d51 100644 --- a/apps/api/src/app/events/usecases/parse-event-request/parse-event-request.command.ts +++ b/apps/api/src/app/events/usecases/parse-event-request/parse-event-request.command.ts @@ -6,6 +6,7 @@ import { TriggerRecipientSubscriber, TriggerRequestCategoryEnum, TriggerTenantContext, + TriggerOverrides, } from '@novu/shared'; import { EnvironmentWithUserCommand } from '../../../shared/commands/project.command'; @@ -19,7 +20,7 @@ export class ParseEventRequestBaseCommand extends EnvironmentWithUserCommand { payload: any; // eslint-disable-line @typescript-eslint/no-explicit-any @IsDefined() - overrides: Record>; + overrides: TriggerOverrides | {}; @IsString() @IsOptional() diff --git a/apps/api/src/app/events/usecases/process-bulk-trigger/process-bulk-trigger.usecase.ts b/apps/api/src/app/events/usecases/process-bulk-trigger/process-bulk-trigger.usecase.ts index 64f3d701de2..ad7af3b9cd9 100644 --- a/apps/api/src/app/events/usecases/process-bulk-trigger/process-bulk-trigger.usecase.ts +++ b/apps/api/src/app/events/usecases/process-bulk-trigger/process-bulk-trigger.usecase.ts @@ -23,7 +23,7 @@ export class ProcessBulkTrigger { organizationId: command.organizationId, identifier: event.name, payload: event.payload, - overrides: event.overrides || {}, + overrides: event.overrides || ({} as any), to: event.to, actor: event.actor, tenant: event.tenant, diff --git a/apps/api/src/app/events/usecases/trigger-event-to-all/trigger-event-to-all.usecase.ts b/apps/api/src/app/events/usecases/trigger-event-to-all/trigger-event-to-all.usecase.ts index d789641ecdd..9b297783382 100644 --- a/apps/api/src/app/events/usecases/trigger-event-to-all/trigger-event-to-all.usecase.ts +++ b/apps/api/src/app/events/usecases/trigger-event-to-all/trigger-event-to-all.usecase.ts @@ -1,5 +1,4 @@ import { Injectable } from '@nestjs/common'; -import { SubscriberRepository } from '@novu/dal'; import { AddressingTypeEnum, TriggerEventStatusEnum, TriggerRequestCategoryEnum } from '@novu/shared'; import { TriggerEventToAllCommand } from './trigger-event-to-all.command'; @@ -7,10 +6,7 @@ import { ParseEventRequest, ParseEventRequestBroadcastCommand } from '../parse-e @Injectable() export class TriggerEventToAll { - constructor( - private subscriberRepository: SubscriberRepository, - private parseEventRequest: ParseEventRequest - ) {} + constructor(private parseEventRequest: ParseEventRequest) {} public async execute(command: TriggerEventToAllCommand) { await this.parseEventRequest.execute( @@ -22,7 +18,7 @@ export class TriggerEventToAll { payload: command.payload || {}, addressingType: AddressingTypeEnum.BROADCAST, transactionId: command.transactionId, - overrides: command.overrides || {}, + overrides: command.overrides || ({} as any), actor: command.actor, tenant: command.tenant, requestCategory: TriggerRequestCategoryEnum.SINGLE, diff --git a/apps/dashboard/src/components/workflow-editor/steps/controls/array-field-template.tsx b/apps/dashboard/src/components/workflow-editor/steps/controls/array-field-template.tsx index 677cc3b2466..f948a971768 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/controls/array-field-template.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/controls/array-field-template.tsx @@ -52,24 +52,26 @@ export function ArrayFieldTemplate(props: ArrayFieldTemplateProps) { className="bg-background border-neutral-alpha-200 relative mt-2 flex w-full flex-col gap-2 rounded-lg border px-3 py-4 data-[state=closed]:rounded-none data-[state=closed]:border-b-0 data-[state=closed]:border-l-0 data-[state=closed]:border-r-0 data-[state=closed]:border-t data-[state=closed]:pb-0" >
-
- - - -
- {canAdd && } - - - + +
+ + + +
+ {canAdd && } +
+ +
+
-
+
diff --git a/apps/dashboard/src/components/workflow-editor/steps/controls/custom-step-controls.tsx b/apps/dashboard/src/components/workflow-editor/steps/controls/custom-step-controls.tsx index b3430817112..09c89a64fb9 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/controls/custom-step-controls.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/controls/custom-step-controls.tsx @@ -17,9 +17,10 @@ import { SidebarContent } from '@/components/side-navigation/sidebar'; import { ConfirmationModal } from '@/components/confirmation-modal'; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/primitives/accordion'; import { InlineToast } from '@/components/primitives/inline-toast'; +import { JsonSchema } from '@novu/framework/internal'; type CustomStepControlsProps = { - dataSchema: ControlsMetadata['dataSchema']; + dataSchema: ControlsMetadata['dataSchema'] | JsonSchema; origin: WorkflowOriginEnum; className?: string; }; @@ -147,7 +148,13 @@ export const CustomStepControls = (props: CustomStepControlsProps) => {
- + { + saveForm(); + }} + />
diff --git a/apps/dashboard/src/components/workflow-editor/steps/controls/json-form.tsx b/apps/dashboard/src/components/workflow-editor/steps/controls/json-form.tsx index 3743adadeee..b0de2f4c79e 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/controls/json-form.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/controls/json-form.tsx @@ -6,6 +6,7 @@ import { ArrayFieldTitleTemplate } from './array-field-title-template'; import { AddButton, RemoveButton } from './button-templates'; import { ObjectFieldTemplate } from './object-field-template'; import { JSON_SCHEMA_FORM_ID_DELIMITER, UI_SCHEMA, WIDGETS } from './template-utils'; +import { DescriptionField } from '../description-field'; type JsonFormProps = Pick< FormProps, @@ -25,6 +26,7 @@ export function JsonForm(props: JsonFormProps) { autoComplete="false" idSeparator={JSON_SCHEMA_FORM_ID_DELIMITER} templates={{ + DescriptionFieldTemplate: DescriptionField as any, ButtonTemplates: { AddButton, RemoveButton, diff --git a/apps/dashboard/src/components/workflow-editor/steps/controls/object-field-template.tsx b/apps/dashboard/src/components/workflow-editor/steps/controls/object-field-template.tsx index 466b4342df6..bf2883dde30 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/controls/object-field-template.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/controls/object-field-template.tsx @@ -38,27 +38,29 @@ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) { onOpenChange={setIsEditorOpen} className="bg-background border-neutral-alpha-200 relative mt-2 flex w-full flex-col gap-2 border-t px-3 py-4 pb-0" > -
-
- - - -
- - - +
+ +
+ + + +
+
+ +
+
-
+
diff --git a/apps/dashboard/src/components/workflow-editor/steps/controls/provider-controls.tsx b/apps/dashboard/src/components/workflow-editor/steps/controls/provider-controls.tsx new file mode 100644 index 00000000000..35a76f4437c --- /dev/null +++ b/apps/dashboard/src/components/workflow-editor/steps/controls/provider-controls.tsx @@ -0,0 +1,102 @@ +import { providerSchemas } from '@novu/framework'; +import { ChannelTypeEnum, EmailProviderIdEnum } from '@novu/shared'; +import { useMemo } from 'react'; +import { JsonForm } from './json-form'; +import { RJSFSchema } from '@rjsf/utils'; +import { Accordion } from '@radix-ui/react-accordion'; +import { cn } from '../../../../utils/ui'; +import { AccordionContent, AccordionItem, AccordionTrigger } from '../../../primitives/accordion'; +import { RiInputField } from 'react-icons/ri'; +import { Form } from '../../../primitives/form/form'; +import { useForm } from 'react-hook-form'; +import { TooltipProvider } from '@radix-ui/react-tooltip'; + +export function ProviderControl() { + const { isLoading, integrations } = { + isLoading: false, + integrations: [ + { + _id: '1', + providerId: 'sendgrid', + name: 'SendGrid', + channel: ChannelTypeEnum.EMAIL, + }, + { + _id: '2', + providerId: 'mailgun', + name: 'MailGun', + channel: ChannelTypeEnum.EMAIL, + }, + { + _id: '3', + providerId: 'slack', + name: 'Slack', + channel: ChannelTypeEnum.CHAT, + }, + ], + }; + + const schemas = useMemo(() => { + return integrations + ?.filter((item) => { + return item.providerId !== 'novu-email' && item.providerId !== 'novu-sms'; + }) + .map((item) => { + return { + schema: providerSchemas[item.channel]?.[item?.providerId as EmailProviderIdEnum]?.output, + integration: item, + }; + }); + }, [integrations]); + + if (isLoading) return null; + + return ( + +
+ + {schemas?.map((item) => { + return ; + })} + +
+
+ ); +} + +function ProviderAccordion({ schema, integration }: { schema: any; integration: any }) { + const form = useForm(); + + return ( +
+ { + console.log(values); + })} + > + + +
+ + {integration.name} overrides +
+
+ + +
+ ; +
+
+
+
+ + ); +} diff --git a/apps/dashboard/src/components/workflow-editor/steps/controls/select-widget.tsx b/apps/dashboard/src/components/workflow-editor/steps/controls/select-widget.tsx index 6bd96ba7473..6f10d7514cd 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/controls/select-widget.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/controls/select-widget.tsx @@ -4,7 +4,6 @@ import { useFormContext } from 'react-hook-form'; import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/primitives/form/form'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/primitives/select'; import { capitalize } from '@/utils/string'; -import { useSaveForm } from '@/components/workflow-editor/steps/save-form-context'; import { getFieldName } from './template-utils'; export function SelectWidget(props: WidgetProps) { @@ -23,8 +22,8 @@ export function SelectWidget(props: WidgetProps) { const extractedName = useMemo(() => getFieldName(id), [id]); const { control } = useFormContext(); - const { saveForm } = useSaveForm(); + console.log({ props }); return ( { field.onChange(value); - saveForm(); }} disabled={disabled || readonly} required={required} diff --git a/apps/dashboard/src/components/workflow-editor/steps/controls/switch-widget.tsx b/apps/dashboard/src/components/workflow-editor/steps/controls/switch-widget.tsx index 276a4674363..bf92133a00f 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/controls/switch-widget.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/controls/switch-widget.tsx @@ -4,13 +4,11 @@ import { useFormContext } from 'react-hook-form'; import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/primitives/form/form'; import { Switch } from '@/components/primitives/switch'; import { capitalize } from '@/utils/string'; -import { useSaveForm } from '@/components/workflow-editor/steps/save-form-context'; import { getFieldName } from './template-utils'; export function SwitchWidget(props: WidgetProps) { const { label, readonly, disabled, required, id } = props; const { control } = useFormContext(); - const { saveForm } = useSaveForm(); const extractedName = useMemo(() => getFieldName(id), [id]); return ( @@ -26,7 +24,6 @@ export function SwitchWidget(props: WidgetProps) { checked={field.value} onCheckedChange={(value) => { field.onChange(value); - saveForm(); }} disabled={readonly || disabled} required={required} diff --git a/apps/dashboard/src/components/workflow-editor/steps/controls/text-widget.tsx b/apps/dashboard/src/components/workflow-editor/steps/controls/text-widget.tsx index 2bcd47b4231..dc74033978c 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/controls/text-widget.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/controls/text-widget.tsx @@ -11,9 +11,12 @@ import { EditorView } from '@uiw/react-codemirror'; import { useMemo } from 'react'; import { useFormContext } from 'react-hook-form'; import { getFieldName } from './template-utils'; +import { RiInformation2Line } from 'react-icons/ri'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@radix-ui/react-tooltip'; +import { cn } from '../../../../utils/ui'; export function TextWidget(props: WidgetProps) { - const { label, readonly, disabled, id, required } = props; + const { label, readonly, disabled, schema, id, required } = props; const { control } = useFormContext(); const { step } = useWorkflow(); const variables = useMemo(() => (step ? parseStepVariablesToLiquidVariables(step.variables) : []), [step]); @@ -31,7 +34,22 @@ export function TextWidget(props: WidgetProps) { name={extractedName} render={({ field }) => ( - {capitalize(label)} + + {capitalize(label)} + + {schema.description ? ( + + + + + + + +

{schema.description}

+
+
+ ) : null} +
{isNumberType ? ( diff --git a/apps/dashboard/src/components/workflow-editor/steps/description-field.tsx b/apps/dashboard/src/components/workflow-editor/steps/description-field.tsx new file mode 100644 index 00000000000..4e1ee14c557 --- /dev/null +++ b/apps/dashboard/src/components/workflow-editor/steps/description-field.tsx @@ -0,0 +1,5 @@ +import { RJSFSchema, type WidgetProps } from '@rjsf/utils'; + +export function DescriptionField(props: WidgetProps) { + return <>; +} diff --git a/apps/dashboard/src/components/workflow-editor/steps/email/email-tabs.tsx b/apps/dashboard/src/components/workflow-editor/steps/email/email-tabs.tsx index e549af18818..705dd37289d 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/email/email-tabs.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/email/email-tabs.tsx @@ -6,6 +6,7 @@ import { EmailEditorPreview } from '@/components/workflow-editor/steps/email/ema import { CustomStepControls } from '../controls/custom-step-controls'; import { StepEditorProps } from '@/components/workflow-editor/steps/configure-step-template-form'; import { TemplateTabs } from '@/components/workflow-editor/steps/template-tabs'; +import { ProviderControl } from '../controls/provider-controls'; export const EmailTabs = (props: StepEditorProps) => { const { workflow, step } = props; @@ -20,6 +21,7 @@ export const EmailTabs = (props: StepEditorProps) => { <> {isNovuCloud && } {isExternal && } + {isNovuCloud && } ); diff --git a/apps/dashboard/src/main.tsx b/apps/dashboard/src/main.tsx index 941734e8455..aafff1b7116 100644 --- a/apps/dashboard/src/main.tsx +++ b/apps/dashboard/src/main.tsx @@ -31,6 +31,7 @@ import { FeatureFlagsProvider } from './context/feature-flags-provider'; import { ConfigureStep } from '@/components/workflow-editor/steps/configure-step'; import { ConfigureStepTemplate } from '@/components/workflow-editor/steps/configure-step-template'; import { RedirectToLegacyStudioAuth } from './pages/redirect-to-legacy-studio-auth'; +import { ProviderControl } from './components/workflow-editor/steps/controls/provider-controls'; initializeSentry(); overrideZodErrorMap(); @@ -170,6 +171,10 @@ const router = createBrowserRouter([ }, ], }, + { + path: '/dima', + element: , + }, ]); createRoot(document.getElementById('root')!).render( diff --git a/apps/worker/src/app/workflow/usecases/send-message/send-message-chat.usecase.ts b/apps/worker/src/app/workflow/usecases/send-message/send-message-chat.usecase.ts index 7a72a6224be..a24698d67f6 100644 --- a/apps/worker/src/app/workflow/usecases/send-message/send-message-chat.usecase.ts +++ b/apps/worker/src/app/workflow/usecases/send-message/send-message-chat.usecase.ts @@ -393,9 +393,12 @@ export class SendMessageChat extends SendMessageBase { ...(command.overrides[integration?.providerId] || {}), }; const bridgeProviderData = command.bridgeData?.providers?.[integration.providerId] || {}; + const triggerOverrides = command.step.stepId + ? command.overrides?.steps?.[command.step.stepId]?.providers[integration.providerId] || {} + : {}; const result = await chatHandler.send({ - bridgeProviderData, + bridgeProviderData: { ...bridgeProviderData, ...triggerOverrides }, phoneNumber, customData: overrides, webhookUrl: chatWebhookUrl, diff --git a/apps/worker/src/app/workflow/usecases/send-message/send-message-email.usecase.ts b/apps/worker/src/app/workflow/usecases/send-message/send-message-email.usecase.ts index c33993a9e85..9ea289223ab 100644 --- a/apps/worker/src/app/workflow/usecases/send-message/send-message-email.usecase.ts +++ b/apps/worker/src/app/workflow/usecases/send-message/send-message-email.usecase.ts @@ -5,7 +5,6 @@ import { addBreadcrumb } from '@sentry/node'; import { MessageRepository, - NotificationStepEntity, SubscriberRepository, EnvironmentRepository, IntegrationEntity, @@ -440,9 +439,15 @@ export class SendMessageEmail extends SendMessageBase { const mailFactory = new MailFactory(); const mailHandler = mailFactory.getHandler(this.buildFactoryIntegration(integration), mailData.from); const bridgeProviderData = command.bridgeData?.providers?.[integration.providerId] || {}; + const triggerOverrides = command.step.stepId + ? command.overrides?.steps?.[command.step.stepId]?.providers[integration.providerId] || {} + : {}; try { - const result = await mailHandler.send({ ...mailData, bridgeProviderData }); + const result = await mailHandler.send({ + ...mailData, + bridgeProviderData: { ...bridgeProviderData, ...triggerOverrides }, + }); Logger.verbose({ command }, 'Email message has been sent', LOG_CONTEXT); diff --git a/apps/worker/src/app/workflow/usecases/send-message/send-message-push.usecase.ts b/apps/worker/src/app/workflow/usecases/send-message/send-message-push.usecase.ts index 05265551636..c17cfac850e 100644 --- a/apps/worker/src/app/workflow/usecases/send-message/send-message-push.usecase.ts +++ b/apps/worker/src/app/workflow/usecases/send-message/send-message-push.usecase.ts @@ -2,14 +2,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { addBreadcrumb } from '@sentry/node'; import { ModuleRef } from '@nestjs/core'; -import { - MessageRepository, - NotificationStepEntity, - SubscriberRepository, - MessageEntity, - IntegrationEntity, - JobEntity, -} from '@novu/dal'; +import { MessageRepository, SubscriberRepository, MessageEntity, IntegrationEntity, JobEntity } from '@novu/dal'; import { ChannelTypeEnum, LogCodeEnum, @@ -298,6 +291,9 @@ export class SendMessagePush extends SendMessageBase { const pushHandler = this.getIntegrationHandler(integration); const bridgeOutputs = command.bridgeData?.outputs; const bridgeProviderData = command.bridgeData?.providers?.[integration.providerId] || {}; + const triggerOverrides = command.step.stepId + ? command.overrides?.steps?.[command.step.stepId]?.providers[integration.providerId] || {} + : {}; const result = await pushHandler.send({ target: [deviceToken], @@ -307,7 +303,7 @@ export class SendMessagePush extends SendMessageBase { overrides, subscriber, step, - bridgeProviderData, + bridgeProviderData: { ...bridgeProviderData, ...triggerOverrides }, }); await this.executionLogRoute.execute( diff --git a/apps/worker/src/app/workflow/usecases/send-message/send-message-sms.usecase.ts b/apps/worker/src/app/workflow/usecases/send-message/send-message-sms.usecase.ts index 247a602d953..7895a8cd695 100644 --- a/apps/worker/src/app/workflow/usecases/send-message/send-message-sms.usecase.ts +++ b/apps/worker/src/app/workflow/usecases/send-message/send-message-sms.usecase.ts @@ -2,13 +2,7 @@ import { Injectable } from '@nestjs/common'; import { addBreadcrumb } from '@sentry/node'; import { ModuleRef } from '@nestjs/core'; -import { - MessageRepository, - NotificationStepEntity, - SubscriberRepository, - MessageEntity, - IntegrationEntity, -} from '@novu/dal'; +import { MessageRepository, SubscriberRepository, MessageEntity, IntegrationEntity } from '@novu/dal'; import { ChannelTypeEnum, LogCodeEnum, ExecutionDetailsSourceEnum, ExecutionDetailsStatusEnum } from '@novu/shared'; import { InstrumentUsecase, @@ -276,6 +270,9 @@ export class SendMessageSms extends SendMessageBase { throw new PlatformException(`Sms handler for provider ${integration.providerId} is not found`); } const bridgeProviderData = command.bridgeData?.providers?.[integration.providerId] || {}; + const triggerOverrides = command.step.stepId + ? command.overrides?.steps?.[command.step.stepId]?.providers[integration.providerId] || {} + : {}; const result = await smsHandler.send({ to: overrides.to || phone, @@ -283,7 +280,7 @@ export class SendMessageSms extends SendMessageBase { content: bridgeBody || overrides.content || content, id: message._id, customData: overrides.customData || {}, - bridgeProviderData, + bridgeProviderData: { ...bridgeProviderData, ...triggerOverrides }, }); await this.executionLogRoute.execute( diff --git a/apps/worker/src/app/workflow/usecases/send-message/send-message.command.ts b/apps/worker/src/app/workflow/usecases/send-message/send-message.command.ts index b4ae980e29c..e0074700f2e 100644 --- a/apps/worker/src/app/workflow/usecases/send-message/send-message.command.ts +++ b/apps/worker/src/app/workflow/usecases/send-message/send-message.command.ts @@ -2,7 +2,7 @@ import { IsDefined, IsOptional, IsString } from 'class-validator'; import { NotificationStepEntity, JobEntity } from '@novu/dal'; import { EnvironmentWithUserCommand } from '@novu/application-generic'; import { ExecuteOutput } from '@novu/framework/internal'; -import { WorkflowPreferences } from '@novu/shared'; +import { WorkflowPreferences, TriggerOverrides } from '@novu/shared'; export class SendMessageCommand extends EnvironmentWithUserCommand { @IsDefined() @@ -16,7 +16,7 @@ export class SendMessageCommand extends EnvironmentWithUserCommand { compileContext?: any; // eslint-disable-line @typescript-eslint/no-explicit-any @IsDefined() - overrides: Record>; + overrides: TriggerOverrides; @IsDefined() step: NotificationStepEntity; diff --git a/packages/framework/src/index.ts b/packages/framework/src/index.ts index 754f749c5d4..1f4b7e7dc88 100644 --- a/packages/framework/src/index.ts +++ b/packages/framework/src/index.ts @@ -3,3 +3,4 @@ export { NovuRequestHandler, type ServeHandlerOptions } from './handler'; export { workflow } from './resources'; export type { Workflow } from './types'; export { CronExpression } from './constants'; +export { providerSchemas } from './schemas'; diff --git a/packages/shared/src/dto/events/event.interface.ts b/packages/shared/src/dto/events/event.interface.ts index 7f8aa7870b3..1848d02b583 100644 --- a/packages/shared/src/dto/events/event.interface.ts +++ b/packages/shared/src/dto/events/event.interface.ts @@ -1,4 +1,4 @@ -import { ISubscribersDefine, ITenantDefine, ITopic } from '../../types'; +import { ISubscribersDefine, ITenantDefine, ITopic, ProvidersIdEnum } from '../../types'; export type TriggerRecipientSubscriber = string | ISubscribersDefine; @@ -9,3 +9,13 @@ export type TriggerRecipients = TriggerRecipient[]; export type TriggerRecipientsPayload = TriggerRecipientSubscriber | TriggerRecipients; export type TriggerTenantContext = string | ITenantDefine; + +export type TriggerOverrides = { + steps: Record< + string, + { + providers: Record>; + } + >; + [key: string]: Record>; +}; From d80e292d45c71492fcaa5ca7f4fc90e526560719 Mon Sep 17 00:00:00 2001 From: Dima Grossman Date: Thu, 19 Dec 2024 19:08:04 +0000 Subject: [PATCH 2/5] fix: prop --- .../workflow-editor/steps/controls/array-field-template.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/dashboard/src/components/workflow-editor/steps/controls/array-field-template.tsx b/apps/dashboard/src/components/workflow-editor/steps/controls/array-field-template.tsx index f948a971768..6b50f61c346 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/controls/array-field-template.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/controls/array-field-template.tsx @@ -24,7 +24,9 @@ export function ArrayFieldTemplate(props: ArrayFieldTemplateProps) { const [isEditorOpen, setIsEditorOpen] = useState(true); - const handleAddClick = () => { + const handleAddClick = (e) => { + e.stopPropogation(); + if (!isEditorOpen) { setIsEditorOpen(true); } From 04ebc69fe4adcfb735a28464d06ee6ba0dd71939 Mon Sep 17 00:00:00 2001 From: Dima Grossman Date: Thu, 19 Dec 2024 19:11:43 +0000 Subject: [PATCH 3/5] Update select-widget.tsx --- .../components/workflow-editor/steps/controls/select-widget.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/dashboard/src/components/workflow-editor/steps/controls/select-widget.tsx b/apps/dashboard/src/components/workflow-editor/steps/controls/select-widget.tsx index 6f10d7514cd..7099d64df86 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/controls/select-widget.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/controls/select-widget.tsx @@ -23,7 +23,6 @@ export function SelectWidget(props: WidgetProps) { const { control } = useFormContext(); - console.log({ props }); return ( Date: Tue, 7 Jan 2025 13:23:45 +0200 Subject: [PATCH 4/5] fix: restore --- .../steps/controls/array-field-template.tsx | 40 +++++++--------- .../steps/controls/custom-step-controls.tsx | 8 +--- .../steps/controls/json-form.tsx | 2 - .../steps/controls/object-field-template.tsx | 42 ++++++++-------- .../steps/controls/select-widget.tsx | 3 ++ .../steps/controls/switch-widget.tsx | 3 ++ .../steps/controls/text-widget.tsx | 22 +-------- apps/dashboard/src/main.tsx | 48 +++++++++---------- 8 files changed, 71 insertions(+), 97 deletions(-) diff --git a/apps/dashboard/src/components/workflow-editor/steps/controls/array-field-template.tsx b/apps/dashboard/src/components/workflow-editor/steps/controls/array-field-template.tsx index 6b50f61c346..677cc3b2466 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/controls/array-field-template.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/controls/array-field-template.tsx @@ -24,9 +24,7 @@ export function ArrayFieldTemplate(props: ArrayFieldTemplateProps) { const [isEditorOpen, setIsEditorOpen] = useState(true); - const handleAddClick = (e) => { - e.stopPropogation(); - + const handleAddClick = () => { if (!isEditorOpen) { setIsEditorOpen(true); } @@ -54,26 +52,24 @@ export function ArrayFieldTemplate(props: ArrayFieldTemplateProps) { className="bg-background border-neutral-alpha-200 relative mt-2 flex w-full flex-col gap-2 rounded-lg border px-3 py-4 data-[state=closed]:rounded-none data-[state=closed]:border-b-0 data-[state=closed]:border-l-0 data-[state=closed]:border-r-0 data-[state=closed]:border-t data-[state=closed]:pb-0" >
- -
- - - -
- {canAdd && } -
- -
-
+
+ + + +
+ {canAdd && } + + +
- +
diff --git a/apps/dashboard/src/components/workflow-editor/steps/controls/custom-step-controls.tsx b/apps/dashboard/src/components/workflow-editor/steps/controls/custom-step-controls.tsx index b25bda8c7ee..4b1ef2d6b92 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/controls/custom-step-controls.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/controls/custom-step-controls.tsx @@ -147,13 +147,7 @@ export const CustomStepControls = (props: CustomStepControlsProps) => {
- { - saveForm(); - }} - /> +
diff --git a/apps/dashboard/src/components/workflow-editor/steps/controls/json-form.tsx b/apps/dashboard/src/components/workflow-editor/steps/controls/json-form.tsx index b0de2f4c79e..3743adadeee 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/controls/json-form.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/controls/json-form.tsx @@ -6,7 +6,6 @@ import { ArrayFieldTitleTemplate } from './array-field-title-template'; import { AddButton, RemoveButton } from './button-templates'; import { ObjectFieldTemplate } from './object-field-template'; import { JSON_SCHEMA_FORM_ID_DELIMITER, UI_SCHEMA, WIDGETS } from './template-utils'; -import { DescriptionField } from '../description-field'; type JsonFormProps = Pick< FormProps, @@ -26,7 +25,6 @@ export function JsonForm(props: JsonFormProps) { autoComplete="false" idSeparator={JSON_SCHEMA_FORM_ID_DELIMITER} templates={{ - DescriptionFieldTemplate: DescriptionField as any, ButtonTemplates: { AddButton, RemoveButton, diff --git a/apps/dashboard/src/components/workflow-editor/steps/controls/object-field-template.tsx b/apps/dashboard/src/components/workflow-editor/steps/controls/object-field-template.tsx index bf2883dde30..466b4342df6 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/controls/object-field-template.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/controls/object-field-template.tsx @@ -38,29 +38,27 @@ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) { onOpenChange={setIsEditorOpen} className="bg-background border-neutral-alpha-200 relative mt-2 flex w-full flex-col gap-2 border-t px-3 py-4 pb-0" > -
- -
- - - -
-
- -
-
+
+
+ + + +
+ + +
- +
diff --git a/apps/dashboard/src/components/workflow-editor/steps/controls/select-widget.tsx b/apps/dashboard/src/components/workflow-editor/steps/controls/select-widget.tsx index 7099d64df86..6bd96ba7473 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/controls/select-widget.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/controls/select-widget.tsx @@ -4,6 +4,7 @@ import { useFormContext } from 'react-hook-form'; import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/primitives/form/form'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/primitives/select'; import { capitalize } from '@/utils/string'; +import { useSaveForm } from '@/components/workflow-editor/steps/save-form-context'; import { getFieldName } from './template-utils'; export function SelectWidget(props: WidgetProps) { @@ -22,6 +23,7 @@ export function SelectWidget(props: WidgetProps) { const extractedName = useMemo(() => getFieldName(id), [id]); const { control } = useFormContext(); + const { saveForm } = useSaveForm(); return ( { field.onChange(value); + saveForm(); }} disabled={disabled || readonly} required={required} diff --git a/apps/dashboard/src/components/workflow-editor/steps/controls/switch-widget.tsx b/apps/dashboard/src/components/workflow-editor/steps/controls/switch-widget.tsx index bf92133a00f..276a4674363 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/controls/switch-widget.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/controls/switch-widget.tsx @@ -4,11 +4,13 @@ import { useFormContext } from 'react-hook-form'; import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/primitives/form/form'; import { Switch } from '@/components/primitives/switch'; import { capitalize } from '@/utils/string'; +import { useSaveForm } from '@/components/workflow-editor/steps/save-form-context'; import { getFieldName } from './template-utils'; export function SwitchWidget(props: WidgetProps) { const { label, readonly, disabled, required, id } = props; const { control } = useFormContext(); + const { saveForm } = useSaveForm(); const extractedName = useMemo(() => getFieldName(id), [id]); return ( @@ -24,6 +26,7 @@ export function SwitchWidget(props: WidgetProps) { checked={field.value} onCheckedChange={(value) => { field.onChange(value); + saveForm(); }} disabled={readonly || disabled} required={required} diff --git a/apps/dashboard/src/components/workflow-editor/steps/controls/text-widget.tsx b/apps/dashboard/src/components/workflow-editor/steps/controls/text-widget.tsx index b5bb6c8eb3a..60ca29b1b43 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/controls/text-widget.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/controls/text-widget.tsx @@ -11,12 +11,9 @@ import { EditorView } from '@uiw/react-codemirror'; import { useMemo } from 'react'; import { useFormContext } from 'react-hook-form'; import { getFieldName } from './template-utils'; -import { RiInformation2Line } from 'react-icons/ri'; -import { Tooltip, TooltipContent, TooltipTrigger } from '@radix-ui/react-tooltip'; -import { cn } from '../../../../utils/ui'; export function TextWidget(props: WidgetProps) { - const { label, readonly, disabled, schema, id, required } = props; + const { label, readonly, disabled, id, required } = props; const { control } = useFormContext(); const { step } = useWorkflow(); const variables = useMemo(() => (step ? parseStepVariablesToLiquidVariables(step.variables) : []), [step]); @@ -34,22 +31,7 @@ export function TextWidget(props: WidgetProps) { name={extractedName} render={({ field }) => ( - - {capitalize(label)} - - {schema.description ? ( - - - - - - - -

{schema.description}

-
-
- ) : null} -
+ {capitalize(label)} {isNumberType ? ( diff --git a/apps/dashboard/src/main.tsx b/apps/dashboard/src/main.tsx index 0cf2e20ab23..a408299900a 100644 --- a/apps/dashboard/src/main.tsx +++ b/apps/dashboard/src/main.tsx @@ -1,39 +1,39 @@ +import { StrictMode } from 'react'; +import { createBrowserRouter, RouterProvider } from 'react-router-dom'; +import { createRoot } from 'react-dom/client'; import ErrorPage from '@/components/error-page'; -import { ConfigureWorkflow } from '@/components/workflow-editor/configure-workflow'; -import { ConfigureStep } from '@/components/workflow-editor/steps/configure-step'; -import { ConfigureStepTemplate } from '@/components/workflow-editor/steps/configure-step-template'; +import { RootRoute, AuthRoute, DashboardRoute, CatchAllRoute } from './routes'; +import { OnboardingParentRoute } from './routes/onboarding'; import { - ActivityFeed, - ApiKeysPage, - IntegrationsListPage, - OrganizationListPage, - QuestionnairePage, - SettingsPage, + WorkflowsPage, SignInPage, SignUpPage, + OrganizationListPage, + QuestionnairePage, UsecaseSelectPage, + ApiKeysPage, WelcomePage, - WorkflowsPage, + IntegrationsListPage, + SettingsPage, + ActivityFeed, } from '@/pages'; -import { StrictMode } from 'react'; -import { createRoot } from 'react-dom/client'; -import { createBrowserRouter, RouterProvider } from 'react-router-dom'; -import { CreateIntegrationSidebar } from './components/integrations/components/create-integration-sidebar'; -import { UpdateIntegrationSidebar } from './components/integrations/components/update-integration-sidebar'; -import { ChannelPreferences } from './components/workflow-editor/channel-preferences'; -import { FeatureFlagsProvider } from './context/feature-flags-provider'; import './index.css'; +import { ROUTES } from './utils/routes'; import { EditWorkflowPage } from './pages/edit-workflow'; -import { InboxEmbedPage } from './pages/inbox-embed-page'; -import { InboxEmbedSuccessPage } from './pages/inbox-embed-success-page'; -import { InboxUsecasePage } from './pages/inbox-usecase-page'; -import { RedirectToLegacyStudioAuth } from './pages/redirect-to-legacy-studio-auth'; import { TestWorkflowPage } from './pages/test-workflow'; -import { AuthRoute, CatchAllRoute, DashboardRoute, RootRoute } from './routes'; -import { OnboardingParentRoute } from './routes/onboarding'; -import { ROUTES } from './utils/routes'; import { initializeSentry } from './utils/sentry'; import { overrideZodErrorMap } from './utils/validation'; +import { InboxUsecasePage } from './pages/inbox-usecase-page'; +import { InboxEmbedPage } from './pages/inbox-embed-page'; +import { ConfigureWorkflow } from '@/components/workflow-editor/configure-workflow'; +import { InboxEmbedSuccessPage } from './pages/inbox-embed-success-page'; +import { ChannelPreferences } from './components/workflow-editor/channel-preferences'; +import { FeatureFlagsProvider } from './context/feature-flags-provider'; +import { ConfigureStep } from '@/components/workflow-editor/steps/configure-step'; +import { ConfigureStepTemplate } from '@/components/workflow-editor/steps/configure-step-template'; +import { RedirectToLegacyStudioAuth } from './pages/redirect-to-legacy-studio-auth'; +import { CreateIntegrationSidebar } from './components/integrations/components/create-integration-sidebar'; +import { UpdateIntegrationSidebar } from './components/integrations/components/update-integration-sidebar'; initializeSentry(); overrideZodErrorMap(); From c02d8ebe84e3eb30c6e4f4194172f438f8a118ce Mon Sep 17 00:00:00 2001 From: Dima Grossman Date: Tue, 7 Jan 2025 13:24:50 +0200 Subject: [PATCH 5/5] fix: --- .../steps/controls/provider-controls.tsx | 102 ------------------ .../steps/description-field.tsx | 5 - 2 files changed, 107 deletions(-) delete mode 100644 apps/dashboard/src/components/workflow-editor/steps/controls/provider-controls.tsx delete mode 100644 apps/dashboard/src/components/workflow-editor/steps/description-field.tsx diff --git a/apps/dashboard/src/components/workflow-editor/steps/controls/provider-controls.tsx b/apps/dashboard/src/components/workflow-editor/steps/controls/provider-controls.tsx deleted file mode 100644 index 35a76f4437c..00000000000 --- a/apps/dashboard/src/components/workflow-editor/steps/controls/provider-controls.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { providerSchemas } from '@novu/framework'; -import { ChannelTypeEnum, EmailProviderIdEnum } from '@novu/shared'; -import { useMemo } from 'react'; -import { JsonForm } from './json-form'; -import { RJSFSchema } from '@rjsf/utils'; -import { Accordion } from '@radix-ui/react-accordion'; -import { cn } from '../../../../utils/ui'; -import { AccordionContent, AccordionItem, AccordionTrigger } from '../../../primitives/accordion'; -import { RiInputField } from 'react-icons/ri'; -import { Form } from '../../../primitives/form/form'; -import { useForm } from 'react-hook-form'; -import { TooltipProvider } from '@radix-ui/react-tooltip'; - -export function ProviderControl() { - const { isLoading, integrations } = { - isLoading: false, - integrations: [ - { - _id: '1', - providerId: 'sendgrid', - name: 'SendGrid', - channel: ChannelTypeEnum.EMAIL, - }, - { - _id: '2', - providerId: 'mailgun', - name: 'MailGun', - channel: ChannelTypeEnum.EMAIL, - }, - { - _id: '3', - providerId: 'slack', - name: 'Slack', - channel: ChannelTypeEnum.CHAT, - }, - ], - }; - - const schemas = useMemo(() => { - return integrations - ?.filter((item) => { - return item.providerId !== 'novu-email' && item.providerId !== 'novu-sms'; - }) - .map((item) => { - return { - schema: providerSchemas[item.channel]?.[item?.providerId as EmailProviderIdEnum]?.output, - integration: item, - }; - }); - }, [integrations]); - - if (isLoading) return null; - - return ( - -
- - {schemas?.map((item) => { - return ; - })} - -
-
- ); -} - -function ProviderAccordion({ schema, integration }: { schema: any; integration: any }) { - const form = useForm(); - - return ( -
- { - console.log(values); - })} - > - - -
- - {integration.name} overrides -
-
- - -
- ; -
-
-
-
- - ); -} diff --git a/apps/dashboard/src/components/workflow-editor/steps/description-field.tsx b/apps/dashboard/src/components/workflow-editor/steps/description-field.tsx deleted file mode 100644 index 4e1ee14c557..00000000000 --- a/apps/dashboard/src/components/workflow-editor/steps/description-field.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { RJSFSchema, type WidgetProps } from '@rjsf/utils'; - -export function DescriptionField(props: WidgetProps) { - return <>; -}