feat(platform): Schedule specific agent version (#9444)

Scheduling always takes the newest version of an agent.

### Changes 🏗️

This PR allows to schedule any graph version by adding number input to
schedule popup. Number is automatically set to the newest version when
agent is chosen.

<img width="533" alt="Screenshot 2025-02-07 at 5 05 56 PM"
src="https://github.com/user-attachments/assets/357b8810-6f02-4066-b7a3-824d9bfd62af"
/>

- Update API, so it accepts graph version
- Update schedule pop up, so it lets user input version number
- Open and schedule correct agent
- Add `Version` column to the schedules table

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
  - [x] Can schedule version between 1 and max version
  - [x] Reject incorrect version
  - [x] Table shows proper version
  - [x] Removing schedule works
This commit is contained in:
Krzysztof Czerwinski
2025-02-10 14:24:25 +01:00
committed by GitHub
parent 610be988c4
commit 00c312d02c
5 changed files with 55 additions and 5 deletions

View File

@@ -609,6 +609,7 @@ class ScheduleCreationRequest(pydantic.BaseModel):
cron: str
input_data: dict[Any, Any]
graph_id: str
graph_version: int
@v1_router.post(
@@ -620,10 +621,13 @@ async def create_schedule(
user_id: Annotated[str, Depends(get_user_id)],
schedule: ScheduleCreationRequest,
) -> scheduler.JobInfo:
graph = await graph_db.get_graph(schedule.graph_id, user_id=user_id)
graph = await graph_db.get_graph(
schedule.graph_id, schedule.graph_version, user_id=user_id
)
if not graph:
raise HTTPException(
status_code=404, detail=f"Graph #{schedule.graph_id} not found."
status_code=404,
detail=f"Graph #{schedule.graph_id} v.{schedule.graph_version} not found.",
)
return await asyncio.to_thread(

View File

@@ -37,8 +37,8 @@ const Monitor = () => {
);
const fetchAgents = useCallback(() => {
api.listLibraryAgents().then((agent) => {
setFlows(agent);
api.listLibraryAgents().then((agents) => {
setFlows(agents);
});
api.getExecutions().then((executions) => {
setExecutions(executions);

View File

@@ -31,6 +31,8 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import { TextRenderer } from "../ui/render";
import { Input } from "../ui/input";
import { Label } from "../ui/label";
interface SchedulesTableProps {
schedules: Schedule[];
@@ -53,6 +55,8 @@ export const SchedulesTable = ({
const router = useRouter();
const cron_manager = new CronExpressionManager();
const [selectedAgent, setSelectedAgent] = useState<string>("");
const [selectedVersion, setSelectedVersion] = useState<number>(0);
const [maxVersion, setMaxVersion] = useState<number>(0);
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [selectedFilter, setSelectedFilter] = useState<string>("");
@@ -86,13 +90,38 @@ export const SchedulesTable = ({
const handleAgentSelect = (agentId: string) => {
setSelectedAgent(agentId);
const agent = agents.find((a) => a.id === agentId);
setMaxVersion(agent!.version);
setSelectedVersion(agent!.version);
};
const handleVersionSelect = (version: string) => {
setSelectedVersion(parseInt(version));
};
const handleSchedule = async () => {
if (!selectedAgent || !selectedVersion) {
toast({
title: "Invalid Input",
description: "Please select an agent and a version.",
variant: "destructive",
});
return;
}
if (selectedVersion < 1 || selectedVersion > maxVersion) {
toast({
title: "Invalid Version",
description: `Please select a version between 1 and ${maxVersion}.`,
variant: "destructive",
});
return;
}
setIsLoading(true);
try {
await new Promise((resolve) => setTimeout(resolve, 100));
router.push(`/build?flowID=${selectedAgent}&open_scheduling=true`);
router.push(
`/build?flowID=${selectedAgent}&flowVersion=${selectedVersion}&open_scheduling=true`,
);
} catch (error) {
console.error("Navigation error:", error);
}
@@ -117,6 +146,18 @@ export const SchedulesTable = ({
))}
</SelectContent>
</Select>
<Label className="mt-4">
Select version between 1 and {maxVersion}
</Label>
<Input
type="number"
min={1}
max={selectedAgent ? maxVersion : 0}
value={selectedVersion}
onChange={(e) => handleVersionSelect(e.target.value)}
placeholder="Select version"
className="w-full"
/>
<Button
onClick={handleSchedule}
disabled={isLoading || !selectedAgent}
@@ -165,6 +206,7 @@ export const SchedulesTable = ({
>
Graph Name
</TableHead>
<TableHead className="cursor-pointer">Graph Version</TableHead>
<TableHead
onClick={() => onSort("next_run_time")}
className="cursor-pointer"
@@ -198,6 +240,7 @@ export const SchedulesTable = ({
{agents.find((a) => a.id === schedule.graph_id)?.name ||
schedule.graph_id}
</TableCell>
<TableCell>{schedule.graph_version}</TableCell>
<TableCell>
{new Date(schedule.next_run_time).toLocaleString()}
</TableCell>

View File

@@ -963,6 +963,8 @@ export default function useAgentGraph(
if (flowID) {
await api.createSchedule({
graph_id: flowID,
// flowVersion is always defined here because scheduling is opened for a specific version
graph_version: flowVersion!,
cron: cronExpression,
input_data: inputs.reduce(
(acc, input) => ({

View File

@@ -512,6 +512,7 @@ export type Schedule = {
export type ScheduleCreatable = {
cron: string;
graph_id: string;
graph_version: number;
input_data: { [key: string]: any };
};