From b0043ffbcb13200baee22a83207aa2585a414064 Mon Sep 17 00:00:00 2001 From: Giuseppe Steduto Date: Mon, 22 Jan 2024 18:00:23 +0100 Subject: [PATCH] feat(progress): add circular progress bar in workflow list page (#394) --- .../components/WorkflowDetails.js | 23 +++-- .../components/WorkflowDetails.module.scss | 17 ++-- .../components/WorkflowProgressCircleBar.js | 94 +++++++++++++++++++ .../WorkflowProgressCircleBar.module.scss | 39 ++++++++ reana-ui/src/util.js | 4 + 5 files changed, 157 insertions(+), 20 deletions(-) create mode 100644 reana-ui/src/pages/workflowList/components/WorkflowProgressCircleBar.js create mode 100644 reana-ui/src/pages/workflowList/components/WorkflowProgressCircleBar.module.scss diff --git a/reana-ui/src/pages/workflowList/components/WorkflowDetails.js b/reana-ui/src/pages/workflowList/components/WorkflowDetails.js index a0a2de3a..50b664a8 100644 --- a/reana-ui/src/pages/workflowList/components/WorkflowDetails.js +++ b/reana-ui/src/pages/workflowList/components/WorkflowDetails.js @@ -11,12 +11,12 @@ import { Icon, Popup } from "semantic-ui-react"; import PropTypes from "prop-types"; -import { WorkflowActionsPopup } from "~/components"; import { statusMapping } from "~/util"; import styles from "./WorkflowDetails.module.scss"; +import WorkflowProgressCircleBar from "~/pages/workflowList/components/WorkflowProgressCircleBar"; -export default function WorkflowDetails({ workflow, actionsOnHover = false }) { +export default function WorkflowDetails({ workflow }) { const { name, run, @@ -26,7 +26,6 @@ export default function WorkflowDetails({ workflow, actionsOnHover = false }) { friendlyCreated, friendlyStarted, friendlyFinished, - duration, completed, total, status, @@ -69,14 +68,18 @@ export default function WorkflowDetails({ workflow, actionsOnHover = false }) {
- - {status} - {" "} - {statusMapping[status].preposition} {duration}
- step {completed}/{total} + + {status} + {" "} +
+ step {completed}/{total} +
+
+
+
diff --git a/reana-ui/src/pages/workflowList/components/WorkflowDetails.module.scss b/reana-ui/src/pages/workflowList/components/WorkflowDetails.module.scss index 49ff55a5..41f51057 100644 --- a/reana-ui/src/pages/workflowList/components/WorkflowDetails.module.scss +++ b/reana-ui/src/pages/workflowList/components/WorkflowDetails.module.scss @@ -72,18 +72,15 @@ .status-box { width: 210px; flex-shrink: 0; + display: flex; + justify-content: end; + text-align: right; } - .actions { - min-width: 22px; - - &:hover { - color: darken($sepia, 30%); - } - - &.always-visible { - visibility: visible; - } + .progressbar-container { + width: 80px; + padding: 10px; + padding-top: 0; } .notebook { diff --git a/reana-ui/src/pages/workflowList/components/WorkflowProgressCircleBar.js b/reana-ui/src/pages/workflowList/components/WorkflowProgressCircleBar.js new file mode 100644 index 00000000..eb1d6502 --- /dev/null +++ b/reana-ui/src/pages/workflowList/components/WorkflowProgressCircleBar.js @@ -0,0 +1,94 @@ +/* + -*- coding: utf-8 -*- + + This file is part of REANA. + Copyright (C) 2024 CERN. + + REANA is free software; you can redistribute it and/or modify it + under the terms of the MIT License; see LICENSE file for more details. +*/ + +import PropTypes from "prop-types"; +import styles from "./WorkflowProgressCircleBar.module.scss"; + +export default function WorkflowProgressCircleBar({ workflow }) { + const { completed, failed, running, total, status } = workflow; + + const size = 80; + const strokeWidth = 10; + const radius = size / 2 - strokeWidth; + const circumference = 2 * Math.PI * radius; + + let lengthFinishedArc = (completed / total) * circumference; + let lengthRunningArc = (running / total) * circumference; + let lengthFailedArc = (failed / total) * circumference; + // Explicitly set the size of the progress bar for workflows that + // are not running to avoid dealing with undefined number of steps + const TERMINAL_STATUSES = ["finished", "failed", "stopped"]; + const PREPARING_STATUSES = ["created", "queued", "pending"]; + if (TERMINAL_STATUSES.includes(status)) { + lengthRunningArc = 0; + } + if (PREPARING_STATUSES.includes(status)) { + lengthFinishedArc = 0; + lengthRunningArc = 0; + lengthFailedArc = 0; + } + + let additionalClass = ""; + if (PREPARING_STATUSES.includes(status)) { + additionalClass = "progress-bar-preparing"; + } else if (["deleted", "stopped"].includes(status)) { + additionalClass = `progress-bar-${status}`; + } + return ( +
+ + + + + + +
+ ); +} + +WorkflowProgressCircleBar.propTypes = { + workflow: PropTypes.object.isRequired, + size: PropTypes.number, +}; diff --git a/reana-ui/src/pages/workflowList/components/WorkflowProgressCircleBar.module.scss b/reana-ui/src/pages/workflowList/components/WorkflowProgressCircleBar.module.scss new file mode 100644 index 00000000..e0b0d13e --- /dev/null +++ b/reana-ui/src/pages/workflowList/components/WorkflowProgressCircleBar.module.scss @@ -0,0 +1,39 @@ +@import "@palette"; + +.progress-bar-container { + svg { + width: 100%; + height: 100%; + transform: rotate(-90deg); + } + + circle { + fill: none; + transition: stroke-dasharray 0.35s ease; + } + + .progress-bar-running { + stroke: $sui-blue; + } + + .progress-bar-finished { + stroke: $sui-green; + } + + .progress-bar-failed { + stroke: $sui-red; + } + + .progress-bar-background, + .progress-bar-preparing { + stroke: $light-gray; + } + + .progress-bar-deleted { + stroke: $gray; + } + + .progress-bar-stopped { + stroke: $sui-yellow !important; + } +} diff --git a/reana-ui/src/util.js b/reana-ui/src/util.js index 2400a44b..5cf10477 100644 --- a/reana-ui/src/util.js +++ b/reana-ui/src/util.js @@ -53,8 +53,12 @@ export function parseWorkflows(workflows) { workflow.run = info.join("."); const progress = workflow.progress.finished; const total = workflow.progress.total; + const running = workflow.progress.running; + const failed = workflow.progress.failed; workflow.completed = typeof progress === "object" ? progress.total : 0; workflow.total = total.total; + workflow.running = typeof running === "object" ? running.total : 0; + workflow.failed = typeof failed === "object" ? failed.total : 0; workflow.launcherURL = workflow.launcher_url; workflow = parseWorkflowDates(workflow);