Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add links for skipped tasks in voxelytics workflow reports #8006

Merged
merged 7 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- Added support for reading zstd-compressed zarr2 datasets [#7964](https://github.com/scalableminds/webknossos/pull/7964)
- 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)
- 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)
- Added links in the workflow report for skipped tasks to the corresponding workflow view. [#8006](https://github.com/scalableminds/webknossos/pull/8006)

### Changed
- 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)
Expand Down
6 changes: 3 additions & 3 deletions app/controllers/VoxelyticsController.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package controllers

import play.silhouette.api.Silhouette
import play.silhouette.api.actions.SecuredRequest
import com.scalableminds.util.time.Instant
import com.scalableminds.util.tools.{Fox, FoxImplicits}
import models.organization.OrganizationDAO
import models.voxelytics._
import play.api.libs.json._
import play.api.mvc._
import play.silhouette.api.Silhouette
import play.silhouette.api.actions.SecuredRequest
import security.WkEnv
import utils.{ObjectId, WkConf}

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

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

// Fetch task configs
tasks <- voxelyticsDAO.findTasks(mostRecentRun.id)
Expand Down
39 changes: 30 additions & 9 deletions app/models/voxelytics/VoxelyticsDAO.scala
Original file line number Diff line number Diff line change
Expand Up @@ -108,23 +108,26 @@ class VoxelyticsDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContex
) running_chunks ON running_chunks.name = tc.name AND running_chunks.executionId = tc.executionId AND running_chunks.chunkName = tc.chunkName
WHERE TRUE ${taskName.map(t => q"AND tc.name = $t").getOrElse(q"")}"""

private def latestCompleteTaskQ(runIds: List[ObjectId], taskName: Option[String], staleTimeout: FiniteDuration) =
private def latestCompleteOrSkippedTaskQ(runIds: List[ObjectId],
taskName: Option[String],
staleTimeout: FiniteDuration) =
q"""SELECT
DISTINCT ON (t.name)
t.name,
t._id
t._id,
ts.state
FROM (${tasksWithStateQ(staleTimeout)}) ts
JOIN webknossos.voxelytics_tasks t ON t._id = ts._id
WHERE
ts.state = ${VoxelyticsRunState.COMPLETE}
ts.state IN ${SqlToken.tupleFromValues(VoxelyticsRunState.COMPLETE, VoxelyticsRunState.SKIPPED)}
AND t._run IN ${SqlToken.tupleFromList(runIds)}
${taskName.map(t => q" AND t.name = $t").getOrElse(q"")}
ORDER BY t.name, ts.beginTime DESC"""

def findArtifacts(runIds: List[ObjectId], staleTimeout: FiniteDuration): Fox[List[ArtifactEntry]] =
def findArtifacts(currentUser: User, runIds: List[ObjectId], staleTimeout: FiniteDuration): Fox[List[ArtifactEntry]] =
for {
r <- run(q"""
WITH latest_complete_tasks AS (${latestCompleteTaskQ(runIds, None, staleTimeout)})
WITH latest_complete_tasks AS (${latestCompleteOrSkippedTaskQ(runIds, None, staleTimeout)})
SELECT
a._id,
a._task,
Expand All @@ -134,10 +137,24 @@ class VoxelyticsDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContex
a.inodeCount,
a.version,
a.metadata,
t.name AS taskName
t.name AS taskName,
o.workflow_hash AS other_workflow_hash,
o._run AS other_run
FROM webknossos.voxelytics_artifacts a
JOIN latest_complete_tasks t ON t._id = a._task
""".as[(String, String, String, String, Long, Long, String, String, String)])
LEFT JOIN ( -- when the task is skipped, the artifact from the producing task run is joined for linking
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add the discussed distinct constraint to the schema, and a comment here explaining how the fanout effect is prevented (joining at most one item, due to the distinct constraint)

SELECT
DISTINCT ON (a.path)
a.path path,
r.workflow_hash workflow_hash,
r._id _run
FROM webknossos.voxelytics_artifacts a
JOIN webknossos.voxelytics_tasks t ON a._task = t._id
JOIN (${tasksWithStateQ(staleTimeout)}) ts ON ts._id = t._id AND ts.state = ${VoxelyticsRunState.COMPLETE}
JOIN (${visibleRunsQ(currentUser, allowUnlisted = true)}) r ON r._id = t._run
ORDER BY a.path, ts.beginTime DESC
) o ON o.path = a.path AND t.state = ${VoxelyticsRunState.SKIPPED}
""".as[(String, String, String, String, Long, Long, String, String, String, Option[String], Option[String])])
} yield
r.toList.map(
row =>
Expand All @@ -150,7 +167,11 @@ class VoxelyticsDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContex
inodeCount = row._6,
version = row._7,
metadata = Json.parse(row._8).as[JsObject],
taskName = row._9
taskName = row._9,
foreignWorkflow = (row._10, row._11) match {
case (Some(workflow_hash), Some(run)) => Some((workflow_hash, run))
case _ => None
}
))

