From e28a3a9b0ac1c266c46d92a90bd63a555f25f8b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20G=C3=B6bel?= <dgoebel@techfak.uni-bielefeld.de>
Date: Tue, 5 Mar 2024 15:43:47 +0100
Subject: [PATCH] Update resource info modal to new flow

#100
---
 src/client/workflow/models/WorkflowOut.ts     |  2 +-
 src/components/CopyToClipboardIcon.vue        |  6 +-
 .../resources/RequestReviewButton.vue         | 44 +++++++++++++
 src/components/resources/ResourceCard.vue     | 38 ++++--------
 .../{ => modals}/ResourceVersionInfoModal.vue |  0
 .../modals/UploadResourceInfoModal.vue        | 52 ++++++++--------
 .../workflows/WorkflowWithVersionsCard.vue    | 62 ++++++++++++-------
 src/views/admin/AdminResourcesView.vue        |  2 +-
 src/views/admin/AdminSyncRequestsView.vue     |  2 +-
 src/views/resources/ListResourcesView.vue     | 14 ++---
 src/views/resources/ReviewResourceView.vue    |  2 +-
 src/views/workflows/MyWorkflowsView.vue       |  2 +-
 src/views/workflows/ReviewWorkflowsView.vue   |  8 +--
 src/views/workflows/WorkflowView.vue          |  9 +++
 14 files changed, 148 insertions(+), 95 deletions(-)
 create mode 100644 src/components/resources/RequestReviewButton.vue
 rename src/components/resources/{ => modals}/ResourceVersionInfoModal.vue (100%)

diff --git a/src/client/workflow/models/WorkflowOut.ts b/src/client/workflow/models/WorkflowOut.ts
index 79bc468..90fbae3 100644
--- a/src/client/workflow/models/WorkflowOut.ts
+++ b/src/client/workflow/models/WorkflowOut.ts
@@ -27,7 +27,7 @@ export type WorkflowOut = {
     /**
      * ID of developer of the workflow
      */
-    developer_id?: (string | null);
+    developer_id: string;
     /**
      * Flag if the workflow is hosted in a private git repository
      */
diff --git a/src/components/CopyToClipboardIcon.vue b/src/components/CopyToClipboardIcon.vue
index 6e85078..6b9f65d 100644
--- a/src/components/CopyToClipboardIcon.vue
+++ b/src/components/CopyToClipboardIcon.vue
@@ -45,7 +45,11 @@ onMounted(() => {
     color-class="danger"
     >Can't copy to clipboard
   </bootstrap-toast>
-  <button v-if="props.button" @click="copyToClipboard" class="btn btn-primary">
+  <button
+    v-if="props.button"
+    @click="copyToClipboard"
+    class="btn btn-primary btn-sm"
+  >
     Copy to Clipboard
     <font-awesome-icon icon="fa-solid fa-clipboard" class="ms-1" />
   </button>
diff --git a/src/components/resources/RequestReviewButton.vue b/src/components/resources/RequestReviewButton.vue
new file mode 100644
index 0000000..fa7599d
--- /dev/null
+++ b/src/components/resources/RequestReviewButton.vue
@@ -0,0 +1,44 @@
+<script setup lang="ts">
+import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
+import { onMounted } from "vue";
+import { Tooltip } from "bootstrap";
+
+const props = defineProps<{
+  disabled?: boolean;
+}>();
+
+const emit = defineEmits<{
+  (e: "click-review"): void;
+  (e: "click-refresh"): void;
+}>();
+const randomIDSuffix = Math.random().toString(16).substring(2, 8);
+
+onMounted(() => {
+  new Tooltip("#refresh-s3-" + randomIDSuffix);
+});
+</script>
+
+<template>
+  <div class="btn-group" role="group">
+    <button
+      type="button"
+      class="btn btn-primary"
+      :disabled="props.disabled"
+      @click="emit('click-review')"
+    >
+      Request Review
+    </button>
+    <button
+      :id="'refresh-s3-' + randomIDSuffix"
+      type="button"
+      class="btn btn-primary"
+      @click="emit('click-refresh')"
+      data-bs-toggle="tooltip"
+      data-bs-title="Check if uploaded resource is ready"
+    >
+      <font-awesome-icon icon="fa-solid fa-arrow-rotate-right" />
+    </button>
+  </div>
+</template>
+
+<style scoped></style>
diff --git a/src/components/resources/ResourceCard.vue b/src/components/resources/ResourceCard.vue
index ddb1fb2..1f7292e 100644
--- a/src/components/resources/ResourceCard.vue
+++ b/src/components/resources/ResourceCard.vue
@@ -12,6 +12,7 @@ import { useS3ObjectStore } from "@/stores/s3objects";
 import { useResourceStore } from "@/stores/resources";
 import { Tooltip } from "bootstrap";
 import { useNameStore } from "@/stores/names";
+import RequestReviewButton from "@/components/resources/RequestReviewButton.vue";
 
 const randomIDSuffix: string = Math.random().toString(16).substring(2, 8);
 const objectRepository = useS3ObjectStore();
@@ -98,7 +99,7 @@ onMounted(() => {
       ...(document
         .querySelector("#resource-card-" + randomIDSuffix)
         ?.querySelectorAll("[data-bs-toggle='tooltip']") ?? []),
-    ].map((el) => new Tooltip(el));
+    ].forEach((el) => new Tooltip(el));
   }
 });
 </script>
