mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-09 15:17:59 -05:00
feat(frontend): Add cron-based scheduling functionality to new builder with input/credential support (#11312)
This PR introduces scheduling functionality to the new builder, allowing users to create cron-based schedules for automated graph execution with configurable inputs and credentials. https://github.com/user-attachments/assets/20c1359f-a3d6-47bf-a881-4f22c657906c ## What's New ### 🚀 Features #### Scheduling Infrastructure - **CronSchedulerDialog Component**: Interactive dialog for creating scheduled runs with: - Schedule name configuration - Cron expression builder with visual UI - Timezone support (displays user timezone or defaults to UTC) - Integration with backend scheduling API - **ScheduleGraph Component**: New action button in builder actions toolbar - Clock icon button to initiate scheduling workflow - Handles conditional flow based on input/credential requirements #### Enhanced Input Management - **Unified RunInputDialog**: Refactored to support both manual runs and scheduled runs - Dynamic "purpose" prop (`"run"` | `"schedule"`) for contextual behavior - Seamless credential and input collection flow - Transitions to cron scheduler when scheduling #### Builder Actions Improvements - **New Action Buttons Layout**: Three primary actions in the builder toolbar: 1. Agent Outputs (placeholder for future implementation) 2. Run Graph (play/stop button with gradient styling) 3. Schedule Graph (clock icon for scheduling) ## Technical Details ### New Components - `CronSchedulerDialog` - Main scheduling dialog component - `useCronSchedulerDialog` - Hook managing scheduling logic and API calls - `ScheduleGraph` - Schedule button component - `useScheduleGraph` - Hook for scheduling flow control - `AgentOutputs` - Placeholder component for future outputs feature ### Modified Components - `BuilderActions` - Added new action buttons - `RunGraph` - Enhanced with tooltip support - `RunInputDialog` - Made multi-purpose for run/schedule - `useRunInputDialog` - Added scheduling dialog state management ### API Integration - Uses `usePostV1CreateExecutionSchedule` for schedule creation - Fetches user timezone with `useGetV1GetUserTimezone` - Validates and passes graph ID, version, inputs, and credentials ## User Experience 1. **Without Inputs/Credentials**: - Click schedule button → Opens cron scheduler directly 2. **With Inputs/Credentials**: - Click schedule button → Opens input dialog - Fill required fields → Click "Schedule Run" - Configure cron expression → Create schedule 3. **Timezone Awareness**: - Shows user's configured timezone - Warns if no timezone is set (defaults to UTC) - Provides link to timezone settings ## Testing Checklist - [x] Create a schedule without inputs/credentials - [x] Create a schedule with required inputs - [x] Create a schedule with credentials - [x] Verify timezone display (with and without user timezone)
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import { AgentOutputs } from "./components/AgentOutputs/AgentOutputs";
|
||||
import { RunGraph } from "./components/RunGraph/RunGraph";
|
||||
import { ScheduleGraph } from "./components/ScheduleGraph/ScheduleGraph";
|
||||
|
||||
export const BuilderActions = () => {
|
||||
return (
|
||||
<div className="absolute bottom-4 left-[50%] z-[100] -translate-x-1/2">
|
||||
{/* TODO: Add Agent Output */}
|
||||
<div className="absolute bottom-4 left-[50%] z-[100] flex -translate-x-1/2 items-center gap-2 gap-4">
|
||||
<AgentOutputs />
|
||||
<RunGraph />
|
||||
{/* TODO: Add Schedule run button */}
|
||||
<ScheduleGraph />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Button } from "@/components/atoms/Button/Button";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/atoms/Tooltip/BaseTooltip";
|
||||
import { LogOutIcon } from "lucide-react";
|
||||
|
||||
export const AgentOutputs = () => {
|
||||
return (
|
||||
<>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
{/* Todo: Implement Agent Outputs */}
|
||||
<Button
|
||||
variant="primary"
|
||||
size="large"
|
||||
className={"relative min-w-0 border-none text-lg"}
|
||||
>
|
||||
<LogOutIcon className="size-6" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Agent Outputs</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,109 @@
|
||||
import { Button } from "@/components/atoms/Button/Button";
|
||||
import { Dialog } from "@/components/molecules/Dialog/Dialog";
|
||||
import { InfoIcon } from "lucide-react";
|
||||
import { CronScheduler } from "@/app/(platform)/library/agents/[id]/components/AgentRunsView/components/ScheduleAgentModal/components/CronScheduler/CronScheduler";
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
import { useCronSchedulerDialog } from "./useCronSchedulerDialog";
|
||||
import { Input } from "@/components/atoms/Input/Input";
|
||||
|
||||
type CronSchedulerDialogProps = {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
inputs: Record<string, any>;
|
||||
credentials: Record<string, any>;
|
||||
defaultCronExpression?: string;
|
||||
title?: string;
|
||||
};
|
||||
|
||||
export function CronSchedulerDialog({
|
||||
open,
|
||||
setOpen,
|
||||
|
||||
defaultCronExpression = "",
|
||||
title = "Schedule Graph",
|
||||
inputs,
|
||||
credentials,
|
||||
}: CronSchedulerDialogProps) {
|
||||
const {
|
||||
setCronExpression,
|
||||
userTimezone,
|
||||
timezoneDisplay,
|
||||
handleCreateSchedule,
|
||||
scheduleName,
|
||||
setScheduleName,
|
||||
isCreatingSchedule,
|
||||
} = useCronSchedulerDialog({
|
||||
open,
|
||||
setOpen,
|
||||
inputs,
|
||||
credentials,
|
||||
defaultCronExpression,
|
||||
});
|
||||
return (
|
||||
<Dialog
|
||||
controlled={{ isOpen: open, set: setOpen }}
|
||||
title={title}
|
||||
styling={{ maxWidth: "600px", minWidth: "600px" }}
|
||||
>
|
||||
<Dialog.Content>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Input
|
||||
id="schedule-name"
|
||||
label="Schedule Name"
|
||||
placeholder="Enter schedule name"
|
||||
size="small"
|
||||
className="max-w-80"
|
||||
value={scheduleName}
|
||||
onChange={(e) => setScheduleName(e.target.value)}
|
||||
/>
|
||||
|
||||
<CronScheduler
|
||||
onCronExpressionChange={setCronExpression}
|
||||
initialCronExpression={defaultCronExpression}
|
||||
key={`${open}-${defaultCronExpression}`}
|
||||
/>
|
||||
|
||||
{/* Timezone info */}
|
||||
{userTimezone === "not-set" ? (
|
||||
<div className="flex items-center gap-2 rounded-xlarge border border-amber-200 bg-amber-50 p-3">
|
||||
<InfoIcon className="h-4 w-4 text-amber-600" />
|
||||
<Text variant="body" className="text-amber-800">
|
||||
No timezone set. Schedule will run in UTC.
|
||||
<a href="/profile/settings" className="ml-1 underline">
|
||||
Set your timezone
|
||||
</a>
|
||||
</Text>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2 rounded-xlarge bg-muted/50 p-3">
|
||||
<InfoIcon className="h-4 w-4 text-muted-foreground" />
|
||||
<Text variant="body">
|
||||
Schedule will run in your timezone:{" "}
|
||||
<Text variant="body-medium" as="span">
|
||||
{timezoneDisplay}
|
||||
</Text>
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-8 flex justify-end space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setOpen(false)}
|
||||
className="h-fit"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
loading={isCreatingSchedule}
|
||||
disabled={isCreatingSchedule}
|
||||
onClick={handleCreateSchedule}
|
||||
className="h-fit"
|
||||
>
|
||||
{isCreatingSchedule ? "Creating schedule..." : "Done"}
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import { useGetV1GetUserTimezone } from "@/app/api/__generated__/endpoints/auth/auth";
|
||||
import { usePostV1CreateExecutionSchedule } from "@/app/api/__generated__/endpoints/schedules/schedules";
|
||||
import { useToast } from "@/components/molecules/Toast/use-toast";
|
||||
import { getTimezoneDisplayName } from "@/lib/timezone-utils";
|
||||
import { parseAsInteger, parseAsString, useQueryStates } from "nuqs";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export const useCronSchedulerDialog = ({
|
||||
open,
|
||||
setOpen,
|
||||
inputs,
|
||||
credentials,
|
||||
defaultCronExpression = "",
|
||||
}: {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
inputs: Record<string, any>;
|
||||
credentials: Record<string, any>;
|
||||
defaultCronExpression?: string;
|
||||
}) => {
|
||||
const { toast } = useToast();
|
||||
const [cronExpression, setCronExpression] = useState<string>("");
|
||||
const [scheduleName, setScheduleName] = useState<string>("");
|
||||
|
||||
const [{ flowID, flowVersion }] = useQueryStates({
|
||||
flowID: parseAsString,
|
||||
flowVersion: parseAsInteger,
|
||||
flowExecutionID: parseAsString,
|
||||
});
|
||||
|
||||
const { data: userTimezone } = useGetV1GetUserTimezone({
|
||||
query: {
|
||||
select: (res) => (res.status === 200 ? res.data.timezone : undefined),
|
||||
},
|
||||
});
|
||||
const timezoneDisplay = getTimezoneDisplayName(userTimezone || "UTC");
|
||||
|
||||
const { mutateAsync: createSchedule, isPending: isCreatingSchedule } =
|
||||
usePostV1CreateExecutionSchedule({
|
||||
mutation: {
|
||||
onSuccess: (response) => {
|
||||
if (response.status === 200) {
|
||||
setOpen(false);
|
||||
toast({
|
||||
title: "Schedule created",
|
||||
description: "Schedule created successfully",
|
||||
});
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Failed to create schedule",
|
||||
description:
|
||||
(error.detail as string) ?? "An unexpected error occurred.",
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setCronExpression(defaultCronExpression);
|
||||
}
|
||||
}, [open, defaultCronExpression]);
|
||||
|
||||
const handleCreateSchedule = async () => {
|
||||
if (!cronExpression || cronExpression.trim() === "") {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Invalid schedule",
|
||||
description: "Please enter a valid cron expression",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await createSchedule({
|
||||
graphId: flowID || "",
|
||||
data: {
|
||||
name: scheduleName,
|
||||
graph_version: flowID ? flowVersion : undefined,
|
||||
cron: cronExpression,
|
||||
inputs: inputs,
|
||||
credentials: credentials,
|
||||
},
|
||||
});
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return {
|
||||
cronExpression,
|
||||
setCronExpression,
|
||||
userTimezone,
|
||||
timezoneDisplay,
|
||||
handleCreateSchedule,
|
||||
setScheduleName,
|
||||
scheduleName,
|
||||
isCreatingSchedule,
|
||||
};
|
||||
};
|
||||
@@ -6,6 +6,11 @@ import { useShallow } from "zustand/react/shallow";
|
||||
import { StopIcon } from "@phosphor-icons/react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { RunInputDialog } from "../RunInputDialog/RunInputDialog";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/atoms/Tooltip/BaseTooltip";
|
||||
|
||||
export const RunGraph = () => {
|
||||
const {
|
||||
@@ -21,24 +26,31 @@ export const RunGraph = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="large"
|
||||
className={cn(
|
||||
"relative min-w-44 border-none bg-gradient-to-r from-purple-500 to-pink-500 text-lg",
|
||||
)}
|
||||
onClick={isGraphRunning ? handleStopGraph : handleRunGraph}
|
||||
>
|
||||
{!isGraphRunning && !isSaving ? (
|
||||
<PlayIcon className="mr-1 size-5" />
|
||||
) : (
|
||||
<StopIcon className="mr-1 size-5" />
|
||||
)}
|
||||
{isGraphRunning || isSaving ? "Stop Agent" : "Run Agent"}
|
||||
</Button>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="large"
|
||||
className={cn(
|
||||
"relative min-w-0 border-none bg-gradient-to-r from-purple-500 to-pink-500 text-lg",
|
||||
)}
|
||||
onClick={isGraphRunning ? handleStopGraph : handleRunGraph}
|
||||
>
|
||||
{!isGraphRunning && !isSaving ? (
|
||||
<PlayIcon className="size-6" />
|
||||
) : (
|
||||
<StopIcon className="size-6" />
|
||||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{isGraphRunning ? "Stop agent" : "Run agent"}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<RunInputDialog
|
||||
isOpen={openRunInputDialog}
|
||||
setIsOpen={setOpenRunInputDialog}
|
||||
purpose="run"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -3,17 +3,20 @@ import { RJSFSchema } from "@rjsf/utils";
|
||||
import { uiSchema } from "../../../FlowEditor/nodes/uiSchema";
|
||||
import { useGraphStore } from "@/app/(platform)/build/stores/graphStore";
|
||||
import { Button } from "@/components/atoms/Button/Button";
|
||||
import { PlayIcon } from "@phosphor-icons/react";
|
||||
import { ClockIcon, PlayIcon } from "@phosphor-icons/react";
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
import { FormRenderer } from "@/components/renderers/input-renderer/FormRenderer";
|
||||
import { useRunInputDialog } from "./useRunInputDialog";
|
||||
import { CronSchedulerDialog } from "../CronSchedulerDialog/CronSchedulerDialog";
|
||||
|
||||
export const RunInputDialog = ({
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
purpose,
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
setIsOpen: (isOpen: boolean) => void;
|
||||
purpose: "run" | "schedule";
|
||||
}) => {
|
||||
const hasInputs = useGraphStore((state) => state.hasInputs);
|
||||
const hasCredentials = useGraphStore((state) => state.hasCredentials);
|
||||
@@ -26,82 +29,107 @@ export const RunInputDialog = ({
|
||||
credentialsUiSchema,
|
||||
handleManualRun,
|
||||
handleInputChange,
|
||||
openCronSchedulerDialog,
|
||||
setOpenCronSchedulerDialog,
|
||||
inputValues,
|
||||
credentialValues,
|
||||
handleCredentialChange,
|
||||
isExecutingGraph,
|
||||
} = useRunInputDialog({ setIsOpen });
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
title="Run Agent"
|
||||
controlled={{
|
||||
isOpen,
|
||||
set: setIsOpen,
|
||||
}}
|
||||
styling={{ maxWidth: "700px" }}
|
||||
>
|
||||
<Dialog.Content>
|
||||
<div className="space-y-6 p-1">
|
||||
{/* Credentials Section */}
|
||||
{hasCredentials() && (
|
||||
<div>
|
||||
<div className="mb-4">
|
||||
<Text variant="h4" className="text-gray-900">
|
||||
Credentials
|
||||
</Text>
|
||||
<>
|
||||
<Dialog
|
||||
title={purpose === "run" ? "Run Agent" : "Schedule Run"}
|
||||
controlled={{
|
||||
isOpen,
|
||||
set: setIsOpen,
|
||||
}}
|
||||
styling={{ maxWidth: "600px", minWidth: "600px" }}
|
||||
>
|
||||
<Dialog.Content>
|
||||
<div className="space-y-6 p-1">
|
||||
{/* Credentials Section */}
|
||||
{hasCredentials() && (
|
||||
<div>
|
||||
<div className="mb-4">
|
||||
<Text variant="h4" className="text-gray-900">
|
||||
Credentials
|
||||
</Text>
|
||||
</div>
|
||||
<div className="px-2">
|
||||
<FormRenderer
|
||||
jsonSchema={credentialsSchema as RJSFSchema}
|
||||
handleChange={(v) => handleCredentialChange(v.formData)}
|
||||
uiSchema={credentialsUiSchema}
|
||||
initialValues={{}}
|
||||
formContext={{
|
||||
showHandles: false,
|
||||
size: "large",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-2">
|
||||
<FormRenderer
|
||||
jsonSchema={credentialsSchema as RJSFSchema}
|
||||
handleChange={(v) => handleCredentialChange(v.formData)}
|
||||
uiSchema={credentialsUiSchema}
|
||||
initialValues={{}}
|
||||
formContext={{
|
||||
showHandles: false,
|
||||
size: "large",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* Inputs Section */}
|
||||
{hasInputs() && (
|
||||
<div>
|
||||
<div className="mb-4">
|
||||
<Text variant="h4" className="text-gray-900">
|
||||
Inputs
|
||||
</Text>
|
||||
{/* Inputs Section */}
|
||||
{hasInputs() && (
|
||||
<div>
|
||||
<div className="mb-4">
|
||||
<Text variant="h4" className="text-gray-900">
|
||||
Inputs
|
||||
</Text>
|
||||
</div>
|
||||
<div className="px-2">
|
||||
<FormRenderer
|
||||
jsonSchema={inputSchema as RJSFSchema}
|
||||
handleChange={(v) => handleInputChange(v.formData)}
|
||||
uiSchema={uiSchema}
|
||||
initialValues={{}}
|
||||
formContext={{
|
||||
showHandles: false,
|
||||
size: "large",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-2">
|
||||
<FormRenderer
|
||||
jsonSchema={inputSchema as RJSFSchema}
|
||||
handleChange={(v) => handleInputChange(v.formData)}
|
||||
uiSchema={uiSchema}
|
||||
initialValues={{}}
|
||||
formContext={{
|
||||
showHandles: false,
|
||||
size: "large",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* Action Button */}
|
||||
<div className="flex justify-end pt-2">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="large"
|
||||
className="group h-fit min-w-0 gap-2 border-none bg-gradient-to-r from-blue-600 to-purple-600 px-8 transition-all"
|
||||
onClick={handleManualRun}
|
||||
loading={isExecutingGraph}
|
||||
>
|
||||
<PlayIcon className="size-5 transition-transform group-hover:scale-110" />
|
||||
<span className="font-semibold">Manual Run</span>
|
||||
</Button>
|
||||
{/* Action Button */}
|
||||
<div className="flex justify-end pt-2">
|
||||
{purpose === "run" && (
|
||||
<Button
|
||||
variant="primary"
|
||||
size="large"
|
||||
className="group h-fit min-w-0 gap-2"
|
||||
onClick={handleManualRun}
|
||||
loading={isExecutingGraph}
|
||||
>
|
||||
<PlayIcon className="size-5 transition-transform group-hover:scale-110" />
|
||||
<span className="font-semibold">Manual Run</span>
|
||||
</Button>
|
||||
)}
|
||||
{purpose === "schedule" && (
|
||||
<Button
|
||||
variant="primary"
|
||||
size="large"
|
||||
className="group h-fit min-w-0 gap-2"
|
||||
onClick={() => setOpenCronSchedulerDialog(true)}
|
||||
>
|
||||
<ClockIcon className="size-5 transition-transform group-hover:scale-110" />
|
||||
<span className="font-semibold">Schedule Run</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog>
|
||||
</Dialog.Content>
|
||||
</Dialog>
|
||||
<CronSchedulerDialog
|
||||
open={openCronSchedulerDialog}
|
||||
setOpen={setOpenCronSchedulerDialog}
|
||||
inputs={inputValues}
|
||||
credentials={credentialValues}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -19,6 +19,8 @@ export const useRunInputDialog = ({
|
||||
const credentialsSchema = useGraphStore(
|
||||
(state) => state.credentialsInputSchema,
|
||||
);
|
||||
|
||||
const [openCronSchedulerDialog, setOpenCronSchedulerDialog] = useState(false);
|
||||
const [inputValues, setInputValues] = useState<Record<string, any>>({});
|
||||
const [credentialValues, setCredentialValues] = useState<
|
||||
Record<string, CredentialsMetaInput>
|
||||
@@ -104,5 +106,7 @@ export const useRunInputDialog = ({
|
||||
handleInputChange,
|
||||
handleCredentialChange,
|
||||
handleManualRun,
|
||||
openCronSchedulerDialog,
|
||||
setOpenCronSchedulerDialog,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Button } from "@/components/atoms/Button/Button";
|
||||
import { ClockIcon } from "@phosphor-icons/react";
|
||||
import { RunInputDialog } from "../RunInputDialog/RunInputDialog";
|
||||
import { useScheduleGraph } from "./useScheduleGraph";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/atoms/Tooltip/BaseTooltip";
|
||||
import { CronSchedulerDialog } from "../CronSchedulerDialog/CronSchedulerDialog";
|
||||
|
||||
export const ScheduleGraph = () => {
|
||||
const {
|
||||
openScheduleInputDialog,
|
||||
setOpenScheduleInputDialog,
|
||||
handleScheduleGraph,
|
||||
openCronSchedulerDialog,
|
||||
setOpenCronSchedulerDialog,
|
||||
} = useScheduleGraph();
|
||||
return (
|
||||
<>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="large"
|
||||
className={"relative min-w-0 border-none text-lg"}
|
||||
onClick={handleScheduleGraph}
|
||||
>
|
||||
<ClockIcon className="size-6" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Schedule Graph</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<RunInputDialog
|
||||
isOpen={openScheduleInputDialog}
|
||||
setIsOpen={setOpenScheduleInputDialog}
|
||||
purpose="schedule"
|
||||
/>
|
||||
<CronSchedulerDialog
|
||||
open={openCronSchedulerDialog}
|
||||
setOpen={setOpenCronSchedulerDialog}
|
||||
inputs={{}}
|
||||
credentials={{}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
import { useGraphStore } from "@/app/(platform)/build/stores/graphStore";
|
||||
import { useShallow } from "zustand/react/shallow";
|
||||
import { useNewSaveControl } from "../../../NewControlPanel/NewSaveControl/useNewSaveControl";
|
||||
import { useState } from "react";
|
||||
|
||||
export const useScheduleGraph = () => {
|
||||
const { onSubmit: onSaveGraph } = useNewSaveControl({
|
||||
showToast: false,
|
||||
});
|
||||
const hasInputs = useGraphStore(useShallow((state) => state.hasInputs));
|
||||
const hasCredentials = useGraphStore(
|
||||
useShallow((state) => state.hasCredentials),
|
||||
);
|
||||
const [openScheduleInputDialog, setOpenScheduleInputDialog] = useState(false);
|
||||
const [openCronSchedulerDialog, setOpenCronSchedulerDialog] = useState(false);
|
||||
|
||||
const handleScheduleGraph = async () => {
|
||||
await onSaveGraph(undefined);
|
||||
if (hasInputs() || hasCredentials()) {
|
||||
setOpenScheduleInputDialog(true);
|
||||
} else {
|
||||
setOpenCronSchedulerDialog(true);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
openScheduleInputDialog,
|
||||
setOpenScheduleInputDialog,
|
||||
handleScheduleGraph,
|
||||
openCronSchedulerDialog,
|
||||
setOpenCronSchedulerDialog,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user