Jon/use new runs urls (#3930)

This commit is contained in:
Jonathan Dobson
2025-11-06 14:28:26 -05:00
committed by GitHub
parent 28f7dcc1a7
commit 20b2c23de9
11 changed files with 98 additions and 63 deletions

View File

@@ -11,7 +11,7 @@ const useFirstParam = (...paramNames: string[]) => {
return value; return value;
} }
} }
return null; return undefined;
}; };
export { useFirstParam }; export { useFirstParam };

View File

@@ -29,6 +29,7 @@ import { useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom"; import { useNavigate, useSearchParams } from "react-router-dom";
import { getClient } from "@/api/AxiosClient"; import { getClient } from "@/api/AxiosClient";
import { useCredentialGetter } from "@/hooks/useCredentialGetter"; import { useCredentialGetter } from "@/hooks/useCredentialGetter";
import * as env from "@/util/env";
function isTask(run: Task | WorkflowRunApiResponse): run is Task { function isTask(run: Task | WorkflowRunApiResponse): run is Task {
return "task_id" in run; return "task_id" in run;
@@ -185,7 +186,9 @@ function RunHistory() {
onClick={(event) => { onClick={(event) => {
handleNavigate( handleNavigate(
event, event,
`/workflows/${run.workflow_permanent_id}/${run.workflow_run_id}/overview`, env.useNewRunsUrl
? `/runs/${run.workflow_run_id}`
: `/workflows/${run.workflow_permanent_id}/${run.workflow_run_id}/overview`,
); );
}} }}
> >

View File

@@ -4,6 +4,7 @@
*/ */
import { Navigate, Route, Routes, useParams } from "react-router-dom"; import { Navigate, Route, Routes, useParams } from "react-router-dom";
import { useMemo } from "react";
import { PageLayout } from "@/components/PageLayout"; import { PageLayout } from "@/components/PageLayout";
import { Status404 } from "@/components/Status404"; import { Status404 } from "@/components/Status404";
@@ -22,12 +23,67 @@ import { WorkflowsPageLayout } from "@/routes/workflows/WorkflowsPageLayout";
import { useTaskV2Query } from "@/routes/runs/useTaskV2Query"; import { useTaskV2Query } from "@/routes/runs/useTaskV2Query";
function RunRouter() { function RunRouter() {
let { runId } = useParams(); const { runId } = useParams();
const { data: task_v2, isLoading } = useTaskV2Query({ const { data: task_v2, isLoading } = useTaskV2Query({
id: runId?.startsWith("tsk_v2") ? runId : undefined, id: runId?.startsWith("tsk_v2") ? runId : undefined,
}); });
const runType = runId?.startsWith("tsk_v2")
? "redirect"
: runId?.startsWith("wr_")
? "workflow"
: runId?.startsWith("tsk_")
? "task"
: null;
const routes = useMemo(() => {
if (runType === "workflow") {
return (
<Routes>
<Route element={<WorkflowsPageLayout />}>
<Route element={<WorkflowRun />}>
<Route index element={<Navigate to="overview" replace />} />
<Route
path="blocks"
element={<Navigate to="overview" replace />}
/>
<Route path="overview" element={<WorkflowRunOverview />} />
<Route path="output" element={<WorkflowRunOutput />} />
<Route
path="parameters"
element={<WorkflowPostRunParameters />}
/>
<Route path="recording" element={<WorkflowRunRecording />} />
<Route
path="code"
element={<WorkflowRunCode showCacheKeyValueSelector={true} />}
/>
</Route>
</Route>
</Routes>
);
}
if (runType === "task") {
return (
<Routes>
<Route element={<PageLayout />}>
<Route element={<TaskDetails />}>
<Route index element={<Navigate to="actions" replace />} />
<Route path="actions" element={<TaskActions />} />
<Route path="recording" element={<TaskRecording />} />
<Route path="parameters" element={<TaskParameters />} />
<Route path="diagnostics" element={<StepArtifactsLayout />} />
</Route>
</Route>
</Routes>
);
}
return <Status404 />;
}, [runType]);
if (runId?.startsWith("tsk_v2")) { if (runId?.startsWith("tsk_v2")) {
if (isLoading) { if (isLoading) {
return <div>Fetching task details...</div>; return <div>Fetching task details...</div>;
@@ -45,50 +101,10 @@ function RunRouter() {
return <Status404 />; return <Status404 />;
} }
runId = workflowRunId;
return <Navigate to={`/runs/${workflowRunId}`} replace />; return <Navigate to={`/runs/${workflowRunId}`} replace />;
} }
if (runId?.startsWith("wr_")) { return routes;
return (
<Routes>
<Route element={<WorkflowsPageLayout />}>
<Route element={<WorkflowRun />}>
<Route index element={<Navigate to="overview" replace />} />
<Route path="blocks" element={<Navigate to="overview" replace />} />
<Route path="overview" element={<WorkflowRunOverview />} />
<Route path="output" element={<WorkflowRunOutput />} />
<Route path="parameters" element={<WorkflowPostRunParameters />} />
<Route path="recording" element={<WorkflowRunRecording />} />
<Route
path="code"
element={<WorkflowRunCode showCacheKeyValueSelector={true} />}
/>
</Route>
</Route>
</Routes>
);
}
if (runId?.startsWith("tsk_")) {
return (
<Routes>
<Route element={<PageLayout />}>
<Route element={<TaskDetails />}>
<Route index element={<Navigate to="actions" replace />} />
<Route path="actions" element={<TaskActions />} />
<Route path="recording" element={<TaskRecording />} />
<Route path="parameters" element={<TaskParameters />} />
<Route path="diagnostics" element={<StepArtifactsLayout />} />
</Route>
</Route>
</Routes>
);
}
// Fallback (should not reach here due to earlier check)
return <Status404 />;
} }
export { RunRouter }; export { RunRouter };

View File

@@ -1,9 +1,9 @@
import { useParams } from "react-router-dom";
import { useTaskQuery } from "../../detail/hooks/useTaskQuery"; import { useTaskQuery } from "../../detail/hooks/useTaskQuery";
import { CreateNewTaskForm } from "../CreateNewTaskForm"; import { CreateNewTaskForm } from "../CreateNewTaskForm";
import { useFirstParam } from "@/hooks/useFirstParam";
function RetryTask() { function RetryTask() {
const { taskId } = useParams(); const taskId = useFirstParam("taskId", "runId");
const { data: task, isLoading } = useTaskQuery({ id: taskId }); const { data: task, isLoading } = useTaskQuery({ id: taskId });
if (isLoading) { if (isLoading) {

View File

@@ -40,6 +40,7 @@ import { statusIsFinalized } from "../types";
import { MAX_STEPS_DEFAULT } from "../constants"; import { MAX_STEPS_DEFAULT } from "../constants";
import { useTaskQuery } from "./hooks/useTaskQuery"; import { useTaskQuery } from "./hooks/useTaskQuery";
import { useFirstParam } from "@/hooks/useFirstParam"; import { useFirstParam } from "@/hooks/useFirstParam";
import * as env from "@/util/env";
function createTaskRequestObject(values: TaskApiResponse) { function createTaskRequestObject(values: TaskApiResponse) {
return { return {
@@ -303,7 +304,11 @@ function TaskDetails() {
workflow && workflow &&
workflowRun && ( workflowRun && (
<Link <Link
to={`/workflows/${workflow.workflow_permanent_id}/${workflowRun.workflow_run_id}/overview`} to={
env.useNewRunsUrl
? `/runs/${workflowRun.workflow_run_id}`
: `/workflows/${workflow.workflow_permanent_id}/${workflowRun.workflow_run_id}/overview`
}
> >
{workflow.title} {workflow.title}
</Link> </Link>

View File

@@ -49,6 +49,7 @@ import { getLabelForWorkflowParameterType } from "./editor/workflowEditorUtils";
import { WorkflowParameter } from "./types/workflowTypes"; import { WorkflowParameter } from "./types/workflowTypes";
import { WorkflowParameterInput } from "./WorkflowParameterInput"; import { WorkflowParameterInput } from "./WorkflowParameterInput";
import { TestWebhookDialog } from "@/components/TestWebhookDialog"; import { TestWebhookDialog } from "@/components/TestWebhookDialog";
import * as env from "@/util/env";
// Utility function to omit specified keys from an object // Utility function to omit specified keys from an object
function omit<T extends Record<string, unknown>, K extends keyof T>( function omit<T extends Record<string, unknown>, K extends keyof T>(
@@ -247,7 +248,9 @@ function RunWorkflowForm({
queryKey: ["runs"], queryKey: ["runs"],
}); });
navigate( navigate(
`/workflows/${workflowPermanentId}/${response.data.workflow_run_id}/overview`, env.useNewRunsUrl
? `/runs/${response.data.workflow_run_id}`
: `/workflows/${workflowPermanentId}/${response.data.workflow_run_id}/overview`,
); );
}, },
onError: (error: AxiosError) => { onError: (error: AxiosError) => {

View File

@@ -49,6 +49,7 @@ import {
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { RunParametersDialog } from "./workflowRun/RunParametersDialog"; import { RunParametersDialog } from "./workflowRun/RunParametersDialog";
import * as env from "@/util/env";
function WorkflowPage() { function WorkflowPage() {
const { workflowPermanentId } = useParams(); const { workflowPermanentId } = useParams();
@@ -186,18 +187,19 @@ function WorkflowPage() {
<TableRow <TableRow
key={workflowRun.workflow_run_id} key={workflowRun.workflow_run_id}
onClick={(event) => { onClick={(event) => {
const url = env.useNewRunsUrl
? `/runs/${workflowRun.workflow_run_id}`
: `/workflows/${workflowPermanentId}/${workflowRun.workflow_run_id}/overview`;
if (event.ctrlKey || event.metaKey) { if (event.ctrlKey || event.metaKey) {
window.open( window.open(
window.location.origin + window.location.origin + url,
`/workflows/${workflowPermanentId}/${workflowRun.workflow_run_id}/overview`,
"_blank", "_blank",
"noopener,noreferrer", "noopener,noreferrer",
); );
return; return;
} }
navigate( navigate(url);
`/workflows/${workflowPermanentId}/${workflowRun.workflow_run_id}/overview`,
);
}} }}
className="cursor-pointer" className="cursor-pointer"
> >

View File

@@ -32,7 +32,7 @@ import {
ReloadIcon, ReloadIcon,
} from "@radix-ui/react-icons"; } from "@radix-ui/react-icons";
import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQueryClient } from "@tanstack/react-query";
import { Link, Outlet, useParams, useSearchParams } from "react-router-dom"; import { Link, Outlet, useSearchParams } from "react-router-dom";
import { statusIsFinalized, statusIsRunningOrQueued } from "../tasks/types"; import { statusIsFinalized, statusIsRunningOrQueued } from "../tasks/types";
import { useWorkflowRunWithWorkflowQuery } from "./hooks/useWorkflowRunWithWorkflowQuery"; import { useWorkflowRunWithWorkflowQuery } from "./hooks/useWorkflowRunWithWorkflowQuery";
import { WorkflowRunTimeline } from "./workflowRun/WorkflowRunTimeline"; import { WorkflowRunTimeline } from "./workflowRun/WorkflowRunTimeline";
@@ -44,6 +44,7 @@ import { cn } from "@/util/utils";
import { ScrollArea, ScrollAreaViewport } from "@/components/ui/scroll-area"; import { ScrollArea, ScrollAreaViewport } from "@/components/ui/scroll-area";
import { ApiWebhookActionsMenu } from "@/components/ApiWebhookActionsMenu"; import { ApiWebhookActionsMenu } from "@/components/ApiWebhookActionsMenu";
import { WebhookReplayDialog } from "@/components/WebhookReplayDialog"; import { WebhookReplayDialog } from "@/components/WebhookReplayDialog";
import { useFirstParam } from "@/hooks/useFirstParam";
import { type ApiCommandOptions } from "@/util/apiCommands"; import { type ApiCommandOptions } from "@/util/apiCommands";
import { useBlockScriptsQuery } from "@/routes/workflows/hooks/useBlockScriptsQuery"; import { useBlockScriptsQuery } from "@/routes/workflows/hooks/useBlockScriptsQuery";
import { constructCacheKeyValue } from "@/routes/workflows/editor/utils"; import { constructCacheKeyValue } from "@/routes/workflows/editor/utils";
@@ -55,7 +56,7 @@ function WorkflowRun() {
const embed = searchParams.get("embed"); const embed = searchParams.get("embed");
const isEmbedded = embed === "true"; const isEmbedded = embed === "true";
const active = searchParams.get("active"); const active = searchParams.get("active");
const { workflowRunId } = useParams(); const workflowRunId = useFirstParam("workflowRunId", "runId");
const credentialGetter = useCredentialGetter(); const credentialGetter = useCredentialGetter();
const apiCredential = useApiCredential(); const apiCredential = useApiCredential();
const queryClient = useQueryClient(); const queryClient = useQueryClient();

View File

@@ -2,7 +2,6 @@ import { getClient } from "@/api/AxiosClient";
import { useCredentialGetter } from "@/hooks/useCredentialGetter"; import { useCredentialGetter } from "@/hooks/useCredentialGetter";
import { statusIsNotFinalized } from "@/routes/tasks/types"; import { statusIsNotFinalized } from "@/routes/tasks/types";
import { keepPreviousData, useQuery } from "@tanstack/react-query"; import { keepPreviousData, useQuery } from "@tanstack/react-query";
import { useParams } from "react-router-dom";
import { WorkflowRunTimelineItem } from "../types/workflowRunTypes"; import { WorkflowRunTimelineItem } from "../types/workflowRunTypes";
import { useWorkflowRunWithWorkflowQuery } from "./useWorkflowRunWithWorkflowQuery"; import { useWorkflowRunWithWorkflowQuery } from "./useWorkflowRunWithWorkflowQuery";
import { useGlobalWorkflowsQuery } from "./useGlobalWorkflowsQuery"; import { useGlobalWorkflowsQuery } from "./useGlobalWorkflowsQuery";
@@ -10,13 +9,11 @@ import { useFirstParam } from "@/hooks/useFirstParam";
function useWorkflowRunTimelineQuery() { function useWorkflowRunTimelineQuery() {
const workflowRunId = useFirstParam("workflowRunId", "runId"); const workflowRunId = useFirstParam("workflowRunId", "runId");
const { workflowPermanentId: workflowPermanentIdParam } = useParams();
const credentialGetter = useCredentialGetter(); const credentialGetter = useCredentialGetter();
const { data: globalWorkflows } = useGlobalWorkflowsQuery(); const { data: globalWorkflows } = useGlobalWorkflowsQuery();
const { data: workflowRun } = useWorkflowRunWithWorkflowQuery(); const { data: workflowRun } = useWorkflowRunWithWorkflowQuery();
const workflow = workflowRun?.workflow;
const workflowPermanentId = const workflowPermanentId = workflow?.workflow_permanent_id;
workflowPermanentIdParam ?? workflowRun?.workflow?.workflow_permanent_id;
return useQuery<Array<WorkflowRunTimelineItem>>({ return useQuery<Array<WorkflowRunTimelineItem>>({
queryKey: ["workflowRunTimeline", workflowPermanentId, workflowRunId], queryKey: ["workflowRunTimeline", workflowPermanentId, workflowRunId],

View File

@@ -1,5 +1,5 @@
import { Status } from "@/api/types"; import { Status } from "@/api/types";
import { useWorkflowRunQuery } from "../hooks/useWorkflowRunQuery"; import { useWorkflowRunWithWorkflowQuery } from "../hooks/useWorkflowRunWithWorkflowQuery";
import { ZoomableImage } from "@/components/ZoomableImage"; import { ZoomableImage } from "@/components/ZoomableImage";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { statusIsNotFinalized } from "@/routes/tasks/types"; import { statusIsNotFinalized } from "@/routes/tasks/types";
@@ -25,12 +25,14 @@ const wssBaseUrl = import.meta.env.VITE_WSS_BASE_URL;
function WorkflowRunStream(props?: Props) { function WorkflowRunStream(props?: Props) {
const alwaysShowStream = props?.alwaysShowStream ?? false; const alwaysShowStream = props?.alwaysShowStream ?? false;
const { data: workflowRun } = useWorkflowRunQuery(); const { data: workflowRun } = useWorkflowRunWithWorkflowQuery();
const [streamImgSrc, setStreamImgSrc] = useState<string>(""); const [streamImgSrc, setStreamImgSrc] = useState<string>("");
const showStream = const showStream =
alwaysShowStream || (workflowRun && statusIsNotFinalized(workflowRun)); alwaysShowStream || (workflowRun && statusIsNotFinalized(workflowRun));
const credentialGetter = useCredentialGetter(); const credentialGetter = useCredentialGetter();
const { workflowRunId, workflowPermanentId } = useParams(); const { workflowRunId } = useParams();
const workflow = workflowRun?.workflow;
const workflowPermanentId = workflow?.workflow_permanent_id;
const queryClient = useQueryClient(); const queryClient = useQueryClient();
useEffect(() => { useEffect(() => {
@@ -73,6 +75,9 @@ function WorkflowRunStream(props?: Props) {
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: ["workflowRun", workflowPermanentId, workflowRunId], queryKey: ["workflowRun", workflowPermanentId, workflowRunId],
}); });
queryClient.invalidateQueries({
queryKey: ["workflowRun", workflowRunId],
});
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: ["workflowTasks", workflowRunId], queryKey: ["workflowTasks", workflowRunId],
}); });

View File

@@ -94,6 +94,8 @@ function clearRuntimeApiKey(): void {
} }
} }
const useNewRunsUrl = true as const;
export { export {
apiBaseUrl, apiBaseUrl,
runsApiBaseUrl, runsApiBaseUrl,
@@ -106,4 +108,5 @@ export {
getRuntimeApiKey, getRuntimeApiKey,
persistRuntimeApiKey, persistRuntimeApiKey,
clearRuntimeApiKey, clearRuntimeApiKey,
useNewRunsUrl,
}; };