Repository: artefactual-sdps/enduro
Pull request #1196: Update status badge style, refs #1160
Author: login: djjuhasz
Created: 2025-04-28T18:39:43Z | Merged: 2025-04-30T01:38:30Z

- Create separate badge styles for packages (AIPs and SIPs) and workflows (workflows and workflow tasks)
- Allow setting badge borders, background colours, and text colours individually
- Make badge corners round


Diff:
diff --git a/dashboard/src/components/SipListLegend.vue b/dashboard/src/components/SipListLegend.vue
index 92de9298d..7a7145294 100644
--- a/dashboard/src/components/SipListLegend.vue
+++ b/dashboard/src/components/SipListLegend.vue
@@ -58,6 +58,7 @@ const items = [
           <div class="col-12 col-md-2 py-2">
             <StatusBadge
               :status="item.status"
+              type="package"
               :aria-describedby="`badge-${index}-desc`"
             />
           </div>
diff --git a/dashboard/src/components/StatusBadge.vue b/dashboard/src/components/StatusBadge.vue
index 151e3be72..9983a2da3 100644
--- a/dashboard/src/components/StatusBadge.vue
+++ b/dashboard/src/components/StatusBadge.vue
@@ -3,42 +3,73 @@ import { computed } from "vue";
 
 import type { api } from "@/client";
 
+type badgeType = "package" | "workflow";
 const props = defineProps<{
-  status:
-    | api.EnduroIngestSipStatusEnum
-    | api.EnduroIngestSipWorkflowStatusEnum
-    | api.EnduroIngestSipTaskStatusEnum
-    | api.EnduroStorageAipStatusEnum
-    | api.EnduroStorageAipWorkflowStatusEnum
-    | api.EnduroStorageAipTaskStatusEnum;
+  status: statusEnum;
+  type: badgeType;
   note?: string;
 }>();
 
-const classes: {
-  [key in
-    | api.EnduroIngestSipStatusEnum
-    | api.EnduroIngestSipWorkflowStatusEnum
-    | api.EnduroIngestSipTaskStatusEnum
-    | api.EnduroStorageAipStatusEnum
-    | api.EnduroStorageAipWorkflowStatusEnum
-    | api.EnduroStorageAipTaskStatusEnum]: string;
+type packageEnum =
+  | api.EnduroIngestSipStatusEnum
+  | api.EnduroStorageAipStatusEnum;
+type workflowEnum =
+  | api.EnduroIngestSipWorkflowStatusEnum
+  | api.EnduroStorageAipWorkflowStatusEnum;
+type taskEnum =
+  | api.EnduroIngestSipTaskStatusEnum
+  | api.EnduroStorageAipTaskStatusEnum;
+
+type statusEnum = packageEnum | workflowEnum | taskEnum;
+
+type badgeStyle = string[];
+
+const packageStyle: {
+  [key in packageEnum]: badgeStyle;
 } = {
-  "in progress": "text-bg-info",
-  done: "text-bg-success",
-  error: "text-bg-danger",
-  queued: "text-bg-secondary",
-  pending: "text-bg-warning",
-  canceled: "text-bg-dark",
-  unspecified: "text-bg-dark",
-  stored: "text-bg-success",
-  deleted: "text-bg-danger",
-  processing: "text-bg-info",
-  failed: "text-bg-danger",
-  ingested: "text-bg-success",
+  ingested: [
+    "text-dark",
+    "bg-success-subtle",
+    "border border-2 border-success",
+  ],
+  stored: ["text-dark", "bg-success-subtle", "border border-2 border-success"],
+  deleted: ["text-dark", "bg-danger-subtle", "border border-2 border-danger"],
+  failed: ["text-dark", "bg-danger-subtle", "border border-2 border-danger"],
+  error: ["text-dark", "bg-danger-subtle", "border border-2 border-danger"],
+  queued: [
+    "text-dark",
+    "bg-secondary-subtle",
+    "border border-2 border-secondary",
+  ],
+  processing: ["text-dark", "bg-info-subtle", "border border-2 border-info"],
+  pending: ["text-dark", "bg-warning-subtle", "border border-2 border-warning"],
+  unspecified: ["text-dark", "bg-dark-subtle", "border border-2 border-dark"],
+};
+
+const workflowStyle: {
+  [key in workflowEnum | taskEnum]: badgeStyle;
+} = {
+  done: ["text-bg-success"],
+  failed: ["text-bg-danger"],
+  error: ["text-bg-danger"],
+  queued: ["text-bg-secondary"],
+  "in progress": ["text-bg-info"],
+  pending: ["text-bg-warning"],
+  canceled: ["text-bg-dark"],
+  unspecified: ["text-bg-dark"],
 };
 
 const colorClass = computed(() => {
-  return classes[props.status];
+  function getBadgeStyle(type: badgeType): badgeStyle {
+    switch (type) {
+      case "package":
+        return packageStyle[props.status as packageEnum];
+      case "workflow":
+        return workflowStyle[props.status as workflowEnum];
+    }
+  }
+
+  return getBadgeStyle(props.type).join(" ");
 });
 </script>
 
diff --git a/dashboard/src/components/Task.vue b/dashboard/src/components/Task.vue
index dbedb6743..c6ac8fcda 100644
--- a/dashboard/src/components/Task.vue
+++ b/dashboard/src/components/Task.vue
@@ -113,7 +113,7 @@ watch(
         </div>
       </div>
       <div class="d-flex pt-1">
-        <StatusBadge :status="task.status" />
+        <StatusBadge :status="task.status" type="workflow" />
       </div>
     </div>
   </div>
diff --git a/dashboard/src/components/WorkflowCollapse.vue b/dashboard/src/components/WorkflowCollapse.vue
index b1f0f6ca9..68fe47193 100644
--- a/dashboard/src/components/WorkflowCollapse.vue
+++ b/dashboard/src/components/WorkflowCollapse.vue
@@ -61,7 +61,7 @@ const showAipDeletionReviewAlert = (
         <div class="d-flex flex-column">
           <div class="h4">
             {{ $filters.getWorkflowLabel(workflow.type) }}
-            <StatusBadge :status="workflow.status" />
+            <StatusBadge :status="workflow.status" type="workflow" />
           </div>
           <div>
             <span v-if="workflow.completedAt">
diff --git a/dashboard/src/pages/ingest/sips/[id]/index.vue b/dashboard/src/pages/ingest/sips/[id]/index.vue
index 43eab7472..7e1340d5c 100644
--- a/dashboard/src/pages/ingest/sips/[id]/index.vue
+++ b/dashboard/src/pages/ingest/sips/[id]/index.vue
@@ -32,7 +32,9 @@ const createAipWorkflow = computed(
           <dt>Name</dt>
           <dd>{{ sipStore.current.name }}</dd>
           <dt>Status</dt>
-          <dd><StatusBadge :status="sipStore.current.status" /></dd>
+          <dd>
+            <StatusBadge :status="sipStore.current.status" type="package" />
+          </dd>
           <dt>Started</dt>
           <dd>{{ $filters.formatDateTime(createAipWorkflow?.startedAt) }}</dd>
           <dt v-if="createAipWorkflow?.completedAt">Completed</dt>
diff --git a/dashboard/src/pages/ingest/sips/index.vue b/dashboard/src/pages/ingest/sips/index.vue
index 4a049cf83..2c53d68c0 100644
--- a/dashboard/src/pages/ingest/sips/index.vue
+++ b/dashboard/src/pages/ingest/sips/index.vue
@@ -342,7 +342,7 @@ watch(
             </td>
             <td><UUID :id="sip.aipId" /></td>
             <td>{{ $filters.formatDateTime(sip.startedAt) }}</td>
-            <td><StatusBadge :status="sip.status" /></td>
+            <td><StatusBadge :status="sip.status" type="package" /></td>
           </tr>
         </tbody>
       </table>
diff --git a/dashboard/src/pages/storage/aips/[id]/index.vue b/dashboard/src/pages/storage/aips/[id]/index.vue
index e1194aed5..29b4b03fc 100644
--- a/dashboard/src/pages/storage/aips/[id]/index.vue
+++ b/dashboard/src/pages/storage/aips/[id]/index.vue
@@ -23,7 +23,9 @@ const authStore = useAuthStore();
           <dt>UUID</dt>
           <dd><UUID :id="aipStore.current.uuid" /></dd>
           <dt>Status</dt>
-          <dd><StatusBadge :status="aipStore.current.status" /></dd>
+          <dd>
+            <StatusBadge :status="aipStore.current.status" type="package" />
+          </dd>
           <dt>Deposited</dt>
           <dd>{{ $filters.formatDateTime(aipStore.current.createdAt) }}</dd>
         </dl>
diff --git a/dashboard/src/pages/storage/aips/index.vue b/dashboard/src/pages/storage/aips/index.vue
index 7094a9caa..a050150bb 100644
--- a/dashboard/src/pages/storage/aips/index.vue
+++ b/dashboard/src/pages/storage/aips/index.vue
@@ -301,7 +301,7 @@ watch(
             <td><UUID :id="aip.uuid" /></td>
             <td>{{ $filters.formatDateTime(aip.createdAt) }}</td>
             <td><UUID :id="aip.locationId" /></td>
-            <td><StatusBadge :status="aip.status" /></td>
+            <td><StatusBadge :status="aip.status" type="package" /></td>
           </tr>
         </tbody>
       </table>
diff --git a/dashboard/src/styles/bootstrap-base.scss b/dashboard/src/styles/bootstrap-base.scss
index 5caf8e1ab..277a07043 100644
--- a/dashboard/src/styles/bootstrap-base.scss
+++ b/dashboard/src/styles/bootstrap-base.scss
@@ -14,6 +14,7 @@ $headings-font-weight: 400;
 $border-radius: 0;
 $border-radius-sm: 0;
 $border-radius-lg: 0;
+$badge-border-radius: 0.5em;
 
 // Accordion overrides.
 $accordion-button-active-bg: white;


Reviews:
user: login: jraddaoui
body: LGTM! But it may be missing some changes based on your comments.
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-29T17:10:33Z
user: login: djjuhasz
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-29T20:11:09Z
user: login: jraddaoui
body: Thanks @djjuhasz!
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-30T01:38:05Z

Review comments:
diff_hunk: @@ -3,42 +3,77 @@ import { computed } from "vue";
 
 import type { api } from "@/client";
 
+type badgeType = "package" | "workflow";
 const props = defineProps<{
-  status:
-    | api.EnduroIngestSipStatusEnum
-    | api.EnduroIngestSipWorkflowStatusEnum
-    | api.EnduroIngestSipTaskStatusEnum
-    | api.EnduroStorageAipStatusEnum
-    | api.EnduroStorageAipWorkflowStatusEnum
-    | api.EnduroStorageAipTaskStatusEnum;
+  status: statusEnum;
+  type: badgeType;
   note?: string;
 }>();
 
-const classes: {
-  [key in
-    | api.EnduroIngestSipStatusEnum
-    | api.EnduroIngestSipWorkflowStatusEnum
-    | api.EnduroIngestSipTaskStatusEnum
-    | api.EnduroStorageAipStatusEnum
-    | api.EnduroStorageAipWorkflowStatusEnum
-    | api.EnduroStorageAipTaskStatusEnum]: string;
+type packageEnum =
+  | api.EnduroIngestSipStatusEnum
+  | api.EnduroStorageAipStatusEnum;
+type workflowEnum =
+  | api.EnduroIngestSipWorkflowStatusEnum
+  | api.EnduroStorageAipWorkflowStatusEnum;
+type taskEnum =
+  | api.EnduroIngestSipTaskStatusEnum
+  | api.EnduroStorageAipTaskStatusEnum;
+
+type statusEnum = packageEnum | workflowEnum | taskEnum;
+
+type badgeStyle = string[];
+
+const packageStyle: {
+  [key in packageEnum]: badgeStyle;
 } = {
-  "in progress": "text-bg-info",
-  done: "text-bg-success",
-  error: "text-bg-danger",
-  queued: "text-bg-secondary",
-  pending: "text-bg-warning",
-  canceled: "text-bg-dark",
-  unspecified: "text-bg-dark",
-  stored: "text-bg-success",
-  deleted: "text-bg-danger",
-  processing: "text-bg-info",
-  failed: "text-bg-danger",
-  ingested: "text-bg-success",
+  ingested: [
+    "text-success",
path: dashboard/src/components/StatusBadge.vue
user: login: jraddaoui
body: Is the black text change missing in this PR?
created_at: 2025-04-29T17:09:29Z
author_association: COLLABORATOR
diff_hunk: @@ -3,42 +3,77 @@ import { computed } from "vue";
 
 import type { api } from "@/client";
 
+type badgeType = "package" | "workflow";
 const props = defineProps<{
-  status:
-    | api.EnduroIngestSipStatusEnum
-    | api.EnduroIngestSipWorkflowStatusEnum
-    | api.EnduroIngestSipTaskStatusEnum
-    | api.EnduroStorageAipStatusEnum
-    | api.EnduroStorageAipWorkflowStatusEnum
-    | api.EnduroStorageAipTaskStatusEnum;
+  status: statusEnum;
+  type: badgeType;
   note?: string;
 }>();
 
-const classes: {
-  [key in
-    | api.EnduroIngestSipStatusEnum
-    | api.EnduroIngestSipWorkflowStatusEnum
-    | api.EnduroIngestSipTaskStatusEnum
-    | api.EnduroStorageAipStatusEnum
-    | api.EnduroStorageAipWorkflowStatusEnum
-    | api.EnduroStorageAipTaskStatusEnum]: string;
+type packageEnum =
+  | api.EnduroIngestSipStatusEnum
+  | api.EnduroStorageAipStatusEnum;
+type workflowEnum =
+  | api.EnduroIngestSipWorkflowStatusEnum
+  | api.EnduroStorageAipWorkflowStatusEnum;
+type taskEnum =
+  | api.EnduroIngestSipTaskStatusEnum
+  | api.EnduroStorageAipTaskStatusEnum;
+
+type statusEnum = packageEnum | workflowEnum | taskEnum;
+
+type badgeStyle = string[];
+
+const packageStyle: {
+  [key in packageEnum]: badgeStyle;
 } = {
-  "in progress": "text-bg-info",
-  done: "text-bg-success",
-  error: "text-bg-danger",
-  queued: "text-bg-secondary",
-  pending: "text-bg-warning",
-  canceled: "text-bg-dark",
-  unspecified: "text-bg-dark",
-  stored: "text-bg-success",
-  deleted: "text-bg-danger",
-  processing: "text-bg-info",
-  failed: "text-bg-danger",
-  ingested: "text-bg-success",
+  ingested: [
+    "text-success",
path: dashboard/src/components/StatusBadge.vue
user: login: djjuhasz
body: Yes, I missed the push somehow :facepalm:  Thanks for catching that @jraddaoui. :)
created_at: 2025-04-29T20:11:09Z
author_association: COLLABORATOR

PR comments:
user: login: djjuhasz
created_at: 2025-04-28T18:42:30Z
body: Here's some screenshots of the updated badges.

SIP badges:
![Screenshot from 2025-04-28 10-00-06](https://github.com/user-attachments/assets/068b930d-1e0a-4dca-b707-9b5cff660728)

Workflow badges:
![Screenshot from 2025-04-28 10-00-51](https://github.com/user-attachments/assets/57b5d154-7b35-4467-be38-1730557be062)

author_association: COLLABORATOR
user: login: djjuhasz
created_at: 2025-04-28T18:43:15Z
body: SIP badges with legend:
![image](https://github.com/user-attachments/assets/abcc2c7e-d336-4912-bb6a-76b59827a494)

author_association: COLLABORATOR
user: login: codecov[bot]
created_at: 2025-04-28T18:43:28Z
body: ## [Codecov](https://app.codecov.io/gh/artefactual-sdps/enduro/pull/1196?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=artefactual-sdps) Report
:white_check_mark: All modified and coverable lines are covered by tests.
:white_check_mark: Project coverage is 51.79%. Comparing base ([`52df8ef`](https://app.codecov.io/gh/artefactual-sdps/enduro/commit/52df8ef353cccaa812a26b8137a795442f3cf39a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=artefactual-sdps)) to head ([`2c5b485`](https://app.codecov.io/gh/artefactual-sdps/enduro/commit/2c5b485a9adb0f5dc3807201e04b79ed0383886d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=artefactual-sdps)).
:warning: Report is 114 commits behind head on main.

<details><summary>Additional details and impacted files</summary>



```diff
@@            Coverage Diff             @@
##             main    #1196      +/-   ##
==========================================
- Coverage   51.80%   51.79%   -0.01%     
==========================================
  Files         113      113              
  Lines        8555     8560       +5     
==========================================
+ Hits         4432     4434       +2     
- Misses       3844     3847       +3     
  Partials      279      279              
```
</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/artefactual-sdps/enduro/pull/1196?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=artefactual-sdps).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=artefactual-sdps).
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: djjuhasz
created_at: 2025-04-28T18:48:34Z
body: The colored badge text for packages didn't meet WCAG colour contrast requirements, so I've switched to black text:
![image](https://github.com/user-attachments/assets/18feeb78-e434-4dbb-ac94-6c7f78f3218f)

author_association: COLLABORATOR