Skip to content

Commit

Permalink
feat(progress): add circular progress bar in workflow list page (rean…
Browse files Browse the repository at this point in the history
  • Loading branch information
giuseppe-steduto committed Feb 19, 2024
1 parent ae1e429 commit f99ae86
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 20 deletions.
23 changes: 13 additions & 10 deletions reana-ui/src/pages/workflowList/components/WorkflowDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -26,7 +26,6 @@ export default function WorkflowDetails({ workflow, actionsOnHover = false }) {
friendlyCreated,
friendlyStarted,
friendlyFinished,
duration,
completed,
total,
status,
Expand Down Expand Up @@ -69,14 +68,18 @@ export default function WorkflowDetails({ workflow, actionsOnHover = false }) {
</div>
</div>
<div className={styles["status-box"]}>
<span
className={`${styles["status"]} sui-${statusMapping[status].color}`}
>
{status}
</span>{" "}
{statusMapping[status].preposition} {duration}
<div>
step {completed}/{total}
<span
className={`${styles["status"]} sui-${statusMapping[status].color}`}
>
{status}
</span>{" "}
<div>
step {completed}/{total}
</div>
</div>
<div className={styles["progressbar-container"]}>
<WorkflowProgressCircleBar workflow={workflow} />
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
-*- 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;
}

// The workflow could be completely restored from the cache, in which case
// the total number of steps would be 0. If the workflow is finished, we
// want to show the full progress bar as finished even in this case.
if (status === "finished") {
lengthFinishedArc = circumference;
lengthRunningArc = 0;
lengthFailedArc = 0;
}

return (
<div className={styles["progress-bar-container"]}>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox={`0 0 ${size} ${size}`}
width={`${size}`}
height={`${size}`}
>
<circle
cx={`${size / 2}`}
cy={`${size / 2}`}
r={`${radius}`}
className={styles["progress-bar-background"]}
strokeWidth={`${strokeWidth}`}
/>
<circle
cx={`${size / 2}`}
cy={`${size / 2}`}
r={`${radius}`}
className={`${styles["progress-bar-running"]} ${
styles["progress-bar-workflow-status-" + status]
}`}
strokeDasharray={`${lengthRunningArc} ${circumference}`}
strokeDashoffset={`-${lengthFinishedArc}`}
strokeWidth={`${strokeWidth}`}
/>
<circle
cx={`${size / 2}`}
cy={`${size / 2}`}
r={`${radius}`}
className={`${styles["progress-bar-finished"]} ${
styles["progress-bar-workflow-status-" + status]
}`}
strokeDasharray={`${lengthFinishedArc} ${circumference}`}
strokeDashoffset="0"
strokeWidth={`${strokeWidth}`}
/>
<circle
cx={`${size / 2}`}
cy={`${size / 2}`}
r={`${radius}`}
className={`${styles["progress-bar-failed"]} ${
styles["progress-bar-workflow-status-" + status]
}`}
strokeDasharray={`${lengthFailedArc} ${circumference}`}
strokeDashoffset={`-${lengthFinishedArc}`}
strokeWidth={`${strokeWidth}`}
/>
</svg>
</div>
);
}

WorkflowProgressCircleBar.propTypes = {
workflow: PropTypes.object.isRequired,
size: PropTypes.number,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
@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-workflow-status-queued,
.progress-bar-workflow-status-pending,
.progress-bar-workflow-status-created {
stroke: $light-gray;
}

.progress-bar-workflow-status-deleted {
stroke: $gray;
}

.progress-bar-workflow-status-stopped {
stroke: $sui-yellow !important;
}
}
4 changes: 4 additions & 0 deletions reana-ui/src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down

0 comments on commit f99ae86

Please sign in to comment.