From f63091944c9f7bb0d2445be53cfe2fbb37b1bd9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Tymczuk?= Date: Tue, 19 Nov 2024 17:21:35 +0100 Subject: [PATCH] fix(dashboard): workflow editor name change updates the workflow slug in the url (#7064) --- .../workflow-editor/configure-workflow.tsx | 43 +++++++++++++++++-- .../steps/step-editor-provider.tsx | 9 +++- .../workflow-editor/steps/use-step.tsx | 8 ++-- apps/dashboard/src/hooks/use-fetch-step.tsx | 6 ++- .../dashboard/src/hooks/use-fetch-workflow.ts | 9 +++- .../src/hooks/use-update-workflow.ts | 14 ++++-- apps/dashboard/src/utils/step.ts | 7 +-- 7 files changed, 78 insertions(+), 18 deletions(-) diff --git a/apps/dashboard/src/components/workflow-editor/configure-workflow.tsx b/apps/dashboard/src/components/workflow-editor/configure-workflow.tsx index 4a7413c63b8..233d25de29b 100644 --- a/apps/dashboard/src/components/workflow-editor/configure-workflow.tsx +++ b/apps/dashboard/src/components/workflow-editor/configure-workflow.tsx @@ -1,7 +1,10 @@ -import { useState } from 'react'; -import * as z from 'zod'; import { useFormContext } from 'react-hook-form'; import { motion } from 'framer-motion'; +import { useLayoutEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import * as z from 'zod'; +// import { RiArrowRightSLine, RiSettingsLine } from 'react-icons/ri'; + import { RouteFill } from '../icons'; import { Input, InputField } from '../primitives/input'; import { Separator } from '../primitives/separator'; @@ -18,14 +21,40 @@ import { SidebarContent, SidebarHeader } from '@/components/side-navigation/Side import { PageMeta } from '../page-meta'; import { ConfirmationModal } from '../confirmation-modal'; import { PAUSE_MODAL_DESCRIPTION, PAUSE_MODAL_TITLE } from '@/utils/constants'; +import { buildRoute, ROUTES } from '@/utils/routes'; +import { useEnvironment } from '@/context/environment/hooks'; export function ConfigureWorkflow() { const [isPauseModalOpen, setIsPauseModalOpen] = useState(false); const tagsQuery = useTagsQuery(); - const { isReadOnly } = useWorkflowEditorContext(); + const { isReadOnly, workflow } = useWorkflowEditorContext(); + const { currentEnvironment } = useEnvironment(); + const { workflowSlug } = useParams<{ workflowSlug: string }>(); + const navigate = useNavigate(); + const [isBlurred, setIsBlurred] = useState(false); const { control, watch, setValue } = useFormContext>(); const workflowName = watch('name'); + const isWorkflowSlugChanged = workflow && workflow?.slug && workflowSlug !== workflow?.slug; + const shouldUpdateWorkflowSlug = isBlurred && isWorkflowSlugChanged; + + useLayoutEffect(() => { + if (shouldUpdateWorkflowSlug) { + setTimeout(() => { + navigate( + buildRoute(ROUTES.EDIT_WORKFLOW, { + environmentSlug: currentEnvironment?.slug ?? '', + workflowSlug: workflow?.slug ?? '', + }), + { + replace: true, + state: { skipAnimation: true }, + } + ); + }, 0); + setIsBlurred(false); + } + }, [shouldUpdateWorkflowSlug, workflow?.slug, currentEnvironment?.slug, navigate]); const onPauseWorkflow = () => { setValue('active', false, { shouldValidate: true, shouldDirty: true }); @@ -100,7 +129,13 @@ export function ConfigureWorkflow() { Workflow Name - + setIsBlurred(false)} + onBlur={() => setIsBlurred(true)} + /> diff --git a/apps/dashboard/src/components/workflow-editor/steps/step-editor-provider.tsx b/apps/dashboard/src/components/workflow-editor/steps/step-editor-provider.tsx index cf3dd602998..02ce9b80183 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/step-editor-provider.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/step-editor-provider.tsx @@ -5,7 +5,7 @@ import { StepTypeEnum } from '@novu/shared'; import { StepEditorContext } from './step-editor-context'; import { useFetchStep } from '@/hooks/use-fetch-step'; import { useWorkflowEditorContext } from '../hooks'; -import { getStepBase62Id } from '@/utils/step'; +import { getEncodedId, STEP_DIVIDER } from '@/utils/step'; export const StepEditorProvider = ({ children }: { children: ReactNode }) => { const { workflowSlug = '', stepSlug = '' } = useParams<{ @@ -27,7 +27,12 @@ export const StepEditorProvider = ({ children }: { children: ReactNode }) => { const navigationStepType = state?.stepType as StepTypeEnum | undefined; const stepType = useMemo( () => - navigationStepType ?? workflow?.steps.find((el) => getStepBase62Id(el.slug) === getStepBase62Id(stepSlug))?.type, + navigationStepType ?? + workflow?.steps.find( + (el) => + getEncodedId({ slug: el.slug, divider: STEP_DIVIDER }) === + getEncodedId({ slug: stepSlug, divider: STEP_DIVIDER }) + )?.type, [navigationStepType, stepSlug, workflow] ); diff --git a/apps/dashboard/src/components/workflow-editor/steps/use-step.tsx b/apps/dashboard/src/components/workflow-editor/steps/use-step.tsx index 65770b23d4e..cc1a3c5f347 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/use-step.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/use-step.tsx @@ -3,7 +3,7 @@ import { useFormContext, useWatch } from 'react-hook-form'; import { useParams } from 'react-router-dom'; import * as z from 'zod'; import { workflowSchema } from '../schema'; -import { getStepBase62Id } from '@/utils/step'; +import { getEncodedId, STEP_DIVIDER } from '@/utils/step'; export const useStep = () => { const { stepSlug = '' } = useParams<{ @@ -12,18 +12,18 @@ export const useStep = () => { const { control } = useFormContext>(); const steps = useWatch({ name: 'steps', control }); - const base62Id = getStepBase62Id(stepSlug); + const base62Id = getEncodedId({ slug: stepSlug, divider: STEP_DIVIDER }); const step = useMemo(() => { if (Array.isArray(steps)) { - return steps.find((el) => getStepBase62Id(el.slug) === base62Id); + return steps.find((el) => getEncodedId({ slug: el.slug, divider: STEP_DIVIDER }) === base62Id); } return undefined; }, [base62Id, steps]); const stepIndex = useMemo(() => { if (Array.isArray(steps)) { - return steps.findIndex((el) => getStepBase62Id(el.slug) === base62Id); + return steps.findIndex((el) => getEncodedId({ slug: el.slug, divider: STEP_DIVIDER }) === base62Id); } return -1; }, [base62Id, steps]); diff --git a/apps/dashboard/src/hooks/use-fetch-step.tsx b/apps/dashboard/src/hooks/use-fetch-step.tsx index d8a19eabe0b..36e0bc4bc5e 100644 --- a/apps/dashboard/src/hooks/use-fetch-step.tsx +++ b/apps/dashboard/src/hooks/use-fetch-step.tsx @@ -1,13 +1,17 @@ +import { useMemo } from 'react'; import { useQuery } from '@tanstack/react-query'; import type { StepDataDto } from '@novu/shared'; import { QueryKeys } from '@/utils/query-keys'; import { useEnvironment } from '@/context/environment/hooks'; import { fetchStep } from '@/api/steps'; +import { getEncodedId, STEP_DIVIDER } from '@/utils/step'; export const useFetchStep = ({ workflowSlug, stepSlug }: { workflowSlug: string; stepSlug: string }) => { const { currentEnvironment } = useEnvironment(); + const stepId = useMemo(() => getEncodedId({ slug: stepSlug, divider: STEP_DIVIDER }), [stepSlug]); + const { data, isPending, isRefetching, error, refetch } = useQuery({ - queryKey: [QueryKeys.fetchWorkflow, currentEnvironment?._id, workflowSlug, stepSlug], + queryKey: [QueryKeys.fetchWorkflow, currentEnvironment?._id, workflowSlug, stepId], queryFn: () => fetchStep({ workflowSlug, stepSlug }), enabled: !!currentEnvironment?._id && !!stepSlug, }); diff --git a/apps/dashboard/src/hooks/use-fetch-workflow.ts b/apps/dashboard/src/hooks/use-fetch-workflow.ts index 66085ff717e..8de30dd3128 100644 --- a/apps/dashboard/src/hooks/use-fetch-workflow.ts +++ b/apps/dashboard/src/hooks/use-fetch-workflow.ts @@ -1,13 +1,20 @@ +import { useMemo } from 'react'; import { useQuery } from '@tanstack/react-query'; import type { WorkflowResponseDto } from '@novu/shared'; import { QueryKeys } from '@/utils/query-keys'; import { fetchWorkflow } from '@/api/workflows'; import { useEnvironment } from '@/context/environment/hooks'; +import { getEncodedId, WORKFLOW_DIVIDER } from '@/utils/step'; export const useFetchWorkflow = ({ workflowSlug }: { workflowSlug?: string }) => { const { currentEnvironment } = useEnvironment(); + const workflowId = useMemo( + () => getEncodedId({ slug: workflowSlug ?? '', divider: WORKFLOW_DIVIDER }), + [workflowSlug] + ); + const { data, isPending, error } = useQuery({ - queryKey: [QueryKeys.fetchWorkflow, currentEnvironment?._id, workflowSlug], + queryKey: [QueryKeys.fetchWorkflow, currentEnvironment?._id, workflowId], queryFn: () => fetchWorkflow({ workflowSlug }), enabled: !!currentEnvironment?._id && !!workflowSlug, }); diff --git a/apps/dashboard/src/hooks/use-update-workflow.ts b/apps/dashboard/src/hooks/use-update-workflow.ts index d5b8924ff34..1ecfe2b3efb 100644 --- a/apps/dashboard/src/hooks/use-update-workflow.ts +++ b/apps/dashboard/src/hooks/use-update-workflow.ts @@ -1,8 +1,9 @@ +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'; +import type { WorkflowResponseDto } from '@novu/shared'; import { updateWorkflow } from '@/api/workflows'; import { useEnvironment } from '@/context/environment/hooks'; import { QueryKeys } from '@/utils/query-keys'; -import type { WorkflowResponseDto } from '@novu/shared'; -import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'; +import { getEncodedId, WORKFLOW_DIVIDER } from '@/utils/step'; export const useUpdateWorkflow = ( options?: UseMutationOptions[0]> @@ -14,7 +15,14 @@ export const useUpdateWorkflow = ( mutationFn: updateWorkflow, ...options, onSuccess: async (data, variables, context) => { - await queryClient.setQueryData([QueryKeys.fetchWorkflow, currentEnvironment?._id, data.slug], data); + await queryClient.setQueryData( + [ + QueryKeys.fetchWorkflow, + currentEnvironment?._id, + getEncodedId({ slug: data.slug, divider: WORKFLOW_DIVIDER }), + ], + data + ); options?.onSuccess?.(data, variables, context); }, }); diff --git a/apps/dashboard/src/utils/step.ts b/apps/dashboard/src/utils/step.ts index 0407420a987..c54634e140a 100644 --- a/apps/dashboard/src/utils/step.ts +++ b/apps/dashboard/src/utils/step.ts @@ -1,8 +1,9 @@ -import { ShortIsPrefixEnum, StepResponseDto } from '@novu/shared'; +import { ShortIsPrefixEnum } from '@novu/shared'; -const divider = `_${ShortIsPrefixEnum.STEP}`; +export const WORKFLOW_DIVIDER = `_${ShortIsPrefixEnum.WORKFLOW}`; +export const STEP_DIVIDER = `_${ShortIsPrefixEnum.STEP}`; -export const getStepBase62Id = (slug: StepResponseDto['slug'] | string = divider) => { +export const getEncodedId = ({ slug, divider }: { slug: string; divider: string }) => { const parts = slug.split(divider); return parts[parts.length - 1]; };