Jon/sky 6395 refresh code while workflow is running (#3444)

This commit is contained in:
Jonathan Dobson
2025-09-16 14:47:09 -04:00
committed by GitHub
parent ca84e57665
commit d57fccfaaf
4 changed files with 98 additions and 40 deletions

View File

@@ -143,7 +143,7 @@ function Workspace({
? "" ? ""
: cacheKeyValueParam : cacheKeyValueParam
? cacheKeyValueParam ? cacheKeyValueParam
: constructCacheKeyValue(cacheKey, workflow), : constructCacheKeyValue({ codeKey: cacheKey, workflow }),
); );
useEffect(() => { useEffect(() => {

View File

@@ -1,4 +1,5 @@
import { WorkflowApiResponse } from "@/routes/workflows/types/workflowTypes"; import { WorkflowApiResponse } from "@/routes/workflows/types/workflowTypes";
import { WorkflowRunStatusApiResponse } from "@/api/types";
import { import {
isDisplayedInWorkflowEditor, isDisplayedInWorkflowEditor,
WorkflowEditorParameterTypes, WorkflowEditorParameterTypes,
@@ -113,23 +114,29 @@ const getInitialParameters = (workflow: WorkflowApiResponse) => {
/** /**
* Attempt to construct a valid code key value from the workflow parameters. * Attempt to construct a valid code key value from the workflow parameters.
*/ */
const constructCacheKeyValue = ( const constructCacheKeyValue = (opts: {
codeKey: string, codeKey: string;
workflow?: WorkflowApiResponse, workflow?: WorkflowApiResponse;
) => { workflowRun?: WorkflowRunStatusApiResponse;
}) => {
const { workflow, workflowRun } = opts;
let codeKey = opts.codeKey;
if (!workflow) { if (!workflow) {
return ""; return "";
} }
const workflowParameters = getInitialParameters(workflow) const workflowParameters = workflowRun
.filter((p) => p.parameterType === "workflow") ? workflowRun?.parameters ?? {}
.reduce( : getInitialParameters(workflow)
(acc, parameter) => { .filter((p) => p.parameterType === "workflow")
acc[parameter.key] = parameter.defaultValue; .reduce(
return acc; (acc, parameter) => {
}, acc[parameter.key] = parameter.defaultValue;
{} as Record<string, unknown>, return acc;
); },
{} as Record<string, unknown>,
);
for (const [name, value] of Object.entries(workflowParameters)) { for (const [name, value] of Object.entries(workflowParameters)) {
if (value === null || value === undefined || value === "") { if (value === null || value === undefined || value === "") {

View File

@@ -7,17 +7,25 @@ type Props = {
cacheKey?: string; cacheKey?: string;
cacheKeyValue?: string; cacheKeyValue?: string;
workflowPermanentId?: string; workflowPermanentId?: string;
pollIntervalMs?: number;
}; };
function useBlockScriptsQuery({ function useBlockScriptsQuery({
cacheKey, cacheKey,
cacheKeyValue, cacheKeyValue,
workflowPermanentId, workflowPermanentId,
pollIntervalMs,
}: Props) { }: Props) {
const credentialGetter = useCredentialGetter(); const credentialGetter = useCredentialGetter();
return useQuery<{ [blockName: string]: string }>({ return useQuery<{ [blockName: string]: string }>({
queryKey: ["block-scripts", workflowPermanentId, cacheKey, cacheKeyValue], queryKey: [
"block-scripts",
workflowPermanentId,
cacheKey,
cacheKeyValue,
pollIntervalMs,
],
queryFn: async () => { queryFn: async () => {
const client = await getClient(credentialGetter, "sans-api-v1"); const client = await getClient(credentialGetter, "sans-api-v1");
@@ -30,6 +38,12 @@ function useBlockScriptsQuery({
return result.blocks; return result.blocks;
}, },
refetchInterval: () => {
if (!pollIntervalMs || pollIntervalMs === 0) {
return false;
}
return Math.max(2000, pollIntervalMs);
},
enabled: !!workflowPermanentId, enabled: !!workflowPermanentId,
}); });
} }

View File

@@ -11,10 +11,12 @@ import {
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { HelpTooltip } from "@/components/HelpTooltip"; import { HelpTooltip } from "@/components/HelpTooltip";
import { statusIsFinalized } from "@/routes/tasks/types";
import { CodeEditor } from "@/routes/workflows/components/CodeEditor"; import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
import { useBlockScriptsQuery } from "@/routes/workflows/hooks/useBlockScriptsQuery"; import { useBlockScriptsQuery } from "@/routes/workflows/hooks/useBlockScriptsQuery";
import { useCacheKeyValuesQuery } from "@/routes/workflows/hooks/useCacheKeyValuesQuery"; import { useCacheKeyValuesQuery } from "@/routes/workflows/hooks/useCacheKeyValuesQuery";
import { useWorkflowQuery } from "@/routes/workflows/hooks/useWorkflowQuery"; import { useWorkflowQuery } from "@/routes/workflows/hooks/useWorkflowQuery";
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
import { constructCacheKeyValue } from "@/routes/workflows/editor/utils"; import { constructCacheKeyValue } from "@/routes/workflows/editor/utils";
import { WorkflowApiResponse } from "@/routes/workflows/types/workflowTypes"; import { WorkflowApiResponse } from "@/routes/workflows/types/workflowTypes";
@@ -72,12 +74,15 @@ function WorkflowRunCode(props?: Props) {
const showCacheKeyValueSelector = props?.showCacheKeyValueSelector ?? false; const showCacheKeyValueSelector = props?.showCacheKeyValueSelector ?? false;
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { workflowPermanentId } = useParams(); const { workflowPermanentId } = useParams();
const { data: workflowRun } = useWorkflowRunQuery();
const { data: workflow } = useWorkflowQuery({ const { data: workflow } = useWorkflowQuery({
workflowPermanentId, workflowPermanentId,
}); });
const cacheKey = workflow?.cache_key ?? ""; const cacheKey = workflow?.cache_key ?? "";
const [cacheKeyValue, setCacheKeyValue] = useState( const [cacheKeyValue, setCacheKeyValue] = useState(
cacheKey === "" ? "" : constructCacheKeyValue(cacheKey, workflow), cacheKey === ""
? ""
: constructCacheKeyValue({ codeKey: cacheKey, workflow, workflowRun }),
); );
const { data: cacheKeyValues } = useCacheKeyValuesQuery({ const { data: cacheKeyValues } = useCacheKeyValuesQuery({
cacheKey, cacheKey,
@@ -85,13 +90,25 @@ function WorkflowRunCode(props?: Props) {
page: 1, page: 1,
workflowPermanentId, workflowPermanentId,
}); });
const isFinalized = workflowRun ? statusIsFinalized(workflowRun) : null;
const parameters = workflowRun?.parameters;
const { data: blockScripts } = useBlockScriptsQuery({
cacheKey,
cacheKeyValue,
workflowPermanentId,
pollIntervalMs: !isFinalized ? 3000 : undefined,
});
const orderedBlockLabels = getOrderedBlockLabels(workflow);
const code = getCode(orderedBlockLabels, blockScripts).join("");
useEffect(() => { useEffect(() => {
setCacheKeyValue( setCacheKeyValue(
cacheKeyValues?.values[0] ?? constructCacheKeyValue(cacheKey, workflow), constructCacheKeyValue({ codeKey: cacheKey, workflow, workflowRun }) ??
cacheKeyValues?.values[0],
); );
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [cacheKeyValues, setCacheKeyValue, workflow]); }, [cacheKeyValues, parameters, setCacheKeyValue, workflow, workflowRun]);
useEffect(() => { useEffect(() => {
queryClient.invalidateQueries({ queryClient.invalidateQueries({
@@ -104,16 +121,13 @@ function WorkflowRunCode(props?: Props) {
], ],
}); });
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [workflow]); }, [queryClient, workflow]);
const { data: blockScripts } = useBlockScriptsQuery({ useEffect(() => {
cacheKey, queryClient.invalidateQueries({
cacheKeyValue, queryKey: ["block-scripts", workflowPermanentId, cacheKey, cacheKeyValue],
workflowPermanentId, });
}); }, [queryClient, workflowRun, workflowPermanentId, cacheKey, cacheKeyValue]);
const orderedBlockLabels = getOrderedBlockLabels(workflow);
const code = getCode(orderedBlockLabels, blockScripts).join("");
if (code.length === 0) { if (code.length === 0) {
return ( return (
@@ -123,10 +137,7 @@ function WorkflowRunCode(props?: Props) {
); );
} }
if ( if (!showCacheKeyValueSelector || !cacheKey || cacheKey === "") {
!showCacheKeyValueSelector ||
(cacheKeyValues?.values ?? []).length <= 1
) {
return ( return (
<CodeEditor <CodeEditor
className="h-full overflow-y-scroll" className="h-full overflow-y-scroll"
@@ -139,14 +150,33 @@ function WorkflowRunCode(props?: Props) {
); );
} }
const cacheKeyValueSet = new Set([...(cacheKeyValues?.values ?? [])]);
const cacheKeyValueForWorkflowRun = constructCacheKeyValue({
codeKey: cacheKey,
workflow,
workflowRun,
});
if (cacheKeyValueForWorkflowRun) {
cacheKeyValueSet.add(cacheKeyValueForWorkflowRun);
}
return ( return (
<div className="flex h-full w-full flex-col items-end justify-center gap-2"> <div className="flex h-full w-full flex-col items-end justify-center gap-2">
<div className="flex w-[20rem] gap-4"> <div className="flex w-[20rem] gap-4">
<div className="flex items-center justify-around gap-2"> <div className="flex items-center justify-around gap-2">
<Label className="w-[7rem]">Code Cache Key</Label> <Label className="w-[7rem]">Code Key Value</Label>
<HelpTooltip content="Which generated (& cached) code to view." /> <HelpTooltip
content={
!isFinalized
? "The code key value the generated code is being stored under."
: "Which generated (& cached) code to view."
}
/>
</div> </div>
<Select <Select
disabled={!isFinalized}
value={cacheKeyValue} value={cacheKeyValue}
onValueChange={(v: string) => setCacheKeyValue(v)} onValueChange={(v: string) => setCacheKeyValue(v)}
> >
@@ -154,13 +184,20 @@ function WorkflowRunCode(props?: Props) {
<SelectValue placeholder="Code Key Value" /> <SelectValue placeholder="Code Key Value" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{(cacheKeyValues?.values ?? []).map((value) => { {Array.from(cacheKeyValueSet)
return ( .sort()
<SelectItem key={value} value={value}> .map((value) => {
{value} return (
</SelectItem> <SelectItem key={value} value={value}>
); {value === cacheKeyValueForWorkflowRun &&
})} isFinalized === true ? (
<span className="underline">{value}</span>
) : (
value
)}
</SelectItem>
);
})}
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>