Skip to content

Commit 0b2dae3

Browse files
normanrzfm3
authored andcommitted
add links for skipped tasks in voxelytics workflow reports (#8006)
* add links for skipped tasks in voxelytics workflow reports * changelog * comments * styling * add evolution and comment --------- Co-authored-by: Florian M <[email protected]>
1 parent cc49f91 commit 0b2dae3

File tree

11 files changed

+92
-22
lines changed

11 files changed

+92
-22
lines changed

CHANGELOG.unreleased.md

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
3232
- The alignment job is in a separate tab of the "AI Tools" now. The "Align Sections" AI job now supports including manually created matches between adjacent section given as skeletons. [#7967](https://github.com/scalableminds/webknossos/pull/7967)
3333
- Added `api.tracing.createNode(position, options)`` to the front-end API. [#7998](https://github.com/scalableminds/webknossos/pull/7998)
3434
- Added a feature to register all segments for a given bounding box at once via the context menu of the bounding box. [#7979](https://github.com/scalableminds/webknossos/pull/7979)
35+
- Added links in the workflow report for skipped tasks to the corresponding workflow view. [#8006](https://github.com/scalableminds/webknossos/pull/8006)
3536

3637
### Changed
3738
- Replaced skeleton tab component with antd's `<Tree />`component. Added support for selecting tree ranges with SHIFT. [#7819](https://github.com/scalableminds/webknossos/pull/7819)

app/controllers/VoxelyticsController.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package controllers
22

3-
import play.silhouette.api.Silhouette
4-
import play.silhouette.api.actions.SecuredRequest
53
import com.scalableminds.util.time.Instant
64
import com.scalableminds.util.tools.{Fox, FoxImplicits}
75
import models.organization.OrganizationDAO
86
import models.voxelytics._
97
import play.api.libs.json._
108
import play.api.mvc._
9+
import play.silhouette.api.Silhouette
10+
import play.silhouette.api.actions.SecuredRequest
1111
import security.WkEnv
1212
import utils.{ObjectId, WkConf}
1313

@@ -138,7 +138,7 @@ class VoxelyticsController @Inject()(
138138
combinedTaskRuns <- voxelyticsDAO.findCombinedTaskRuns(sortedRuns.map(_.id), conf.staleTimeout)
139139

140140
// Fetch artifact data for task runs
141-
artifacts <- voxelyticsDAO.findArtifacts(sortedRuns.map(_.id), conf.staleTimeout)
141+
artifacts <- voxelyticsDAO.findArtifacts(request.identity, sortedRuns.map(_.id), conf.staleTimeout)
142142

143143
// Fetch task configs
144144
tasks <- voxelyticsDAO.findTasks(mostRecentRun.id)

app/models/voxelytics/VoxelyticsDAO.scala

+31-9
Original file line numberDiff line numberDiff line change
@@ -108,23 +108,26 @@ class VoxelyticsDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContex
108108
) running_chunks ON running_chunks.name = tc.name AND running_chunks.executionId = tc.executionId AND running_chunks.chunkName = tc.chunkName
109109
WHERE TRUE ${taskName.map(t => q"AND tc.name = $t").getOrElse(q"")}"""
110110

111-
private def latestCompleteTaskQ(runIds: List[ObjectId], taskName: Option[String], staleTimeout: FiniteDuration) =
111+
private def latestCompleteOrSkippedTaskQ(runIds: List[ObjectId],
112+
taskName: Option[String],
113+
staleTimeout: FiniteDuration) =
112114
q"""SELECT
113115
DISTINCT ON (t.name)
114116
t.name,
115-
t._id
117+
t._id,
118+
ts.state
116119
FROM (${tasksWithStateQ(staleTimeout)}) ts
117120
JOIN webknossos.voxelytics_tasks t ON t._id = ts._id
118121
WHERE
119-
ts.state = ${VoxelyticsRunState.COMPLETE}
122+
ts.state IN ${SqlToken.tupleFromValues(VoxelyticsRunState.COMPLETE, VoxelyticsRunState.SKIPPED)}
120123
AND t._run IN ${SqlToken.tupleFromList(runIds)}
121124
${taskName.map(t => q" AND t.name = $t").getOrElse(q"")}
122125
ORDER BY t.name, ts.beginTime DESC"""
123126

124-
def findArtifacts(runIds: List[ObjectId], staleTimeout: FiniteDuration): Fox[List[ArtifactEntry]] =
127+
def findArtifacts(currentUser: User, runIds: List[ObjectId], staleTimeout: FiniteDuration): Fox[List[ArtifactEntry]] =
125128
for {
126129
r <- run(q"""
127-
WITH latest_complete_tasks AS (${latestCompleteTaskQ(runIds, None, staleTimeout)})
130+
WITH latest_complete_tasks AS (${latestCompleteOrSkippedTaskQ(runIds, None, staleTimeout)})
128131
SELECT
129132
a._id,
130133
a._task,
@@ -134,10 +137,25 @@ class VoxelyticsDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContex
134137
a.inodeCount,
135138
a.version,
136139
a.metadata,
137-
t.name AS taskName
140+
t.name AS taskName,
141+
o.workflow_hash AS other_workflow_hash,
142+
o._run AS other_run
138143
FROM webknossos.voxelytics_artifacts a
139144
JOIN latest_complete_tasks t ON t._id = a._task
140-
""".as[(String, String, String, String, Long, Long, String, String, String)])
145+
LEFT JOIN ( -- when the task is skipped, the artifact from the producing task run is joined for linking
146+
-- Fan out is not possible because of distinct path selection in combination with unique (_task, path) constraint
147+
SELECT
148+
DISTINCT ON (a.path)
149+
a.path path,
150+
r.workflow_hash workflow_hash,
151+
r._id _run
152+
FROM webknossos.voxelytics_artifacts a
153+
JOIN webknossos.voxelytics_tasks t ON a._task = t._id
154+
JOIN (${tasksWithStateQ(staleTimeout)}) ts ON ts._id = t._id AND ts.state = ${VoxelyticsRunState.COMPLETE}
155+
JOIN (${visibleRunsQ(currentUser, allowUnlisted = true)}) r ON r._id = t._run
156+
ORDER BY a.path, ts.beginTime DESC
157+
) o ON o.path = a.path AND t.state = ${VoxelyticsRunState.SKIPPED}
158+
""".as[(String, String, String, String, Long, Long, String, String, String, Option[String], Option[String])])
141159
} yield
142160
r.toList.map(
143161
row =>
@@ -150,7 +168,11 @@ class VoxelyticsDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContex
150168
inodeCount = row._6,
151169
version = row._7,
152170
metadata = Json.parse(row._8).as[JsObject],
153-
taskName = row._9
171+
taskName = row._9,
172+
foreignWorkflow = (row._10, row._11) match {
173+
case (Some(workflow_hash), Some(run)) => Some((workflow_hash, run))
174+
case _ => None
175+
}
154176
))
155177

156178
def findTasks(runId: ObjectId): Fox[List[TaskEntry]] =
@@ -725,7 +747,7 @@ class VoxelyticsDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContex
725747
staleTimeout: FiniteDuration): Fox[List[ArtifactChecksumEntry]] =
726748
for {
727749
r <- run(q"""
728-
WITH latest_complete_tasks AS (${latestCompleteTaskQ(runIds, Some(taskName), staleTimeout)})
750+
WITH latest_complete_tasks AS (${latestCompleteOrSkippedTaskQ(runIds, Some(taskName), staleTimeout)})
729751
SELECT
730752
t.name,
731753
a.name,

app/models/voxelytics/VoxelyticsService.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ case class ArtifactEntry(artifactId: ObjectId,
100100
inodeCount: Long,
101101
version: String,
102102
metadata: JsObject,
103-
taskName: String)
103+
taskName: String,
104+
foreignWorkflow: Option[(String, String)])
104105

105106
object ArtifactEntry {
106107
implicit val jsonFormat: OFormat[ArtifactEntry] = Json.format[ArtifactEntry]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
START TRANSACTION;
2+
3+
do $$ begin ASSERT (select schemaVersion from webknossos.releaseInformation) = 117, 'Previous schema version mismatch'; end; $$ LANGUAGE plpgsql;
4+
5+
ALTER TABLE "webknossos"."voxelytics_artifacts" ADD CONSTRAINT "voxelytics_artifacts__task_path_key" UNIQUE ("_task","path");
6+
7+
UPDATE webknossos.releaseInformation SET schemaVersion = 118;
8+
9+
COMMIT TRANSACTION;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
START TRANSACTION;
2+
3+
do $$ begin ASSERT (select schemaVersion from webknossos.releaseInformation) = 118, 'Previous schema version mismatch'; end; $$ LANGUAGE plpgsql;
4+
5+
ALTER TABLE "webknossos"."voxelytics_artifacts" DROP CONSTRAINT "voxelytics_artifacts__task_path_key";
6+
7+
UPDATE webknossos.releaseInformation SET schemaVersion = 117;
8+
9+
COMMIT TRANSACTION;

frontend/javascripts/admin/voxelytics/artifacts_view.tsx

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import React from "react";
22
import { JSONTree } from "react-json-tree";
33
import { Button, Card, message } from "antd";
4-
import { CopyOutlined } from "@ant-design/icons";
4+
import { CopyOutlined, ExportOutlined } from "@ant-design/icons";
55
import { VoxelyticsArtifactConfig } from "types/api_flow_types";
66
import { getVoxelyticsArtifactChecksums } from "admin/admin_rest_api";
77
import { formatCountToDataAmountUnit } from "libs/format_utils";
88
import { copyToClipboad, isObjectEmpty, useTheme } from "./utils";
9+
import { Link } from "react-router-dom";
910

1011
export function renderArtifactPath(artifact: VoxelyticsArtifactConfig) {
1112
return (
@@ -126,7 +127,19 @@ function ArtifactsView({
126127
function renderArtifact(artifactName: string, artifact: VoxelyticsArtifactConfig) {
127128
const title = (
128129
<>
129-
<span>{artifactName}</span>
130+
<span>
131+
{artifact.foreignWorkflow != null ? (
132+
<>
133+
<Link
134+
to={`/workflows/${artifact.foreignWorkflow[0]}?runId=${artifact.foreignWorkflow[1]}`}
135+
>
136+
{artifactName} <ExportOutlined />
137+
</Link>
138+
</>
139+
) : (
140+
artifactName
141+
)}
142+
</span>
130143
<span style={{ fontSize: "10px", marginLeft: 10 }}>
131144
\\ version {artifact.version}, {formatCountToDataAmountUnit(artifact.fileSize)},{" "}
132145
{artifact.inodeCount.toLocaleString()} inodes

frontend/javascripts/admin/voxelytics/task_list_view.tsx

+19-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
ExclamationCircleOutlined,
2525
LeftOutlined,
2626
FieldTimeOutlined,
27+
ExportOutlined,
2728
} from "@ant-design/icons";
2829
import MiniSearch from "minisearch";
2930

@@ -512,6 +513,12 @@ export default function TaskListView({
512513
return undefined;
513514
}
514515

516+
const taskArtifacts = report.artifacts[task.taskName] || {};
517+
let foreignWorkflow: null | [string, string] = null;
518+
if (taskInfo.state === VoxelyticsRunState.SKIPPED) {
519+
foreignWorkflow = Object.values(taskArtifacts)[0]?.foreignWorkflow ?? null;
520+
}
521+
515522
return {
516523
key: task.taskName,
517524
id: `task-panel-${task.taskName}`,
@@ -527,7 +534,17 @@ export default function TaskListView({
527534
backgroundColor: colorHasher.hex(task.task),
528535
}}
529536
/>
530-
{task.taskName}
537+
{foreignWorkflow != null ? (
538+
<>
539+
<Link to={`/workflows/${foreignWorkflow[0]}?runId=${foreignWorkflow[1]}`}>
540+
{task.taskName}
541+
&nbsp;
542+
<ExportOutlined />
543+
</Link>
544+
</>
545+
) : (
546+
task.taskName
547+
)}
531548
<wbr />
532549
{task.config.name != null && <span className="task-panel-name">{task.config.name}</span>}
533550
<wbr />
@@ -542,7 +559,7 @@ export default function TaskListView({
542559
workflowHash={report.workflow.hash}
543560
runId={singleRunId}
544561
task={task}
545-
artifacts={report.artifacts[task.taskName] || []}
562+
artifacts={taskArtifacts}
546563
dag={report.dag}
547564
taskInfo={taskInfo}
548565
onSelectTask={handleSelectTask}

frontend/javascripts/types/api_flow_types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,7 @@ export type VoxelyticsArtifactConfig = {
902902
iframes: Record<string, string>;
903903
links: Record<string, string>;
904904
};
905+
foreignWorkflow: [string, string] | null;
905906
};
906907

907908
export type VoxelyticsRunInfo = {

frontend/stylesheets/main.less

-4
Original file line numberDiff line numberDiff line change
@@ -427,10 +427,6 @@ button.narrow {
427427
.voxelytics-view {
428428
min-width: 1300px;
429429

430-
.tasks-header {
431-
border-bottom: 1px solid var(--ant-color-border);
432-
}
433-
434430
.task-panel {
435431
height: calc(100vh - 100px);
436432
position: relative;

tools/postgres/schema.sql

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ CREATE TABLE webknossos.releaseInformation (
2020
schemaVersion BIGINT NOT NULL
2121
);
2222

23-
INSERT INTO webknossos.releaseInformation(schemaVersion) values(117);
23+
INSERT INTO webknossos.releaseInformation(schemaVersion) values(118);
2424
COMMIT TRANSACTION;
2525

2626

@@ -594,6 +594,7 @@ CREATE TABLE webknossos.voxelytics_artifacts(
594594
metadata JSONB,
595595
PRIMARY KEY (_id),
596596
UNIQUE (_task, name),
597+
UNIQUE (_task, path),
597598
CONSTRAINT metadataIsJsonObject CHECK(jsonb_typeof(metadata) = 'object')
598599
);
599600

0 commit comments

Comments
 (0)