Frontend: unified /runs URL (#3912)
This commit is contained in:
@@ -246,9 +246,7 @@ function RunWorkflowForm({
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["runs"],
|
||||
});
|
||||
navigate(
|
||||
`/workflows/${workflowPermanentId}/${response.data.workflow_run_id}/overview`,
|
||||
);
|
||||
navigate(`/runs/${response.data.workflow_run_id}`);
|
||||
},
|
||||
onError: (error: AxiosError) => {
|
||||
const detail = (error.response?.data as { detail?: string })?.detail;
|
||||
|
||||
@@ -189,15 +189,13 @@ function WorkflowPage() {
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
window.open(
|
||||
window.location.origin +
|
||||
`/workflows/${workflowPermanentId}/${workflowRun.workflow_run_id}/overview`,
|
||||
`/runs/${workflowRun.workflow_run_id}`,
|
||||
"_blank",
|
||||
"noopener,noreferrer",
|
||||
);
|
||||
return;
|
||||
}
|
||||
navigate(
|
||||
`/workflows/${workflowPermanentId}/${workflowRun.workflow_run_id}/overview`,
|
||||
);
|
||||
navigate(`/runs/${workflowRun.workflow_run_id}`);
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AxiosError } from "axios";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getClient } from "@/api/AxiosClient";
|
||||
import { ProxyLocation, Status } from "@/api/types";
|
||||
@@ -7,6 +8,7 @@ import {
|
||||
type SwitchBarNavigationOption,
|
||||
} from "@/components/SwitchBarNavigation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Status404 } from "@/components/Status404";
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
@@ -32,8 +34,7 @@ import {
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { Link, Outlet, useParams, useSearchParams } from "react-router-dom";
|
||||
import { statusIsFinalized, statusIsRunningOrQueued } from "../tasks/types";
|
||||
import { useWorkflowQuery } from "./hooks/useWorkflowQuery";
|
||||
import { useWorkflowRunQuery } from "./hooks/useWorkflowRunQuery";
|
||||
import { useWorkflowRunWithWorkflowQuery } from "./hooks/useWorkflowRunWithWorkflowQuery";
|
||||
import { WorkflowRunTimeline } from "./workflowRun/WorkflowRunTimeline";
|
||||
import { useWorkflowRunTimelineQuery } from "./hooks/useWorkflowRunTimelineQuery";
|
||||
import { findActiveItem } from "./workflowRun/workflowTimelineUtils";
|
||||
@@ -54,23 +55,22 @@ function WorkflowRun() {
|
||||
const embed = searchParams.get("embed");
|
||||
const isEmbedded = embed === "true";
|
||||
const active = searchParams.get("active");
|
||||
const { workflowRunId, workflowPermanentId } = useParams();
|
||||
const { workflowRunId } = useParams();
|
||||
const credentialGetter = useCredentialGetter();
|
||||
const apiCredential = useApiCredential();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data: workflow, isLoading: workflowIsLoading } = useWorkflowQuery({
|
||||
workflowPermanentId,
|
||||
});
|
||||
|
||||
const cacheKey = workflow?.cache_key ?? "";
|
||||
|
||||
const {
|
||||
data: workflowRun,
|
||||
isLoading: workflowRunIsLoading,
|
||||
isFetched,
|
||||
} = useWorkflowRunQuery();
|
||||
error,
|
||||
} = useWorkflowRunWithWorkflowQuery();
|
||||
|
||||
const status = (error as AxiosError | undefined)?.response?.status;
|
||||
const workflow = workflowRun?.workflow;
|
||||
const workflowPermanentId = workflow?.workflow_permanent_id;
|
||||
const cacheKey = workflow?.cache_key ?? "";
|
||||
const isFinalized = workflowRun ? statusIsFinalized(workflowRun) : null;
|
||||
|
||||
const [hasPublishedCode, setHasPublishedCode] = useState(false);
|
||||
@@ -155,7 +155,7 @@ function WorkflowRun() {
|
||||
workflowRun?.proxy_location ?? ProxyLocation.Residential;
|
||||
const maxScreenshotScrolls = workflowRun?.max_screenshot_scrolls ?? null;
|
||||
|
||||
const title = workflowIsLoading ? (
|
||||
const title = workflowRunIsLoading ? (
|
||||
<Skeleton className="h-9 w-48" />
|
||||
) : (
|
||||
<h1 className="text-3xl">
|
||||
@@ -292,6 +292,10 @@ function WorkflowRun() {
|
||||
},
|
||||
];
|
||||
|
||||
if (status === 404) {
|
||||
return <Status404 />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{!isEmbedded && (
|
||||
|
||||
@@ -4,14 +4,19 @@ import { statusIsNotFinalized } from "@/routes/tasks/types";
|
||||
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { WorkflowRunTimelineItem } from "../types/workflowRunTypes";
|
||||
import { useWorkflowRunQuery } from "./useWorkflowRunQuery";
|
||||
import { useWorkflowRunWithWorkflowQuery } from "./useWorkflowRunWithWorkflowQuery";
|
||||
import { useGlobalWorkflowsQuery } from "./useGlobalWorkflowsQuery";
|
||||
import { useFirstParam } from "@/hooks/useFirstParam";
|
||||
|
||||
function useWorkflowRunTimelineQuery() {
|
||||
const { workflowRunId, workflowPermanentId } = useParams();
|
||||
const workflowRunId = useFirstParam("workflowRunId", "runId");
|
||||
const { workflowPermanentId: workflowPermanentIdParam } = useParams();
|
||||
const credentialGetter = useCredentialGetter();
|
||||
const { data: globalWorkflows } = useGlobalWorkflowsQuery();
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const { data: workflowRun } = useWorkflowRunWithWorkflowQuery();
|
||||
|
||||
const workflowPermanentId =
|
||||
workflowPermanentIdParam ?? workflowRun?.workflow?.workflow_permanent_id;
|
||||
|
||||
return useQuery<Array<WorkflowRunTimelineItem>>({
|
||||
queryKey: ["workflowRunTimeline", workflowPermanentId, workflowRunId],
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { getClient } from "@/api/AxiosClient";
|
||||
import { WorkflowRunStatusApiResponseWithWorkflow } from "@/api/types";
|
||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||
import {
|
||||
statusIsNotFinalized,
|
||||
statusIsRunningOrQueued,
|
||||
} from "@/routes/tasks/types";
|
||||
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||
import { useFirstParam } from "@/hooks/useFirstParam";
|
||||
|
||||
function useWorkflowRunWithWorkflowQuery() {
|
||||
const workflowRunId = useFirstParam("workflowRunId", "runId");
|
||||
const credentialGetter = useCredentialGetter();
|
||||
|
||||
return useQuery<WorkflowRunStatusApiResponseWithWorkflow>({
|
||||
queryKey: ["workflowRun", workflowRunId],
|
||||
queryFn: async () => {
|
||||
const client = await getClient(credentialGetter, "sans-api-v1");
|
||||
return client
|
||||
.get(`/workflows/runs/${workflowRunId}`)
|
||||
.then((response) => response.data);
|
||||
},
|
||||
refetchInterval: (query) => {
|
||||
if (!query.state.data) {
|
||||
return false;
|
||||
}
|
||||
if (statusIsNotFinalized(query.state.data)) {
|
||||
return 5000;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
// required for OS-level notifications to work (workflow run completion)
|
||||
refetchIntervalInBackground: true,
|
||||
placeholderData: keepPreviousData,
|
||||
refetchOnMount: (query) => {
|
||||
if (!query.state.data) {
|
||||
return false;
|
||||
}
|
||||
return statusIsRunningOrQueued(query.state.data) ? "always" : false;
|
||||
},
|
||||
refetchOnWindowFocus: (query) => {
|
||||
if (!query.state.data) {
|
||||
return false;
|
||||
}
|
||||
return statusIsRunningOrQueued(query.state.data);
|
||||
},
|
||||
enabled: !!workflowRunId,
|
||||
});
|
||||
}
|
||||
|
||||
export { useWorkflowRunWithWorkflowQuery };
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useWorkflowRunQuery } from "../hooks/useWorkflowRunQuery";
|
||||
import { useWorkflowRunWithWorkflowQuery } from "../hooks/useWorkflowRunWithWorkflowQuery";
|
||||
import { CodeEditor } from "../components/CodeEditor";
|
||||
import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea";
|
||||
import { useActiveWorkflowRunItem } from "./useActiveWorkflowRunItem";
|
||||
@@ -18,7 +18,7 @@ function WorkflowPostRunParameters() {
|
||||
useWorkflowRunTimelineQuery();
|
||||
const [activeItem] = useActiveWorkflowRunItem();
|
||||
const { data: workflowRun, isLoading: workflowRunIsLoading } =
|
||||
useWorkflowRunQuery();
|
||||
useWorkflowRunWithWorkflowQuery();
|
||||
const parameters = workflowRun?.parameters ?? {};
|
||||
|
||||
if (workflowRunIsLoading || workflowRunTimelineIsLoading) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import {
|
||||
@@ -15,8 +14,7 @@ import { statusIsFinalized } from "@/routes/tasks/types";
|
||||
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
|
||||
import { useBlockScriptsQuery } from "@/routes/workflows/hooks/useBlockScriptsQuery";
|
||||
import { useCacheKeyValuesQuery } from "@/routes/workflows/hooks/useCacheKeyValuesQuery";
|
||||
import { useWorkflowQuery } from "@/routes/workflows/hooks/useWorkflowQuery";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
import { useWorkflowRunWithWorkflowQuery } from "@/routes/workflows/hooks/useWorkflowRunWithWorkflowQuery";
|
||||
import { constructCacheKeyValue } from "@/routes/workflows/editor/utils";
|
||||
import { getCode, getOrderedBlockLabels } from "@/routes/workflows/utils";
|
||||
import { cn } from "@/util/utils";
|
||||
@@ -30,11 +28,9 @@ interface Props {
|
||||
function WorkflowRunCode(props?: Props) {
|
||||
const showCacheKeyValueSelector = props?.showCacheKeyValueSelector ?? false;
|
||||
const queryClient = useQueryClient();
|
||||
const { workflowPermanentId } = useParams();
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const { data: workflow } = useWorkflowQuery({
|
||||
workflowPermanentId,
|
||||
});
|
||||
const { data: workflowRun } = useWorkflowRunWithWorkflowQuery();
|
||||
const workflow = workflowRun?.workflow;
|
||||
const workflowPermanentId = workflow?.workflow_permanent_id;
|
||||
const cacheKey = workflow?.cache_key ?? "";
|
||||
const [cacheKeyValue, setCacheKeyValue] = useState(
|
||||
cacheKey === ""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FileIcon } from "@radix-ui/react-icons";
|
||||
import { CodeEditor } from "../components/CodeEditor";
|
||||
import { useWorkflowRunQuery } from "../hooks/useWorkflowRunQuery";
|
||||
import { useWorkflowRunWithWorkflowQuery } from "../hooks/useWorkflowRunWithWorkflowQuery";
|
||||
import { useActiveWorkflowRunItem } from "./useActiveWorkflowRunItem";
|
||||
import {
|
||||
hasExtractedInformation,
|
||||
@@ -17,7 +17,7 @@ function WorkflowRunOutput() {
|
||||
const { data: workflowRunTimeline, isLoading: workflowRunTimelineIsLoading } =
|
||||
useWorkflowRunTimelineQuery();
|
||||
const [activeItem] = useActiveWorkflowRunItem();
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const { data: workflowRun } = useWorkflowRunWithWorkflowQuery();
|
||||
|
||||
if (workflowRunTimelineIsLoading) {
|
||||
return <div>Loading...</div>;
|
||||
|
||||
@@ -3,8 +3,7 @@ import { BrowserStream } from "@/components/BrowserStream";
|
||||
import { AspectRatio } from "@/components/ui/aspect-ratio";
|
||||
import { ActionScreenshot } from "@/routes/tasks/detail/ActionScreenshot";
|
||||
import { statusIsFinalized } from "@/routes/tasks/types";
|
||||
import { useWorkflowRunQuery } from "../hooks/useWorkflowRunQuery";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useWorkflowRunWithWorkflowQuery } from "../hooks/useWorkflowRunWithWorkflowQuery";
|
||||
import { useWorkflowRunTimelineQuery } from "../hooks/useWorkflowRunTimelineQuery";
|
||||
import {
|
||||
isAction,
|
||||
@@ -37,17 +36,16 @@ export type WorkflowRunOverviewActiveElement =
|
||||
function WorkflowRunOverview() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const active = searchParams.get("active");
|
||||
const { workflowPermanentId } = useParams<{
|
||||
workflowPermanentId: string;
|
||||
}>();
|
||||
const queryClient = useQueryClient();
|
||||
const { data: workflowRun, isLoading: workflowRunIsLoading } =
|
||||
useWorkflowRunQuery();
|
||||
useWorkflowRunWithWorkflowQuery();
|
||||
|
||||
const { data: workflowRunTimeline, isLoading: workflowRunTimelineIsLoading } =
|
||||
useWorkflowRunTimelineQuery();
|
||||
|
||||
const workflowRunId = workflowRun?.workflow_run_id;
|
||||
const workflow = workflowRun?.workflow;
|
||||
const workflowPermanentId = workflow?.workflow_permanent_id;
|
||||
|
||||
const invalidateQueries = useCallback(() => {
|
||||
if (workflowRunId) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useWorkflowRunQuery } from "../hooks/useWorkflowRunQuery";
|
||||
import { useWorkflowRunWithWorkflowQuery } from "../hooks/useWorkflowRunWithWorkflowQuery";
|
||||
import { artifactApiBaseUrl } from "@/util/env";
|
||||
|
||||
function WorkflowRunRecording() {
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const { data: workflowRun } = useWorkflowRunWithWorkflowQuery();
|
||||
let recordingURL = workflowRun?.recording_url;
|
||||
if (recordingURL?.startsWith("file://")) {
|
||||
recordingURL = `${artifactApiBaseUrl}/artifact/recording?path=${recordingURL.slice(7)}`;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { statusIsFinalized, statusIsNotFinalized } from "@/routes/tasks/types";
|
||||
import { cn } from "@/util/utils";
|
||||
import { DotFilledIcon } from "@radix-ui/react-icons";
|
||||
import { useWorkflowRunQuery } from "../hooks/useWorkflowRunQuery";
|
||||
import { useWorkflowRunWithWorkflowQuery } from "../hooks/useWorkflowRunWithWorkflowQuery";
|
||||
import { useWorkflowRunTimelineQuery } from "../hooks/useWorkflowRunTimelineQuery";
|
||||
import {
|
||||
isBlockItem,
|
||||
@@ -36,7 +36,7 @@ function WorkflowRunTimeline({
|
||||
onBlockItemSelected,
|
||||
}: Props) {
|
||||
const { data: workflowRun, isLoading: workflowRunIsLoading } =
|
||||
useWorkflowRunQuery();
|
||||
useWorkflowRunWithWorkflowQuery();
|
||||
|
||||
const { data: workflowRunTimeline, isLoading: workflowRunTimelineIsLoading } =
|
||||
useWorkflowRunTimelineQuery();
|
||||
|
||||
Reference in New Issue
Block a user