diff --git a/apps/api/src/app/workflows-v2/shared/schemas/digest-control.schema.ts b/apps/api/src/app/workflows-v2/shared/schemas/digest-control.schema.ts index e47a80888c8..733ffeda298 100644 --- a/apps/api/src/app/workflows-v2/shared/schemas/digest-control.schema.ts +++ b/apps/api/src/app/workflows-v2/shared/schemas/digest-control.schema.ts @@ -14,12 +14,12 @@ const digestRegularControlZodSchema = z .object({ skip: z.object({}).catchall(z.unknown()).optional(), amount: z.union([z.number().min(1), z.string().min(1)]), - unit: z.nativeEnum(TimeUnitEnum).default(TimeUnitEnum.SECONDS), + unit: z.nativeEnum(TimeUnitEnum), digestKey: z.string().optional(), lookBackWindow: z .object({ amount: z.number().min(1), - unit: z.nativeEnum(TimeUnitEnum).default(TimeUnitEnum.SECONDS), + unit: z.nativeEnum(TimeUnitEnum), }) .strict() .optional(), diff --git a/apps/dashboard/src/components/activity/activity-panel.tsx b/apps/dashboard/src/components/activity/activity-panel.tsx index 02be4804ae0..07f5032b316 100644 --- a/apps/dashboard/src/components/activity/activity-panel.tsx +++ b/apps/dashboard/src/components/activity/activity-panel.tsx @@ -1,15 +1,20 @@ +import { useEffect, useState } from 'react'; +import { useQueryClient } from '@tanstack/react-query'; import { motion } from 'motion/react'; import { RiPlayCircleLine, RiRouteFill } from 'react-icons/ri'; +import { IActivityJob, JobStatusEnum } from '@novu/shared'; + import { ActivityJobItem } from './activity-job-item'; import { InlineToast } from '../primitives/inline-toast'; import { useFetchActivity } from '@/hooks/use-fetch-activity'; import { ActivityOverview } from './components/activity-overview'; import { cn } from '../../utils/ui'; -import { IActivityJob } from '@novu/shared'; import { Skeleton } from '../primitives/skeleton'; +import { QueryKeys } from '@/utils/query-keys'; +import { useEnvironment } from '@/context/environment/hooks'; export interface ActivityPanelProps { - activityId: string; + activityId?: string; onActivitySelect: (activityId: string) => void; headerClassName?: string; overviewHeaderClassName?: string; @@ -21,7 +26,34 @@ export function ActivityPanel({ headerClassName, overviewHeaderClassName, }: ActivityPanelProps) { - const { activity, isPending, error } = useFetchActivity({ activityId }); + const queryClient = useQueryClient(); + const [shouldRefetch, setShouldRefetch] = useState(true); + const { currentEnvironment } = useEnvironment(); + const { activity, isPending, error } = useFetchActivity( + { activityId }, + { + refetchInterval: shouldRefetch ? 1000 : false, + } + ); + + useEffect(() => { + if (!activity) return; + + const isPending = activity.jobs?.some( + (job) => + job.status === JobStatusEnum.PENDING || + job.status === JobStatusEnum.QUEUED || + job.status === JobStatusEnum.RUNNING || + job.status === JobStatusEnum.DELAYED + ); + + // Only stop refetching if we have an activity and it's not pending + setShouldRefetch(isPending || !activity?.jobs?.length); + + queryClient.invalidateQueries({ + queryKey: [QueryKeys.fetchActivity, currentEnvironment?._id, activityId], + }); + }, [activity, queryClient, currentEnvironment, activityId]); if (isPending) { return ( @@ -77,7 +109,10 @@ export function ActivityPanel({ ctaClassName="text-foreground-950" variant={'tip'} ctaLabel="View Execution" - onCtaClick={() => { + onCtaClick={(e) => { + e.stopPropagation(); + e.preventDefault(); + if (activity._digestedNotificationId) { onActivitySelect(activity._digestedNotificationId); } diff --git a/apps/dashboard/src/components/primitives/inline-toast.tsx b/apps/dashboard/src/components/primitives/inline-toast.tsx index 3c791ac42ed..41ee8abc0f3 100644 --- a/apps/dashboard/src/components/primitives/inline-toast.tsx +++ b/apps/dashboard/src/components/primitives/inline-toast.tsx @@ -41,7 +41,7 @@ export interface InlineToastProps title?: string; description?: string | React.ReactNode; ctaLabel?: string; - onCtaClick?: () => void; + onCtaClick?: React.MouseEventHandler; isCtaLoading?: boolean; ctaClassName?: string; } diff --git a/apps/dashboard/src/components/workflow-editor/test-workflow/test-workflow-logs-sidebar.tsx b/apps/dashboard/src/components/workflow-editor/test-workflow/test-workflow-logs-sidebar.tsx index 0e98f0071f7..d34b817fa6e 100644 --- a/apps/dashboard/src/components/workflow-editor/test-workflow/test-workflow-logs-sidebar.tsx +++ b/apps/dashboard/src/components/workflow-editor/test-workflow/test-workflow-logs-sidebar.tsx @@ -1,58 +1,44 @@ -import { ActivityPanel } from '@/components/activity/activity-panel'; import { useEffect, useState } from 'react'; -import { JobStatusEnum } from '@novu/shared'; import { Loader2 } from 'lucide-react'; + +import { ActivityPanel } from '@/components/activity/activity-panel'; import { WorkflowTriggerInboxIllustration } from '../../icons/workflow-trigger-inbox'; import { useFetchActivities } from '../../../hooks/use-fetch-activities'; -import { QueryKeys } from '../../../utils/query-keys'; -import { useEnvironment } from '../../../context/environment/hooks'; -import { useQueryClient } from '@tanstack/react-query'; type TestWorkflowLogsSidebarProps = { transactionId?: string; }; export const TestWorkflowLogsSidebar = ({ transactionId }: TestWorkflowLogsSidebarProps) => { - const queryClient = useQueryClient(); - const { currentEnvironment } = useEnvironment(); + const [parentActivityId, setParentActivityId] = useState(undefined); const [shouldRefetch, setShouldRefetch] = useState(true); const { activities } = useFetchActivities( { filters: transactionId ? { transactionId } : undefined, }, { - enabled: transactionId !== undefined, + enabled: !!transactionId, refetchInterval: shouldRefetch ? 1000 : false, } ); + const activityId: string | undefined = parentActivityId ?? activities?.[0]?._id; useEffect(() => { - if (!activities?.length) return; - - const activity = activities[0]; - const isPending = activity.jobs?.some( - (job) => - job.status === JobStatusEnum.PENDING || - job.status === JobStatusEnum.QUEUED || - job.status === JobStatusEnum.RUNNING || - job.status === JobStatusEnum.DELAYED - ); - - // Only stop refetching if we have an activity and it's not pending - setShouldRefetch(isPending || !activity?.jobs?.length); - - queryClient.invalidateQueries({ - queryKey: [QueryKeys.fetchActivity, currentEnvironment?._id, activity._id], - }); - }, [activities]); + if (activityId) { + setShouldRefetch(false); + } + }, [activityId]); // Reset refetch when transaction ID changes useEffect(() => { + if (!transactionId) { + return; + } + setShouldRefetch(true); + setParentActivityId(undefined); }, [transactionId]); - const activityId = activities?.[0]?._id; - return (