Jon/browser stream component (#2808)
This commit is contained in:
@@ -1,21 +1,22 @@
|
|||||||
import { Status } from "@/api/types";
|
import { Status } from "@/api/types";
|
||||||
import { useWorkflowRunQuery } from "../hooks/useWorkflowRunQuery";
|
|
||||||
import { useEffect, useState, useRef, useCallback } from "react";
|
import { useEffect, useState, useRef, useCallback } from "react";
|
||||||
import { HandIcon, PlayIcon } from "@radix-ui/react-icons";
|
import { HandIcon, PlayIcon } from "@radix-ui/react-icons";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { statusIsNotFinalized } from "@/routes/tasks/types";
|
import { statusIsNotFinalized } from "@/routes/tasks/types";
|
||||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
import { envCredential } from "@/util/env";
|
import { envCredential } from "@/util/env";
|
||||||
import { toast } from "@/components/ui/use-toast";
|
import { toast } from "@/components/ui/use-toast";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
|
||||||
import RFB from "@novnc/novnc/lib/rfb.js";
|
import RFB from "@novnc/novnc/lib/rfb.js";
|
||||||
import { environment } from "@/util/env";
|
import { environment } from "@/util/env";
|
||||||
import { cn } from "@/util/utils";
|
import { cn } from "@/util/utils";
|
||||||
import { useClientIdStore } from "@/store/useClientIdStore";
|
import { useClientIdStore } from "@/store/useClientIdStore";
|
||||||
|
import type {
|
||||||
|
TaskApiResponse,
|
||||||
|
WorkflowRunStatusApiResponse,
|
||||||
|
} from "@/api/types";
|
||||||
|
|
||||||
import "./workflow-run-stream-vnc.css";
|
import "./browser-stream.css";
|
||||||
|
|
||||||
const wssBaseUrl = import.meta.env.VITE_WSS_BASE_URL;
|
const wssBaseUrl = import.meta.env.VITE_WSS_BASE_URL;
|
||||||
|
|
||||||
@@ -29,13 +30,38 @@ interface CommandCedeControl {
|
|||||||
|
|
||||||
type Command = CommandTakeControl | CommandCedeControl;
|
type Command = CommandTakeControl | CommandCedeControl;
|
||||||
|
|
||||||
function WorkflowRunStreamVnc() {
|
type Props = {
|
||||||
const { data: workflowRun } = useWorkflowRunQuery();
|
task?: {
|
||||||
|
run: TaskApiResponse;
|
||||||
|
};
|
||||||
|
workflow?: {
|
||||||
|
run: WorkflowRunStatusApiResponse;
|
||||||
|
};
|
||||||
|
// --
|
||||||
|
onClose?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
const { workflowRunId, workflowPermanentId } = useParams<{
|
function BrowserStream({
|
||||||
workflowRunId: string;
|
task = undefined,
|
||||||
workflowPermanentId: string;
|
workflow = undefined,
|
||||||
}>();
|
// --
|
||||||
|
onClose,
|
||||||
|
}: Props) {
|
||||||
|
let showStream: boolean = false;
|
||||||
|
let runId: string;
|
||||||
|
let entity: "task" | "workflow";
|
||||||
|
|
||||||
|
if (task) {
|
||||||
|
runId = task.run.task_id;
|
||||||
|
showStream = statusIsNotFinalized(task.run);
|
||||||
|
entity = "task";
|
||||||
|
} else if (workflow) {
|
||||||
|
runId = workflow.run.workflow_run_id;
|
||||||
|
showStream = statusIsNotFinalized(workflow.run);
|
||||||
|
entity = "workflow";
|
||||||
|
} else {
|
||||||
|
throw new Error("No task or workflow provided");
|
||||||
|
}
|
||||||
|
|
||||||
const [commandSocket, setCommandSocket] = useState<WebSocket | null>(null);
|
const [commandSocket, setCommandSocket] = useState<WebSocket | null>(null);
|
||||||
const [userIsControlling, setUserIsControlling] = useState<boolean>(false);
|
const [userIsControlling, setUserIsControlling] = useState<boolean>(false);
|
||||||
@@ -46,8 +72,8 @@ function WorkflowRunStreamVnc() {
|
|||||||
useState(0);
|
useState(0);
|
||||||
const prevCommandConnectedRef = useRef<boolean>(false);
|
const prevCommandConnectedRef = useRef<boolean>(false);
|
||||||
const [isCommandConnected, setIsCommandConnected] = useState<boolean>(false);
|
const [isCommandConnected, setIsCommandConnected] = useState<boolean>(false);
|
||||||
const showStream = workflowRun && statusIsNotFinalized(workflowRun);
|
// goes up a level
|
||||||
const queryClient = useQueryClient();
|
// const queryClient = useQueryClient();
|
||||||
const [canvasContainer, setCanvasContainer] = useState<HTMLDivElement | null>(
|
const [canvasContainer, setCanvasContainer] = useState<HTMLDivElement | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
@@ -78,37 +104,28 @@ function WorkflowRunStreamVnc() {
|
|||||||
return `${params}`;
|
return `${params}`;
|
||||||
}, [clientId, credentialGetter]);
|
}, [clientId, credentialGetter]);
|
||||||
|
|
||||||
const invalidateQueries = useCallback(() => {
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: ["workflowRun", workflowPermanentId, workflowRunId],
|
|
||||||
});
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["workflowRuns"] });
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: ["workflowTasks", workflowRunId],
|
|
||||||
});
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["runs"] });
|
|
||||||
}, [queryClient, workflowPermanentId, workflowRunId]);
|
|
||||||
|
|
||||||
// effect for vnc disconnects only
|
// effect for vnc disconnects only
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (prevVncConnectedRef.current && !isVncConnected) {
|
if (prevVncConnectedRef.current && !isVncConnected) {
|
||||||
setVncDisconnectedTrigger((x) => x + 1);
|
setVncDisconnectedTrigger((x) => x + 1);
|
||||||
|
onClose?.();
|
||||||
}
|
}
|
||||||
prevVncConnectedRef.current = isVncConnected;
|
prevVncConnectedRef.current = isVncConnected;
|
||||||
}, [isVncConnected]);
|
}, [isVncConnected, onClose]);
|
||||||
|
|
||||||
// effect for command disconnects only
|
// effect for command disconnects only
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (prevCommandConnectedRef.current && !isCommandConnected) {
|
if (prevCommandConnectedRef.current && !isCommandConnected) {
|
||||||
setCommandDisconnectedTrigger((x) => x + 1);
|
setCommandDisconnectedTrigger((x) => x + 1);
|
||||||
|
onClose?.();
|
||||||
}
|
}
|
||||||
prevCommandConnectedRef.current = isCommandConnected;
|
prevCommandConnectedRef.current = isCommandConnected;
|
||||||
}, [isCommandConnected]);
|
}, [isCommandConnected, onClose]);
|
||||||
|
|
||||||
// vnc socket
|
// vnc socket
|
||||||
useEffect(
|
useEffect(
|
||||||
() => {
|
() => {
|
||||||
if (!showStream || !canvasContainer || !workflowRunId) {
|
if (!showStream || !canvasContainer || !runId) {
|
||||||
if (rfbRef.current) {
|
if (rfbRef.current) {
|
||||||
rfbRef.current.disconnect();
|
rfbRef.current.disconnect();
|
||||||
rfbRef.current = null;
|
rfbRef.current = null;
|
||||||
@@ -123,7 +140,16 @@ function WorkflowRunStreamVnc() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const wsParams = await getWebSocketParams();
|
const wsParams = await getWebSocketParams();
|
||||||
const vncUrl = `${wssBaseUrl}/stream/vnc/workflow_run/${workflowRunId}?${wsParams}`;
|
const vncUrl =
|
||||||
|
entity === "task"
|
||||||
|
? `${wssBaseUrl}/stream/vnc/task/${runId}?${wsParams}`
|
||||||
|
: entity === "workflow"
|
||||||
|
? `${wssBaseUrl}/stream/vnc/workflow_run/${runId}?${wsParams}`
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!vncUrl) {
|
||||||
|
throw new Error("No vnc url");
|
||||||
|
}
|
||||||
|
|
||||||
if (rfbRef.current) {
|
if (rfbRef.current) {
|
||||||
rfbRef.current.disconnect();
|
rfbRef.current.disconnect();
|
||||||
@@ -147,7 +173,6 @@ function WorkflowRunStreamVnc() {
|
|||||||
|
|
||||||
rfb.addEventListener("disconnect", async (/* e: RfbEvent */) => {
|
rfb.addEventListener("disconnect", async (/* e: RfbEvent */) => {
|
||||||
setIsVncConnected(false);
|
setIsVncConnected(false);
|
||||||
invalidateQueries();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,20 +190,35 @@ function WorkflowRunStreamVnc() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[
|
[
|
||||||
canvasContainer,
|
canvasContainer,
|
||||||
invalidateQueries,
|
|
||||||
showStream,
|
showStream,
|
||||||
vncDisconnectedTrigger, // will re-run on disconnects
|
vncDisconnectedTrigger, // will re-run on disconnects
|
||||||
workflowRunId,
|
runId,
|
||||||
|
entity,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
// command socket
|
// command socket
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!showStream || !canvasContainer || !runId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let ws: WebSocket | null = null;
|
let ws: WebSocket | null = null;
|
||||||
|
|
||||||
const connect = async () => {
|
const connect = async () => {
|
||||||
const wsParams = await getWebSocketParams();
|
const wsParams = await getWebSocketParams();
|
||||||
const commandUrl = `${wssBaseUrl}/stream/commands/workflow_run/${workflowRunId}?${wsParams}`;
|
|
||||||
|
const commandUrl =
|
||||||
|
entity === "task"
|
||||||
|
? `${wssBaseUrl}/stream/commands/task/${runId}?${wsParams}`
|
||||||
|
: entity === "workflow"
|
||||||
|
? `${wssBaseUrl}/stream/commands/workflow_run/${runId}?${wsParams}`
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!commandUrl) {
|
||||||
|
throw new Error("No command url");
|
||||||
|
}
|
||||||
|
|
||||||
ws = new WebSocket(commandUrl);
|
ws = new WebSocket(commandUrl);
|
||||||
|
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
@@ -188,7 +228,6 @@ function WorkflowRunStreamVnc() {
|
|||||||
|
|
||||||
ws.onclose = () => {
|
ws.onclose = () => {
|
||||||
setIsCommandConnected(false);
|
setIsCommandConnected(false);
|
||||||
invalidateQueries();
|
|
||||||
setCommandSocket(null);
|
setCommandSocket(null);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -203,10 +242,12 @@ function WorkflowRunStreamVnc() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
|
canvasContainer,
|
||||||
commandDisconnectedTrigger,
|
commandDisconnectedTrigger,
|
||||||
|
entity,
|
||||||
getWebSocketParams,
|
getWebSocketParams,
|
||||||
invalidateQueries,
|
runId,
|
||||||
workflowRunId,
|
showStream,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// effect to send a command when the user is controlling, vs not controlling
|
// effect to send a command when the user is controlling, vs not controlling
|
||||||
@@ -233,34 +274,41 @@ function WorkflowRunStreamVnc() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [userIsControlling, isCommandConnected]);
|
}, [userIsControlling, isCommandConnected]);
|
||||||
|
|
||||||
// Effect to show toast when workflow reaches a final state based on hook updates
|
// Effect to show toast when task or workflow reaches a final state based on hook updates
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (workflowRun) {
|
const run = task ? task.run : workflow ? workflow.run : null;
|
||||||
if (
|
|
||||||
workflowRun.status === Status.Failed ||
|
if (!run) {
|
||||||
workflowRun.status === Status.Terminated
|
return;
|
||||||
) {
|
|
||||||
// Only show toast if VNC is not connected or was never connected,
|
|
||||||
// to avoid double toasting if disconnect handler also triggers similar logic.
|
|
||||||
// However, the disconnect handler now primarily invalidates queries.
|
|
||||||
toast({
|
|
||||||
title: "Run Ended",
|
|
||||||
description: `The workflow run has ${workflowRun.status}.`,
|
|
||||||
variant: "destructive",
|
|
||||||
});
|
|
||||||
} else if (workflowRun.status === Status.Completed) {
|
|
||||||
toast({
|
|
||||||
title: "Run Completed",
|
|
||||||
description: "The workflow run has been completed.",
|
|
||||||
variant: "success",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [workflowRun, workflowRun?.status]);
|
|
||||||
|
const name = task ? "task" : workflow ? "workflow" : null;
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (run.status === Status.Failed || run.status === Status.Terminated) {
|
||||||
|
// Only show toast if VNC is not connected or was never connected,
|
||||||
|
// to avoid double toasting if disconnect handler also triggers similar logic.
|
||||||
|
// However, the disconnect handler now primarily invalidates queries.
|
||||||
|
toast({
|
||||||
|
title: "Run Ended",
|
||||||
|
description: `The ${name} run has ${run.status}.`,
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
} else if (run.status === Status.Completed) {
|
||||||
|
toast({
|
||||||
|
title: "Run Completed",
|
||||||
|
description: `The ${name} run has been completed.`,
|
||||||
|
variant: "success",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [task, workflow]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn("workflow-run-stream-vnc", {
|
className={cn("browser-stream", {
|
||||||
"user-is-controlling": userIsControlling,
|
"user-is-controlling": userIsControlling,
|
||||||
})}
|
})}
|
||||||
ref={setCanvasContainerRef}
|
ref={setCanvasContainerRef}
|
||||||
@@ -301,4 +349,4 @@ function WorkflowRunStreamVnc() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { WorkflowRunStreamVnc };
|
export { BrowserStream };
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
.workflow-run-stream-vnc {
|
.browser-stream {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -9,11 +9,11 @@
|
|||||||
transition: padding 0.2s ease-in-out;
|
transition: padding 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workflow-run-stream-vnc.user-is-controlling {
|
.browser-stream.user-is-controlling {
|
||||||
padding: 0rem;
|
padding: 0rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workflow-run-stream-vnc .overlay-container {
|
.browser-stream .overlay-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workflow-run-stream-vnc .overlay {
|
.browser-stream .overlay {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: auto;
|
height: auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -36,15 +36,15 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workflow-run-stream-vnc.user-is-controlling .overlay {
|
.browser-stream.user-is-controlling .overlay {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workflow-run-stream-vnc.user-is-controlling .overlay-container {
|
.browser-stream.user-is-controlling .overlay-container {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workflow-run-stream-vnc .take-control {
|
.browser-stream .take-control {
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
transition:
|
transition:
|
||||||
transform 0.2s ease-in-out,
|
transform 0.2s ease-in-out,
|
||||||
@@ -52,17 +52,17 @@
|
|||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workflow-run-stream-vnc .take-control:not(.hide):hover {
|
.browser-stream .take-control:not(.hide):hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workflow-run-stream-vnc .take-control.hide {
|
.browser-stream .take-control.hide {
|
||||||
transform: translateY(100%);
|
transform: translateY(100%);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workflow-run-stream-vnc .relinquish-control {
|
.browser-stream .relinquish-control {
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
transition:
|
transition:
|
||||||
transform 0.2s ease-in-out,
|
transform 0.2s ease-in-out,
|
||||||
@@ -71,17 +71,17 @@
|
|||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workflow-run-stream-vnc .relinquish-control:not(.hide):hover {
|
.browser-stream .relinquish-control:not(.hide):hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workflow-run-stream-vnc .relinquish-control.hide {
|
.browser-stream .relinquish-control.hide {
|
||||||
transform: translateY(100%);
|
transform: translateY(100%);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workflow-run-stream-vnc > div > canvas {
|
.browser-stream > div > canvas {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
animation: skyvern-anim-fadeIn 1s ease-in forwards;
|
animation: skyvern-anim-fadeIn 1s ease-in forwards;
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
import { ActionsApiResponse } from "@/api/types";
|
import { ActionsApiResponse } from "@/api/types";
|
||||||
|
import { BrowserStream } from "@/components/BrowserStream";
|
||||||
import { AspectRatio } from "@/components/ui/aspect-ratio";
|
import { AspectRatio } from "@/components/ui/aspect-ratio";
|
||||||
import { ActionScreenshot } from "@/routes/tasks/detail/ActionScreenshot";
|
import { ActionScreenshot } from "@/routes/tasks/detail/ActionScreenshot";
|
||||||
import { statusIsFinalized } from "@/routes/tasks/types";
|
import { statusIsFinalized } from "@/routes/tasks/types";
|
||||||
import { useWorkflowRunQuery } from "../hooks/useWorkflowRunQuery";
|
import { useWorkflowRunQuery } from "../hooks/useWorkflowRunQuery";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
import { useWorkflowRunTimelineQuery } from "../hooks/useWorkflowRunTimelineQuery";
|
import { useWorkflowRunTimelineQuery } from "../hooks/useWorkflowRunTimelineQuery";
|
||||||
import {
|
import {
|
||||||
isAction,
|
isAction,
|
||||||
@@ -14,10 +16,11 @@ import {
|
|||||||
import { ObserverThoughtScreenshot } from "./ObserverThoughtScreenshot";
|
import { ObserverThoughtScreenshot } from "./ObserverThoughtScreenshot";
|
||||||
import { WorkflowRunBlockScreenshot } from "./WorkflowRunBlockScreenshot";
|
import { WorkflowRunBlockScreenshot } from "./WorkflowRunBlockScreenshot";
|
||||||
import { WorkflowRunStream } from "./WorkflowRunStream";
|
import { WorkflowRunStream } from "./WorkflowRunStream";
|
||||||
import { WorkflowRunStreamVnc } from "./WorkflowRunStreamVnc";
|
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
import { findActiveItem } from "./workflowTimelineUtils";
|
import { findActiveItem } from "./workflowTimelineUtils";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
|
||||||
export type ActionItem = {
|
export type ActionItem = {
|
||||||
block: WorkflowRunBlock;
|
block: WorkflowRunBlock;
|
||||||
@@ -34,12 +37,33 @@ export type WorkflowRunOverviewActiveElement =
|
|||||||
function WorkflowRunOverview() {
|
function WorkflowRunOverview() {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const active = searchParams.get("active");
|
const active = searchParams.get("active");
|
||||||
|
const { workflowPermanentId } = useParams<{
|
||||||
|
workflowPermanentId: string;
|
||||||
|
}>();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const { data: workflowRun, isLoading: workflowRunIsLoading } =
|
const { data: workflowRun, isLoading: workflowRunIsLoading } =
|
||||||
useWorkflowRunQuery();
|
useWorkflowRunQuery();
|
||||||
|
|
||||||
const { data: workflowRunTimeline, isLoading: workflowRunTimelineIsLoading } =
|
const { data: workflowRunTimeline, isLoading: workflowRunTimelineIsLoading } =
|
||||||
useWorkflowRunTimelineQuery();
|
useWorkflowRunTimelineQuery();
|
||||||
|
|
||||||
|
const invalidateQueries = useCallback(() => {
|
||||||
|
if (workflowRun) {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: [
|
||||||
|
"workflowRun",
|
||||||
|
workflowPermanentId,
|
||||||
|
workflowRun.workflow_run_id,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["workflowRuns"] });
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["workflowTasks", workflowRun.workflow_run_id],
|
||||||
|
});
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["runs"] });
|
||||||
|
}
|
||||||
|
}, [queryClient, workflowPermanentId, workflowRun]);
|
||||||
|
|
||||||
if (workflowRunIsLoading || workflowRunTimelineIsLoading) {
|
if (workflowRunIsLoading || workflowRunTimelineIsLoading) {
|
||||||
return (
|
return (
|
||||||
<AspectRatio ratio={16 / 9}>
|
<AspectRatio ratio={16 / 9}>
|
||||||
@@ -64,7 +88,10 @@ function WorkflowRunOverview() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const streamingComponent = workflowRun.browser_session_id ? (
|
const streamingComponent = workflowRun.browser_session_id ? (
|
||||||
<WorkflowRunStreamVnc />
|
<BrowserStream
|
||||||
|
workflow={{ run: workflowRun }}
|
||||||
|
onClose={() => invalidateQueries()}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<WorkflowRunStream />
|
<WorkflowRunStream />
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user