@@ -224,33 +225,20 @@ onMounted(() => {
                     props.extended &&
                     resourceVersion.status == Status.RESOURCE_REQUESTED
                   "
+                  class="d-flex justify-content-between align-items-center"
                 >
-                  <div class="btn-group" role="group">
-                    <button
-                      type="button"
-                      class="btn btn-primary"
-                      :disabled="
-                        !resourceVersionS3Ready[
-                          resourceVersion.resource_version_id
-                        ]
-                      "
-                      @click="requestReview(resourceVersion)"
-                    >
-                      Request Review
-                    </button>
-                    <button
-                      type="button"
-                      class="btn btn-primary"
-                      @click="clickCheckS3Resource(resourceVersion)"
-                    >
-                      <font-awesome-icon
-                        icon="fa-solid fa-arrow-rotate-right"
-                      />
-                    </button>
-                  </div>
+                  <request-review-button
+                    :disabled="
+                      !resourceVersionS3Ready[
+                        resourceVersion.resource_version_id
+                      ]
+                    "
+                    @click-refresh="clickCheckS3Resource(resourceVersion)"
+                    @click-review="requestReview(resourceVersion)"
+                  />
                   <button
                     type="button"
-                    class="btn btn-info btn-sm float-end"
+                    class="btn btn-info btn-sm"
                     data-bs-toggle="modal"
                     data-bs-target="#uploadResourceInfoModal"
                     @click="emit('click-info', resourceVersion)"
diff --git a/src/components/resources/ResourceVersionInfoModal.vue b/src/components/resources/modals/ResourceVersionInfoModal.vue
similarity index 100%
rename from src/components/resources/ResourceVersionInfoModal.vue
rename to src/components/resources/modals/ResourceVersionInfoModal.vue
diff --git a/src/components/resources/modals/UploadResourceInfoModal.vue b/src/components/resources/modals/UploadResourceInfoModal.vue
index 10282ae..3b33bd3 100644
--- a/src/components/resources/modals/UploadResourceInfoModal.vue
+++ b/src/components/resources/modals/UploadResourceInfoModal.vue
@@ -10,6 +10,7 @@ import { useS3ObjectStore } from "@/stores/s3objects";
 import { useResourceStore } from "@/stores/resources";
 import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
 import { Modal } from "bootstrap";
+import RequestReviewButton from "@/components/resources/RequestReviewButton.vue";
 
 const props = defineProps<{
   modalId: string;
@@ -160,6 +161,7 @@ onMounted(() => {
           </p>
         </li>
         <li
+          class="pb-2"
           :class="{
             'text-decoration-line-through': resourceReviewEnabled,
           }"
@@ -298,47 +300,45 @@ s3 = boto3.resource(
               />
             </b>
           </template>
+          <copy-to-clipboard-icon
+            v-if="props.resourceVersion && !resourceReviewEnabled"
+            button
+            :text="codeExample"
+          />
         </li>
         <li>
           <h6>Request review</h6>
           <p>
             Click <b>Request Review</b> to request a review of the resource.
           </p>
-          <div class="btn-group mb-2" role="group" v-if="props.resourceVersion">
-            <button
-              type="button"
-              class="btn btn-primary"
-              :disabled="!resourceReviewEnabled"
-              @click="requestReview(props.resourceVersion)"
-            >
-              Request Review
-            </button>
-            <button
-              v-if="props.resourceVersion.status === Status.RESOURCE_REQUESTED"
-              type="button"
-              class="btn btn-primary"
-              @click="clickCheckS3Resource(props.resourceVersion)"
-            >
-              <font-awesome-icon icon="fa-solid fa-arrow-rotate-right" />
-            </button>
-          </div>
+          <request-review-button
+            v-if="props.resourceVersion?.status === Status.RESOURCE_REQUESTED"
+            class="mb-2"
+            :disabled="!resourceReviewEnabled"
+            @click-review="requestReview(props.resourceVersion)"
+            @click-refresh="clickCheckS3Resource(props.resourceVersion)"
+          />
+        </li>
+        <li>
+          <h6>Request resource synchronization</h6>
+          <p>
+            Once a Reviewer approves your resource, the resource will be
+            publicly visible in CloWM. To use it in during a workflow execution,
+            you can request the synchronization of the resource to the cluster.
+          </p>
         </li>
         <li>
           <h6>Resource availability</h6>
           <p>
-            Once a Reviewer approves your resource synchronization request, the
-            resource will be made available in CloWM and is accessible for every
-            workflow via its <b>Nextflow Access Path</b>.
+            When an administrator approves the synchronization request and the
+            resource is download to the cluster, the resource will now available
+            for a workflow execution and is accessible for every workflow via
+            its <b>Nextflow Access Path</b>.
           </p>
         </li>
       </ol>
     </template>
     <template #footer>
-      <copy-to-clipboard-icon
-        v-if="props.resourceVersion && !resourceReviewEnabled"
-        button
-        :text="codeExample"
-      />
       <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
         Close
       </button>
diff --git a/src/components/workflows/WorkflowWithVersionsCard.vue b/src/components/workflows/WorkflowWithVersionsCard.vue
index dc30668..1afe153 100644
--- a/src/components/workflows/WorkflowWithVersionsCard.vue
+++ b/src/components/workflows/WorkflowWithVersionsCard.vue
@@ -218,7 +218,7 @@ onMounted(() => {
                 <th scope="col">Updated at</th>
                 <th scope="col" class="text-align-center">Usage</th>
                 <th scope="col" class="text-align-center">Icon</th>
-                <th scope="col">Link</th>
+                <th scope="col"></th>
               </tr>
             </thead>
             <tbody class="table-group-divider">
@@ -287,31 +287,45 @@ onMounted(() => {
                     data-bs-toggle="modal"
                     data-bs-target="#updateWorkflowVersionIconModal"
                   />
+                </td>
+                <td class="text-end">
                   <font-awesome-icon
-                    v-else
-                    icon="fa-solid fa-circle-plus"
-                    class="add-icon-hover cursor-pointer"
-                    @click="emit('workflow-update-icon-click', version)"
-                    data-bs-toggle="modal"
-                    data-bs-target="#updateWorkflowVersionIconModal"
+                    icon="fa-solid fa-bars"
+                    data-bs-toggle="dropdown"
+                    aria-expanded="false"
+                    class="cursor-pointer p-1"
                   />
-                </td>
-                <td>
-                  <router-link
-                    class="w-fit mx-0"
-                    :to="{
-                      name: 'workflow-version',
-                      params: {
-                        workflowId: props.workflow.workflow_id,
-                        versionId: version.workflow_version_id,
-                      },
-                      query: {
-                        workflowModeId: version.modes?.[0] ?? undefined,
-                        developerView: 'true',
-                      },
-                    }"
-                    >View
-                  </router-link>
+                  <ul class="dropdown-menu dropdown-menu-end">
+                    <li>
+                      <router-link
+                        class="dropdown-item"
+                        :to="{
+                          name: 'workflow-version',
+                          params: {
+                            workflowId: props.workflow.workflow_id,
+                            versionId: version.workflow_version_id,
+                          },
+                          query: {
+                            workflowModeId: version.modes?.[0] ?? undefined,
+                            developerView: 'true',
+                          },
+                        }"
+                        >View
+                      </router-link>
+                    </li>
+                    <li>
+                      <a
+                        class="dropdown-item"
+                        href="#"
+                        data-bs-toggle="modal"
+                        data-bs-target="#updateWorkflowVersionIconModal"
+                        @click.prevent="
+                          emit('workflow-update-icon-click', version)
+                        "
+                        >Update icon</a
+                      >
+                    </li>
+                  </ul>
                 </td>
               </tr>
               <tr>
diff --git a/src/views/admin/AdminResourcesView.vue b/src/views/admin/AdminResourcesView.vue
index b371e7e..409ae18 100644
--- a/src/views/admin/AdminResourcesView.vue
+++ b/src/views/admin/AdminResourcesView.vue
@@ -11,7 +11,7 @@ import type { User } from "@/client/auth";
 import { useNameStore } from "@/stores/names";
 import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
 import dayjs from "dayjs";
-import ResourceVersionInfoModal from "@/components/resources/ResourceVersionInfoModal.vue";
+import ResourceVersionInfoModal from "@/components/resources/modals/ResourceVersionInfoModal.vue";
 
 const resourceRepository = useResourceStore();
 const nameRepository = useNameStore();
diff --git a/src/views/admin/AdminSyncRequestsView.vue b/src/views/admin/AdminSyncRequestsView.vue
index 5774737..bb7c0f6 100644
--- a/src/views/admin/AdminSyncRequestsView.vue
+++ b/src/views/admin/AdminSyncRequestsView.vue
@@ -1,7 +1,7 @@
 <script setup lang="ts">
 import BootstrapToast from "@/components/BootstrapToast.vue";
 import ReasonModal from "@/components/modals/ReasonModal.vue";
-import ResourceVersionInfoModal from "@/components/resources/ResourceVersionInfoModal.vue";
+import ResourceVersionInfoModal from "@/components/resources/modals/ResourceVersionInfoModal.vue";
 import { onMounted, reactive } from "vue";
 import {
   type ResourceOut,
diff --git a/src/views/resources/ListResourcesView.vue b/src/views/resources/ListResourcesView.vue
index c764868..7cea20a 100644
--- a/src/views/resources/ListResourcesView.vue
+++ b/src/views/resources/ListResourcesView.vue
@@ -118,15 +118,15 @@ onMounted(() => {
         />
       </div>
     </div>
-    <div class="form-check fs-5 ms-auto">
+    <div class="form-check form-check-reverse form-check-inline fs-6 ms-auto">
+      <input
+        class="form-check-input"
+        type="checkbox"
+        v-model="resourceState.showPrivate"
+        id="public-resources-checkbox"
+      />
       <label class="form-check-label" for="public-resources-checkbox">
         Show only public resources
-        <input
-          class="form-check-input"
-          type="checkbox"
-          v-model="resourceState.showPrivate"
-          id="public-resources-checkbox"
-        />
       </label>
     </div>
     <font-awesome-icon
diff --git a/src/views/resources/ReviewResourceView.vue b/src/views/resources/ReviewResourceView.vue
index 5c863c3..5b7c226 100644
--- a/src/views/resources/ReviewResourceView.vue
+++ b/src/views/resources/ReviewResourceView.vue
@@ -8,7 +8,7 @@ import {
 } from "@/client/resource";
 import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
 import { Modal, Toast, Tooltip } from "bootstrap";
-import ResourceVersionInfoModal from "@/components/resources/ResourceVersionInfoModal.vue";
+import ResourceVersionInfoModal from "@/components/resources/modals/ResourceVersionInfoModal.vue";
 import { useNameStore } from "@/stores/names";
 import BootstrapToast from "@/components/BootstrapToast.vue";
 import ReasonModal from "@/components/modals/ReasonModal.vue";
diff --git a/src/views/workflows/MyWorkflowsView.vue b/src/views/workflows/MyWorkflowsView.vue
index 89632a1..34fb0c0 100644
--- a/src/views/workflows/MyWorkflowsView.vue
+++ b/src/views/workflows/MyWorkflowsView.vue
@@ -114,7 +114,7 @@ onMounted(() => {
     modal-id="updateWorkflowCredentialsModal"
   />
   <div
-    class="row border-bottom mb-4 justify-content-between align-items-center pb-2"
+    class="row border-bottom mb-4 justify-content-between align-items-center pb-2 pe-2"
   >
     <h2 class="w-fit">My Workflows</h2>
     <button
diff --git a/src/views/workflows/ReviewWorkflowsView.vue b/src/views/workflows/ReviewWorkflowsView.vue
index ad15851..e8420b3 100644
--- a/src/views/workflows/ReviewWorkflowsView.vue
+++ b/src/views/workflows/ReviewWorkflowsView.vue
@@ -35,10 +35,6 @@ function updateWorkflowVersionStatus(
     });
 }
 
-function isDefined<T>(argument: T | undefined | null): argument is T {
-  return argument != undefined;
-}
-
 onMounted(() => {
   workflowRepository
     .fetchReviewableWorkflows(() => {
@@ -55,9 +51,7 @@ onMounted(() => {
       }, 1000);
       return workflows;
     })
-    .then((workflows) =>
-      workflows.map((workflow) => workflow.developer_id).filter(isDefined),
-    )
+    .then((workflows) => workflows.map((workflow) => workflow.developer_id))
     .then(userRepository.fetchUsernames);
 });
 </script>
diff --git a/src/views/workflows/WorkflowView.vue b/src/views/workflows/WorkflowView.vue
index 28d53d4..32dfd64 100644
--- a/src/views/workflows/WorkflowView.vue
+++ b/src/views/workflows/WorkflowView.vue
@@ -16,9 +16,11 @@ import {
 import { determineGitIcon } from "@/utils/GitRepository";
 import { useAuthStore } from "@/stores/users";
 import { useWorkflowStore } from "@/stores/workflows";
+import { useNameStore } from "@/stores/names";
 
 const workflowRepository = useWorkflowStore();
 const userRepository = useAuthStore();
+const nameRepository = useNameStore();
 
 // Props
 // =============================================================================
@@ -132,6 +134,10 @@ function updateWorkflow(workflowId: string) {
       workflowState.loading = false;
       workflowState.initialOpen = false;
     })
+    .then((workflow) => {
+      userRepository.fetchUsernames([workflow.developer_id]);
+      return workflow;
+    })
     .then((workflow) => {
       document.title = workflow.name + " - CloWM";
       if (props.versionId == undefined) {
@@ -230,6 +236,9 @@ onMounted(() => {
       <h3 class="w-fit">
         {{ workflow.name }}
         <span v-if="activeVersionString">@{{ activeVersionString }}</span>
+        <span v-if="nameRepository.getName(workflow.developer_id)" class="fs-4">
+          by {{ nameRepository.getName(workflow.developer_id) }}</span
+        >
       </h3>
       <img
         v-if="activeVersionIcon != null"
-- 
GitLab