def findTasks(runId: ObjectId): Fox[List[TaskEntry]] =
Expand Down Expand Up @@ -725,7 +746,7 @@ class VoxelyticsDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContex
staleTimeout: FiniteDuration): Fox[List[ArtifactChecksumEntry]] =
for {
r <- run(q"""
WITH latest_complete_tasks AS (${latestCompleteTaskQ(runIds, Some(taskName), staleTimeout)})
WITH latest_complete_tasks AS (${latestCompleteOrSkippedTaskQ(runIds, Some(taskName), staleTimeout)})
SELECT
t.name,
a.name,
Expand Down
3 changes: 2 additions & 1 deletion app/models/voxelytics/VoxelyticsService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ case class ArtifactEntry(artifactId: ObjectId,
inodeCount: Long,
version: String,
metadata: JsObject,
taskName: String)
taskName: String,
foreignWorkflow: Option[(String, String)])

object ArtifactEntry {
implicit val jsonFormat: OFormat[ArtifactEntry] = Json.format[ArtifactEntry]
Expand Down
17 changes: 15 additions & 2 deletions frontend/javascripts/admin/voxelytics/artifacts_view.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from "react";
import { JSONTree } from "react-json-tree";
import { Button, Card, message } from "antd";
import { CopyOutlined } from "@ant-design/icons";
import { CopyOutlined, ExportOutlined } from "@ant-design/icons";
import { VoxelyticsArtifactConfig } from "types/api_flow_types";
import { getVoxelyticsArtifactChecksums } from "admin/admin_rest_api";
import { formatCountToDataAmountUnit } from "libs/format_utils";
import { copyToClipboad, isObjectEmpty, useTheme } from "./utils";
import { Link } from "react-router-dom";

export function renderArtifactPath(artifact: VoxelyticsArtifactConfig) {
return (
Expand Down Expand Up @@ -126,7 +127,19 @@ function ArtifactsView({
function renderArtifact(artifactName: string, artifact: VoxelyticsArtifactConfig) {
const title = (
<>
<span>{artifactName}</span>
<span>
{artifact.foreignWorkflow != null ? (
<>
<Link
to={`/workflows/${artifact.foreignWorkflow[0]}?runId=${artifact.foreignWorkflow[1]}`}
>
{artifactName} <ExportOutlined />
</Link>
</>
) : (
artifactName
)}
</span>
<span style={{ fontSize: "10px", marginLeft: 10 }}>
\\ version {artifact.version}, {formatCountToDataAmountUnit(artifact.fileSize)},{" "}
{artifact.inodeCount.toLocaleString()} inodes
Expand Down
21 changes: 19 additions & 2 deletions frontend/javascripts/admin/voxelytics/task_list_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
ExclamationCircleOutlined,
LeftOutlined,
FieldTimeOutlined,
ExportOutlined,
} from "@ant-design/icons";
import MiniSearch from "minisearch";

Expand Down Expand Up @@ -512,6 +513,12 @@ export default function TaskListView({
return undefined;
}

const taskArtifacts = report.artifacts[task.taskName] || {};
let foreignWorkflow: null | [string, string] = null;
if (taskInfo.state === VoxelyticsRunState.SKIPPED) {
foreignWorkflow = Object.values(taskArtifacts)[0]?.foreignWorkflow ?? null;
}

return {
key: task.taskName,
id: `task-panel-${task.taskName}`,
Expand All @@ -527,7 +534,17 @@ export default function TaskListView({
backgroundColor: colorHasher.hex(task.task),
}}
/>
{task.taskName}
{foreignWorkflow != null ? (
<>
<Link to={`/workflows/${foreignWorkflow[0]}?runId=${foreignWorkflow[1]}`}>
{task.taskName}
&nbsp;
<ExportOutlined />
</Link>
</>
) : (
task.taskName
)}
<wbr />
{task.config.name != null && <span className="task-panel-name">{task.config.name}</span>}
<wbr />
Expand All @@ -542,7 +559,7 @@ export default function TaskListView({
workflowHash={report.workflow.hash}
runId={singleRunId}
task={task}
artifacts={report.artifacts[task.taskName] || []}
artifacts={taskArtifacts}
dag={report.dag}
taskInfo={taskInfo}
onSelectTask={handleSelectTask}
Expand Down
1 change: 1 addition & 0 deletions frontend/javascripts/types/api_flow_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,7 @@ export type VoxelyticsArtifactConfig = {
iframes: Record<string, string>;
links: Record<string, string>;
};
foreignWorkflow: [string, string] | null;
};

export type VoxelyticsRunInfo = {
Expand Down
4 changes: 0 additions & 4 deletions frontend/stylesheets/main.less
Original file line number Diff line number Diff line change
Expand Up @@ -436,10 +436,6 @@ button.narrow {
.voxelytics-view {
min-width: 1300px;

.tasks-header {
border-bottom: 1px solid var(--ant-color-border);
}

.task-panel {
height: calc(100vh - 100px);
position: relative;
Expand Down