Skip to content

Commit 0f20ccb

Browse files
committed
Merge branch 'master' of github.com:scalableminds/webknossos into split-setting-components
2 parents 6be72fa + 1deb4d1 commit 0f20ccb

File tree

4 files changed

+71
-66
lines changed

4 files changed

+71
-66
lines changed

frontend/javascripts/admin/voxelytics/task_list_view.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import {
3636
formatTimeInterval,
3737
formatTimeIntervalStrict,
3838
} from "libs/format_utils";
39-
import { useSearchParams, useUpdateEvery, useWkSelector } from "libs/react_hooks";
39+
import { useUpdateEvery, useWkSelector } from "libs/react_hooks";
4040
import { notEmpty } from "libs/utils";
4141
import MiniSearch from "minisearch";
4242
import React, { useEffect, useMemo, useState } from "react";
@@ -254,6 +254,7 @@ export default function TaskListView({
254254
isLoading,
255255
onToggleExpandedMetaTaskKey,
256256
onReload,
257+
runId,
257258
}: {
258259
report: VoxelyticsWorkflowReport;
259260
tasksWithHierarchy: Array<VoxelyticsTaskConfigWithHierarchy>;
@@ -262,10 +263,10 @@ export default function TaskListView({
262263
isLoading: boolean;
263264
onToggleExpandedMetaTaskKey: (v: string) => void;
264265
onReload: () => void;
266+
runId: string | null;
265267
}) {
266268
const { modal } = App.useApp();
267269
const [searchQuery, setSearchQuery] = useState("");
268-
const { runId } = useSearchParams();
269270
const navigate = useNavigate();
270271

271272
// expandedTask = state of the collapsible list
@@ -566,7 +567,9 @@ export default function TaskListView({
566567
}}
567568
/>
568569
{foreignWorkflow != null ? (
569-
<Link to={`/workflows/${foreignWorkflow[0]}?runId=${foreignWorkflow[1]}`}>
570+
<Link
571+
to={`/workflows/${foreignWorkflow[0]}/run/${encodeURIComponent(foreignWorkflow[1])}`}
572+
>
570573
{task.taskName}
571574
&nbsp;
572575
<ExportOutlined />
@@ -679,13 +682,12 @@ export default function TaskListView({
679682
</Button>
680683
<Select
681684
value={runId ?? ""}
682-
onChange={(value) =>
685+
onChange={(value) => {
686+
const basePath = `/workflows/${report.workflow.hash}`;
683687
navigate(
684-
value === ""
685-
? removeUrlParam(location, "runId")
686-
: addUrlParam(location, "runId", value),
687-
)
688-
}
688+
value === "" ? `${basePath}` : `${basePath}/run/${encodeURIComponent(value)}`,
689+
);
690+
}}
689691
popupMatchSelectWidth={false}
690692
styles={{
691693
root: {

frontend/javascripts/admin/voxelytics/workflow_list_view.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ export default function WorkflowListView() {
201201
{run.workflowName} ({run.workflowHash})
202202
</Link>
203203
) : (
204-
<Link to={`/workflows/${run.workflowHash}?runId=${encodeURIComponent(run.id)}`}>
204+
<Link to={`/workflows/${run.workflowHash}/run/${encodeURIComponent(run.id)}`}>
205205
{run.name}
206206
</Link>
207207
),

frontend/javascripts/admin/voxelytics/workflow_view.tsx

Lines changed: 38 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1+
import { useQuery } from "@tanstack/react-query";
12
import { isWorkflowAccessibleBySwitching } from "admin/api/organization";
23
import { getVoxelyticsWorkflow } from "admin/rest_api";
34
import BrainSpinner, { BrainSpinnerWithError } from "components/brain_spinner";
4-
import { usePolling, useSearchParams, useWkSelector } from "libs/react_hooks";
5-
import Toast from "libs/toast";
5+
import { useSearchParams, useWkSelector } from "libs/react_hooks";
66
import sortBy from "lodash-es/sortBy";
77
import { useEffect, useMemo, useState } from "react";
88
import { useParams } from "react-router-dom";
99
import {
10-
type APIOrganization,
1110
VoxelyticsRunState,
1211
type VoxelyticsTaskConfig,
1312
type VoxelyticsTaskConfigWithHierarchy,
@@ -22,12 +21,6 @@ import TabTitle from "viewer/view/components/tab_title_component";
2221
import TaskListView from "./task_list_view";
2322
import { VX_POLLING_INTERVAL } from "./utils";
2423

25-
type LoadingState =
26-
| { status: "PENDING" }
27-
| { status: "READY" }
28-
| { status: "LOADING" }
29-
| { status: "FAILED"; error: Error; organizationToSwitchTo?: APIOrganization };
30-
3124
function lexicographicalTopologicalSort(
3225
nodes: Array<VoxelyticsWorkflowDagNode>,
3326
edges: Array<VoxelyticsWorkflowDagEdge>,
@@ -318,12 +311,10 @@ function shouldCollapseId(id: string, expandedKeys: Record<string, boolean>): [s
318311
}
319312

320313
export default function WorkflowView() {
321-
const { workflowHash = "" } = useParams();
314+
const { workflowHash = "", runId } = useParams();
322315
const { metatask } = useSearchParams();
323316
const user = useWkSelector((state) => state.activeUser);
324317

325-
const [loadingState, setLoadingState] = useState<LoadingState>({ status: "PENDING" });
326-
const [report, setReport] = useState<VoxelyticsWorkflowReport | null>(null);
327318
// expandedMetaTaskKeys holds the meta tasks which should be expanded
328319
// in the left-side DAG. The right-side task listing will always show
329320
// all tasks (but in a hierarchical manner if meta tasks exist).
@@ -342,34 +333,36 @@ export default function WorkflowView() {
342333
setExpandedMetaTaskKeys(sortedKeys);
343334
};
344335

345-
async function loadData() {
346-
try {
347-
setLoadingState({ status: "LOADING" });
348-
let _report = parseReport(await getVoxelyticsWorkflow(workflowHash, null));
349-
if (metatask != null) {
350-
// If a meta task is passed via a GET parameter,
351-
// the entire report is filtered so that only the tasks of the given
352-
// meta task are shown (left-hand as well as right-hand side).
353-
_report = selectMetaTask(_report, metatask);
354-
}
355-
setReport(_report);
356-
setLoadingState({ status: "READY" });
357-
} catch (err) {
358-
try {
359-
const organization =
360-
user != null ? await isWorkflowAccessibleBySwitching(workflowHash) : null;
361-
setLoadingState({
362-
status: "FAILED",
363-
organizationToSwitchTo: organization ?? undefined,
364-
error: err as Error,
365-
});
366-
} catch (accessibleBySwitchingError) {
367-
Toast.error("Could not load workflow report.");
368-
console.error(accessibleBySwitchingError);
369-
setLoadingState({ status: "FAILED", error: accessibleBySwitchingError as Error });
370-
}
371-
}
372-
}
336+
const {
337+
data: report,
338+
isLoading,
339+
isError,
340+
refetch,
341+
} = useQuery({
342+
queryKey: ["voxelyticsWorkflow", workflowHash],
343+
queryFn: async () => await getVoxelyticsWorkflow(workflowHash, null),
344+
// If a meta task is passed via a URL parameter, the entire report is filtered so that only the
345+
// tasks of the given meta task are shown (left-hand as well as right-hand side).
346+
select: (data) => {
347+
const parsedReport = parseReport(data);
348+
return metatask != null ? selectMetaTask(parsedReport, metatask) : parsedReport;
349+
},
350+
refetchInterval: (query) => {
351+
const data = query.state.data;
352+
return data == null || data.runs.some((run) => run.state === VoxelyticsRunState.RUNNING)
353+
? (VX_POLLING_INTERVAL ?? false)
354+
: false;
355+
},
356+
staleTime: 0, // disable caching
357+
gcTime: 0, // disable garbage collection
358+
retry: false,
359+
});
360+
361+
const { data: accessibleOrganization } = useQuery({
362+
queryKey: ["voxelyticsWorkflowAccess", workflowHash],
363+
queryFn: () => isWorkflowAccessibleBySwitching(workflowHash),
364+
enabled: isError && user != null,
365+
});
373366

374367
useEffect(() => {
375368
if (report != null) {
@@ -391,23 +384,11 @@ export default function WorkflowView() {
391384
[report],
392385
);
393386

394-
usePolling(
395-
loadData,
396-
// Only poll while the workflow is still running, but also load a report
397-
// if it wasn't loaded before or the workflow hash changed
398-
report == null ||
399-
report.workflow.hash !== workflowHash ||
400-
report.runs.some((run) => run.state === VoxelyticsRunState.RUNNING)
401-
? VX_POLLING_INTERVAL
402-
: null,
403-
[workflowHash],
404-
);
405-
406-
if (loadingState.status === "FAILED" && user != null) {
387+
if (isError && user != null) {
407388
return (
408389
<BrainSpinnerWithError
409390
gotUnhandledError={false}
410-
organizationToSwitchTo={loadingState.organizationToSwitchTo}
391+
organizationToSwitchTo={accessibleOrganization ?? undefined}
411392
entity="workflow"
412393
/>
413394
);
@@ -425,8 +406,9 @@ export default function WorkflowView() {
425406
expandedMetaTaskKeys={expandedMetaTaskKeys}
426407
onToggleExpandedMetaTaskKey={handleToggleExpandedMetaTaskKey}
427408
openMetatask={metatask}
428-
onReload={loadData}
429-
isLoading={loadingState.status === "LOADING"}
409+
runId={runId ?? null}
410+
onReload={refetch}
411+
isLoading={isLoading}
430412
/>
431413
</div>
432414
);

frontend/javascripts/router/router.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,27 @@ const routes = createRoutesFromElements(
457457
/>
458458
<Route
459459
path="/workflows/:workflowHash"
460+
loader={({ params, request }) => {
461+
const url = new URL(request.url);
462+
const runId = url.searchParams.get("runId");
463+
if (runId) {
464+
url.searchParams.delete("runId");
465+
const search = url.searchParams.toString();
466+
467+
return redirect(
468+
`/workflows/${params.workflowHash}/run/${encodeURIComponent(runId)}${search ? `?${search}` : ""}`,
469+
);
470+
}
471+
return null;
472+
}}
473+
element={
474+
<SecuredRoute>
475+
<AsyncWorkflowView />
476+
</SecuredRoute>
477+
}
478+
/>
479+
<Route
480+
path="/workflows/:workflowHash/run/:runId"
460481
element={
461482
<SecuredRoute>
462483
<AsyncWorkflowView />

0 commit comments

Comments
 (0)