Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dashboard): Open step template drawer on Add Step #7406

Open
wants to merge 1 commit into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { ChannelTypeEnum, UpdateWorkflowDto, WorkflowPreferences, WorkflowResponseDto } from '@novu/shared';
import { ChannelTypeEnum, WorkflowPreferences, WorkflowResponseDto } from '@novu/shared';
import { motion } from 'motion/react';
import { useMemo } from 'react';
import { useForm, useWatch } from 'react-hook-form';
Expand All @@ -9,6 +9,7 @@ import { z } from 'zod';

import { SidebarContent, SidebarHeader } from '@/components/side-navigation/sidebar';
import { UserPreferencesFormSchema } from '@/components/workflow-editor/schema';
import { UpdateWorkflowFn } from '@/components/workflow-editor/workflow-provider';
import { useTelemetry } from '@/hooks/use-telemetry';
import { STEP_TYPE_TO_COLOR } from '@/utils/color';
import { StepTypeEnum, WorkflowOriginEnum } from '@/utils/enums';
Expand All @@ -28,7 +29,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '../primitives/tooltip';

type ConfigureWorkflowFormProps = {
workflow: WorkflowResponseDto;
update: (data: UpdateWorkflowDto) => void;
update: UpdateWorkflowFn;
};

const CHANNEL_LABELS_LOOKUP: Record<`${ChannelTypeEnum}` | 'all', string> = {
Expand Down Expand Up @@ -87,10 +88,12 @@ export const ChannelPreferencesForm = (props: ConfigureWorkflowFormProps) => {

const updateUserPreference = (userPreferences: WorkflowPreferences | null) => {
update({
...workflow,
preferences: {
...workflow.preferences,
user: userPreferences,
data: {
...workflow,
preferences: {
...workflow.preferences,
user: userPreferences,
},
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ToastIcon } from '@/components/primitives/sonner';
import { showToast } from '@/components/primitives/sonner-helpers';
import { SidebarContent, SidebarHeader } from '@/components/side-navigation/sidebar';
import { MAX_DESCRIPTION_LENGTH, workflowSchema } from '@/components/workflow-editor/schema';
import { UpdateWorkflowFn } from '@/components/workflow-editor/workflow-provider';
import { useEnvironment } from '@/context/environment/hooks';
import { useDeleteWorkflow } from '@/hooks/use-delete-workflow';
import { useFormAutosave } from '@/hooks/use-form-autosave';
Expand All @@ -26,7 +27,7 @@ import { useTags } from '@/hooks/use-tags';
import { ROUTES } from '@/utils/routes';
import { cn } from '@/utils/ui';
import { zodResolver } from '@hookform/resolvers/zod';
import { UpdateWorkflowDto, WorkflowOriginEnum, WorkflowResponseDto } from '@novu/shared';
import { WorkflowOriginEnum, WorkflowResponseDto } from '@novu/shared';
import {
RiArrowRightSLine,
RiCodeSSlashLine,
Expand Down Expand Up @@ -55,7 +56,7 @@ import { usePromotionalBanner } from '../promotional/coming-soon-banner';

type ConfigureWorkflowFormProps = {
workflow: WorkflowResponseDto;
update: (data: UpdateWorkflowDto) => void;
update: UpdateWorkflowFn;
};

const toastOptions: ExternalToast = {
Expand Down Expand Up @@ -133,7 +134,7 @@ export const ConfigureWorkflowForm = (props: ConfigureWorkflowFormProps) => {
});

const { onBlur, saveForm } = useFormAutosave({
previousData: workflow,
previousData: { data: workflow },
form,
isReadOnly,
save: update,
Expand Down
28 changes: 22 additions & 6 deletions apps/dashboard/src/components/workflow-editor/edges.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { BaseEdge, Edge, EdgeLabelRenderer, EdgeProps, getBezierPath } from '@xyflow/react';
import { AddStepMenu } from './add-step-menu';
import { createStep } from '@/components/workflow-editor/step-utils';
import { useWorkflow } from '@/components/workflow-editor/workflow-provider';
import { AUTO_OPEN_DRAWER_AFTER_CREATION_STEP_TYPES } from '@/utils/constants';
import { buildRoute, ROUTES } from '@/utils/routes';
import { WorkflowOriginEnum } from '@novu/shared';
import { createStep } from '@/components/workflow-editor/step-utils';
import { BaseEdge, Edge, EdgeLabelRenderer, EdgeProps, getBezierPath } from '@xyflow/react';
import { useNavigate } from 'react-router-dom';
import { AddStepMenu } from './add-step-menu';

export type AddNodeEdgeType = Edge<{ isLast: boolean; addStepIndex: number }>;

Expand All @@ -18,6 +21,7 @@ export function AddNodeEdge({
markerEnd,
}: EdgeProps<AddNodeEdgeType>) {
const { workflow, update } = useWorkflow();
const navigate = useNavigate();
const isReadOnly = workflow?.origin === WorkflowOriginEnum.EXTERNAL;

const [edgePath, labelX, labelY] = getBezierPath({
Expand Down Expand Up @@ -47,7 +51,7 @@ export function AddNodeEdge({
>
{!isReadOnly && (
<AddStepMenu
onMenuItemClick={(stepType) => {
onMenuItemClick={async (stepType) => {
if (workflow) {
const indexToAdd = data.addStepIndex;

Expand All @@ -60,8 +64,20 @@ export function AddNodeEdge({
];

update({
...workflow,
steps: updatedSteps,
data: {
...workflow,
steps: updatedSteps,
},
onSuccess: (data) => {
if (AUTO_OPEN_DRAWER_AFTER_CREATION_STEP_TYPES.includes(stepType)) {
navigate(
buildRoute(ROUTES.EDIT_STEP_TEMPLATE, {
workflowSlug: workflow.slug,
stepSlug: data.steps[indexToAdd].slug,
})
);
}
},
});
}
}}
Expand Down
19 changes: 17 additions & 2 deletions apps/dashboard/src/components/workflow-editor/nodes.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { createStep } from '@/components/workflow-editor/step-utils';
import { useWorkflow } from '@/components/workflow-editor/workflow-provider';
import { STEP_TYPE_TO_COLOR } from '@/utils/color';
import { AUTO_OPEN_DRAWER_AFTER_CREATION_STEP_TYPES } from '@/utils/constants';
import { StepTypeEnum } from '@/utils/enums';
import { buildRoute, ROUTES } from '@/utils/routes';
import { getWorkflowIdFromSlug, STEP_DIVIDER } from '@/utils/step';
import { cn } from '@/utils/ui';
import { WorkflowOriginEnum } from '@novu/shared';
import { Node as FlowNode, Handle, NodeProps, Position } from '@xyflow/react';
import { steps } from 'motion/react';
import { ComponentProps } from 'react';
import { RiPlayCircleLine } from 'react-icons/ri';
import { Link, useParams } from 'react-router-dom';
import { Link, useNavigate, useParams } from 'react-router-dom';
import { STEP_TYPE_TO_ICON } from '../icons/utils';
import { AddStepMenu } from './add-step-menu';
import { Node, NodeBody, NodeError, NodeHeader, NodeIcon, NodeName } from './base-node';
Expand Down Expand Up @@ -242,6 +244,7 @@ export const CustomNode = (props: NodeProps<NodeType>) => {

export const AddNode = (_props: NodeProps<NodeType>) => {
const { workflow, update } = useWorkflow();
const navigate = useNavigate();
if (!workflow) {
return null;
}
Expand All @@ -257,7 +260,19 @@ export const AddNode = (_props: NodeProps<NodeType>) => {
<AddStepMenu
visible
onMenuItemClick={(stepType) => {
update({ ...workflow, steps: [...workflow.steps, createStep(stepType)] });
update({
data: { ...workflow, steps: [...workflow.steps, createStep(stepType)] },
onSuccess: (data) => {
if (AUTO_OPEN_DRAWER_AFTER_CREATION_STEP_TYPES.includes(stepType)) {
navigate(
buildRoute(ROUTES.EDIT_STEP_TEMPLATE, {
workflowSlug: workflow.slug,
stepSlug: data.steps[steps.length - 1].slug,
})
);
}
},
});
}}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
StepDataDto,
StepTypeEnum,
StepUpdateDto,
UpdateWorkflowDto,
WorkflowOriginEnum,
WorkflowResponseDto,
} from '@novu/shared';
Expand Down Expand Up @@ -39,6 +38,7 @@ import { ConfigurePushStepPreview } from '@/components/workflow-editor/steps/pus
import { SaveFormContext } from '@/components/workflow-editor/steps/save-form-context';
import { SdkBanner } from '@/components/workflow-editor/steps/sdk-banner';
import { ConfigureSmsStepPreview } from '@/components/workflow-editor/steps/sms/configure-sms-step-preview';
import { UpdateWorkflowFn } from '@/components/workflow-editor/workflow-provider';
import { useFormAutosave } from '@/hooks/use-form-autosave';
import {
AUTOCOMPLETE_PASSWORD_MANAGERS_OFF,
Expand Down Expand Up @@ -77,7 +77,7 @@ type ConfigureStepFormProps = {
workflow: WorkflowResponseDto;
environment: IEnvironment;
step: StepDataDto;
update: (data: UpdateWorkflowDto) => void;
update: UpdateWorkflowFn;
};

export const ConfigureStepForm = (props: ConfigureStepFormProps) => {
Expand All @@ -104,7 +104,7 @@ export const ConfigureStepForm = (props: ConfigureStepFormProps) => {
const isInlineConfigurableStepWithCustomControls = isInlineConfigurableStep && hasCustomControls;

const onDeleteStep = () => {
update({ ...workflow, steps: workflow.steps.filter((s) => s._id !== step._id) });
update({ data: { ...workflow, steps: workflow.steps.filter((s) => s._id !== step._id) } });
navigate(buildRoute(ROUTES.EDIT_WORKFLOW, { environmentSlug: environment.slug!, workflowSlug: workflow.slug }));
};

Expand Down Expand Up @@ -144,7 +144,7 @@ export const ConfigureStepForm = (props: ConfigureStepFormProps) => {
name: data.name,
...(data.controlValues ? { controlValues: data.controlValues } : {}),
};
update(updateStepInWorkflow(workflow, step.stepId, updateStepData));
update({ data: updateStepInWorkflow(workflow, step.stepId, updateStepData) });
},
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
import { type StepDataDto, StepTypeEnum, StepUpdateDto, type WorkflowResponseDto } from '@novu/shared';
import { useCallback, useEffect, useMemo } from 'react';
import { useForm } from 'react-hook-form';
import {
type StepDataDto,
StepTypeEnum,
StepUpdateDto,
UpdateWorkflowDto,
type WorkflowResponseDto,
} from '@novu/shared';

import { flattenIssues, updateStepInWorkflow } from '@/components/workflow-editor/step-utils';
import { Form } from '@/components/primitives/form/form';
import { EmailTabs } from '@/components/workflow-editor/steps/email/email-tabs';
import { getStepDefaultValues } from '@/components/workflow-editor/step-default-values';
import { flattenIssues, updateStepInWorkflow } from '@/components/workflow-editor/step-utils';
import { ChatTabs } from '@/components/workflow-editor/steps/chat/chat-tabs';
import { CommonCustomControlValues } from '@/components/workflow-editor/steps/common/common-custom-control-values';
import { EmailTabs } from '@/components/workflow-editor/steps/email/email-tabs';
import { InAppTabs } from '@/components/workflow-editor/steps/in-app/in-app-tabs';
import { PushTabs } from '@/components/workflow-editor/steps/push/push-tabs';
import { SaveFormContext } from '@/components/workflow-editor/steps/save-form-context';
import { SmsTabs } from '@/components/workflow-editor/steps/sms/sms-tabs';
import { ChatTabs } from '@/components/workflow-editor/steps/chat/chat-tabs';
import { UpdateWorkflowFn } from '@/components/workflow-editor/workflow-provider';
import { useFormAutosave } from '@/hooks/use-form-autosave';
import { CommonCustomControlValues } from '@/components/workflow-editor/steps/common/common-custom-control-values';

const STEP_TYPE_TO_TEMPLATE_FORM: Record<StepTypeEnum, (args: StepEditorProps) => React.JSX.Element | null> = {
[StepTypeEnum.EMAIL]: EmailTabs,
Expand All @@ -38,7 +33,7 @@ export type StepEditorProps = {
};

type ConfigureStepTemplateFormProps = StepEditorProps & {
update: (data: UpdateWorkflowDto) => void;
update: UpdateWorkflowFn;
};

export const ConfigureStepTemplateForm = (props: ConfigureStepTemplateFormProps) => {
Expand All @@ -58,7 +53,7 @@ export const ConfigureStepTemplateForm = (props: ConfigureStepTemplateFormProps)
const updateStepData: Partial<StepUpdateDto> = {
controlValues: data,
};
update(updateStepInWorkflow(workflow, step.stepId, updateStepData));
update({ data: updateStepInWorkflow(workflow, step.stepId, updateStepData) });
},
});

Expand Down
44 changes: 27 additions & 17 deletions apps/dashboard/src/components/workflow-editor/workflow-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,39 @@ import { PatchWorkflowDto, StepDataDto, UpdateWorkflowDto, WorkflowResponseDto }
import { createContext, ReactNode, useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react';
import { useBlocker, useNavigate, useParams } from 'react-router-dom';

import { useEnvironment } from '@/context/environment/hooks';
import { useFetchWorkflow } from '@/hooks/use-fetch-workflow';
import { useUpdateWorkflow } from '@/hooks/use-update-workflow';
import { usePatchWorkflow } from '@/hooks/use-patch-workflow';
import { createContextHook } from '@/utils/context';
import { buildRoute, ROUTES } from '@/utils/routes';
import { toast } from 'sonner';
import { RiCloseFill } from 'react-icons/ri';
import {
AlertDialog,
AlertDialogHeader,
AlertDialogContent,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/primitives/alert-dialog';
import { RiAlertFill } from 'react-icons/ri';
import { CheckCircleIcon } from 'lucide-react';
import { useEnvironment } from '@/context/environment/hooks';
import { useFetchWorkflow } from '@/hooks/use-fetch-workflow';
import { useInvocationQueue } from '@/hooks/use-invocation-queue';
import { usePatchWorkflow } from '@/hooks/use-patch-workflow';
import { useUpdateWorkflow } from '@/hooks/use-update-workflow';
import { createContextHook } from '@/utils/context';
import { buildRoute, ROUTES } from '@/utils/routes';
import { getWorkflowIdFromSlug, STEP_DIVIDER } from '@/utils/step';
import { CheckCircleIcon } from 'lucide-react';
import { RiAlertFill, RiCloseFill } from 'react-icons/ri';
import { toast } from 'sonner';
import { showErrorToast, showSavingToast, showSuccessToast } from './toasts';
import { STEP_DIVIDER } from '@/utils/step';
import { getWorkflowIdFromSlug } from '@/utils/step';

export type UpdateWorkflowFn = ({
data,
onSuccess,
}: {
data: UpdateWorkflowDto;
onSuccess?: (workflow: WorkflowResponseDto) => void;
}) => void;

export type WorkflowContextType = {
isPending: boolean;
workflow?: WorkflowResponseDto;
step?: StepDataDto;
update: (data: UpdateWorkflowDto) => void;
update: UpdateWorkflowFn;
patch: (data: PatchWorkflowDto) => void;
};

Expand Down Expand Up @@ -105,9 +111,13 @@ export const WorkflowProvider = ({ children }: { children: ReactNode }) => {
const isUpdatePatchPending = isPatchPending || isUpdatePending || hasPendingItems;

const update = useCallback(
(data: UpdateWorkflowDto) => {
({ data, onSuccess }: { data: UpdateWorkflowDto; onSuccess?: (workflow: WorkflowResponseDto) => void }) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be better DX to do

    (data: UpdateWorkflowDto, { onSuccess }: { onSuccess?: (workflow: WorkflowResponseDto) => void }) => {```

It's also a safer change and doesn't introduce the super generic data key across the board.

if (workflow) {
enqueue(() => updateWorkflow({ workflowSlug: workflow.slug, workflow: { ...data } }));
enqueue(async () => {
const res = await updateWorkflow({ workflowSlug: workflow.slug, workflow: { ...data } });
onSuccess?.(res);
return res;
});
}
},
[enqueue, updateWorkflow, workflow]
Expand Down
8 changes: 8 additions & 0 deletions apps/dashboard/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ export const STEP_TYPE_LABELS: Record<StepTypeEnum, string> = {
[StepTypeEnum.CUSTOM]: 'Custom',
};

export const AUTO_OPEN_DRAWER_AFTER_CREATION_STEP_TYPES: StepTypeEnum[] = [
Copy link
Contributor

Choose a reason for hiding this comment

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

Every TEMPLATE_CONFIGURABLE_STEP_TYPES should allow for the same behavior. So we don't need a separate enum for this.

Moreover, these enums have a bit of a code smell as the UI around drawers should work independently of the step types. We will get back to that soon; meanwhile, let's not add another constant array of array steps.

StepTypeEnum.EMAIL,
StepTypeEnum.SMS,
StepTypeEnum.IN_APP,
StepTypeEnum.CHAT,
StepTypeEnum.PUSH,
];

export const DEFAULT_CONTROL_DELAY_AMOUNT = 30;
export const DEFAULT_CONTROL_DELAY_UNIT = TimeUnitEnum.SECONDS;
export const DEFAULT_CONTROL_DELAY_TYPE = 'regular';
Expand Down
Loading