Skip to content
Snippets Groups Projects
ListWorkflowExecutionsView.vue 11.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • <script setup lang="ts">
    import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
    import { onMounted, reactive, computed } from "vue";
    import type { WorkflowExecutionOut } from "@/client/workflow";
    import { useAuthStore } from "@/stores/auth";
    import {
      WorkflowExecutionService,
      WorkflowExecutionStatus,
      WorkflowService,
      WorkflowVersionService,
    } from "@/client/workflow";
    import dayjs from "dayjs";
    import DeleteModal from "@/components/modals/DeleteModal.vue";
    
    const userStore = useAuthStore();
    
    const executionsState = reactive<{
      workflowMapping: Record<string, string>;
      versionMapping: Record<string, string>;
      workflowExecutions: WorkflowExecutionOut[];
      loading: boolean;
      mappingLoading: boolean;
      executionToDelete?: WorkflowExecutionOut;
    }>({
      workflowMapping: {},
      versionMapping: {},
      workflowExecutions: [],
      loading: true,
      mappingLoading: true,
      executionToDelete: undefined,
    });
    
    const statusToColorMapping = {
      PENDING: "bg-warning",
      SCHEDULED: "bg-warning",
      RUNNING: "bg-info",
      CANCELED: "bg-danger",
      SUCCESS: "bg-success",
      ERROR: "bg-danger",
    };
    
    const statusToIconMapping = {
      PENDING: "fa-solid fa-circle-pause",
      SCHEDULED: "fa-solid fa-circle-pause",
      RUNNING: "fa-solid fa-circle-play",
      CANCELED: "fa-solid fa-circle-xmark",
      SUCCESS: "fa-solid fa-circle-check",
      ERROR: "fa-solid fa-circle-xmark",
    };
    
    const sortedExecutions = computed<WorkflowExecutionOut[]>(() => {
      const tempList = [...executionsState.workflowExecutions];
      tempList.sort((a, b) => {
        // sort by start time descending
        return dayjs(a.start_time).isBefore(dayjs(b.start_time)) ? 1 : -1;
      });
      return tempList;
    });
    
    const deleteModalString = computed<string>(() => {
      if (executionsState.executionToDelete === undefined) {
        return "";
      } else if (
        !executionsState.executionToDelete.workflow_version_id ||
        !executionsState.executionToDelete.workflow_id
      ) {
        return `Workflow Execution from ${dayjs(
          executionsState.executionToDelete.start_time
        ).format("DD.MM.YYYY HH:mm")}`;
      } else {
        return `Workflow Execution ${
          executionsState.workflowMapping[
            executionsState.executionToDelete.workflow_id
          ]
        }@${
          executionsState.versionMapping[
            executionsState.executionToDelete.workflow_version_id
          ]
        } from ${dayjs(executionsState.executionToDelete.start_time).format(
          "DD.MM.YYYY HH:mm"
        )}`;
      }
    });
    
    // Functions
    // -----------------------------------------------------------------------------
    function updateExecutions() {
      const listExecutionsPromise =
        WorkflowExecutionService.workflowExecutionListWorkflowExecutions(
          userStore.currentUID
        )
          .then((executions) => {
            executionsState.workflowExecutions = executions;
            return executions;
          })
          .finally(() => {
            executionsState.loading = false;
          });
      listExecutionsPromise // construct mapping from workflow id to workflow name
        .then((executions) =>
          Promise.all(
            // group all calls to the API
            executions
              .filter((execution) => execution.workflow_id) // filter undefined workflows
              .filter(
                (execution) =>
                  !executionsState.workflowMapping[execution.workflow_id]
              )
              .filter(
                // filter unique workflows
                (execution, index, array) =>
                  array.findIndex(
                    (val) => val.workflow_id === execution.workflow_id
                  ) === index
              )
              .map((execution) =>
                WorkflowService.workflowGetWorkflow(execution.workflow_id)
              )
          )
        )
        .then((workflows) =>
          workflows.forEach((workflow) => {
            executionsState.workflowMapping[workflow.workflow_id] = workflow.name;
          })
        )
        .finally(() => {
          executionsState.mappingLoading = false;
        });
      listExecutionsPromise // construct mapping from version id to clear text version
        .then((executions) =>
          Promise.all(
            // group all calls to the API
            executions
              .filter(
                // filter undefined workflow versions
                (execution) =>
                  execution.workflow_id && execution.workflow_version_id
              )
              .filter(
                // filter already seen workflow versions
                (version) => !executionsState.versionMapping[version.workflow_id]
              )
              .filter(
                // filter unique workflow versions
                (execution, index, array) =>
                  array.findIndex(
                    (val) =>
                      val.workflow_version_id === execution.workflow_version_id
                  ) === index
              )
              .map((execution) =>
                WorkflowVersionService.workflowVersionGetWorkflowVersion(
                  execution.workflow_version_id,
                  execution.workflow_id
                )
              )
          )
        )
        .then((versions) =>
          versions.forEach((version) => {
            executionsState.versionMapping[version.git_commit_hash] =
              version.version;
          })
        );
    }
    
    function workflowExecutionDeletable(status: WorkflowExecutionStatus): boolean {
      return [
        WorkflowExecutionStatus.ERROR,
        WorkflowExecutionStatus.CANCELED,
        WorkflowExecutionStatus.SUCCESS,
      ].includes(status);
    }
    function workflowExecutionCancable(status: WorkflowExecutionStatus): boolean {
      return [
        WorkflowExecutionStatus.RUNNING,
        WorkflowExecutionStatus.PENDING,
        WorkflowExecutionStatus.SCHEDULED,
      ].includes(status);
    }
    
    function deleteWorkflowExecution(executionId?: string) {
      if (executionId) {
        WorkflowExecutionService.workflowExecutionDeleteWorkflowExecution(
          executionId
        ).then(() => {
          executionsState.workflowExecutions =
            executionsState.workflowExecutions.filter(
              (execution) => execution.execution_id !== executionId
            );
        });
      }
    }
    
    function cancelWorkflowExecution(executionId: string) {
      WorkflowExecutionService.workflowExecutionCancelWorkflowExecution(
        executionId
      ).then(() => {
        const index = executionsState.workflowExecutions.findIndex(
          (execution) => execution.execution_id === executionId
        );
        if (index > -1) {
          executionsState.workflowExecutions[index].status =
            WorkflowExecutionStatus.CANCELED;
          executionsState.workflowExecutions[index].end_time =
            dayjs().toISOString();
        }
      });
    }
    
    onMounted(() => {
      updateExecutions();
    });
    </script>
    
    <template>
      <delete-modal
        modal-i-d="deleteWorkflowExecutionModal"
        :object-name-delete="deleteModalString"
        @confirm-delete="
          deleteWorkflowExecution(executionsState.executionToDelete?.execution_id)
        "
      />
      <div
        class="row m-2 border-bottom border-light mb-4 justify-content-between align-items-center"
      >
        <h1 class="mb-2 text-light w-fit">My Workflow Executions</h1>
        <router-link :to="{ name: 'workflows' }" class="btn btn-primary w-fit"
          >Start Workflow Execution</router-link
        >
      </div>
      <table
        class="table table-dark table-striped table-hover caption-top align-middle"
      >
        <caption>
          Displaying
          {{
            executionsState.workflowExecutions.length
          }}
          Workflow Execution
        </caption>
        <thead>
          <tr>
            <th scope="col">Workflow</th>
            <th scope="col">Status</th>
            <th scope="col">Started</th>
            <th scope="col">Ended</th>
            <th scope="col"></th>
          </tr>
        </thead>
        <tbody>
          <template v-if="executionsState.loading">
            <tr v-for="n in 5" :key="n">
              <td class="placeholder-glow w-25">
                <span class="placeholder col-6"></span>
              </td>
              <td class="placeholder-glow" style="width: 20%">
                <span class="placeholder col-4"></span>
              </td>
              <td class="placeholder-glow" style="width: 20%">
                <span class="placeholder col-6"></span>
              </td>
              <td class="placeholder-glow" style="width: 20%">
                <span class="placeholder col-6"></span>
              </td>
              <td class="text-end">
                <div
                  class="btn-group btn-group-sm dropdown-center dropdown-menu-start"
                >
                  <button type="button" class="btn btn-secondary" disabled>
                    Details
                  </button>
                  <button
                    type="button"
                    class="btn btn-secondary dropdown-toggle dropdown-toggle-split"
                    disabled
                  >
                    <span class="visually-hidden">Toggle Dropdown</span>
                  </button>
                </div>
              </td>
            </tr>
          </template>
          <template v-else-if="executionsState.workflowExecutions.length > 0">
            <tr v-for="execution in sortedExecutions" :key="execution.execution_id">
              <td
                v-if="executionsState.mappingLoading"
                class="placeholder-glow w-25"
              >
                <span class="placeholder col-6"></span>
              </td>
              <td v-else>
                <span>{{
                  executionsState.workflowMapping[execution.workflow_id]
                }}</span>
                <span
                  >@{{
                    executionsState.versionMapping[execution.workflow_version_id]
                  }}</span
                >
              </td>
              <td>
                <span
                  class="rounded-pill py-1 px-2"
                  :class="statusToColorMapping[execution.status]"
                  ><font-awesome-icon
                    class="me-2"
                    :icon="statusToIconMapping[execution.status]"
                  />{{ execution.status }}</span
                >
              </td>
              <td>{{ dayjs(execution.start_time).format("DD.MM.YYYY HH:mm") }}</td>
              <td>
                <template v-if="execution.end_time">
                  {{ dayjs(execution.end_time).format("DD.MM.YYYY HH:mm") }}
                </template>
                <template v-else> - </template>
              </td>
              <td class="text-end">
                <div
                  class="btn-group btn-group-sm dropdown-center dropdown-menu-start"
                >
                  <button type="button" class="btn btn-secondary">Details</button>
                  <button
                    type="button"
                    class="btn btn-secondary dropdown-toggle dropdown-toggle-split"
                    data-bs-toggle="dropdown"
                    aria-expanded="false"
                  >
                    <span class="visually-hidden">Toggle Dropdown</span>
                  </button>
                  <ul class="dropdown-menu dropdown-menu-dark">
                    <li v-if="workflowExecutionCancable(execution.status)">
                      <button
                        class="dropdown-item text-danger align-middle"
                        type="button"
                        @click="cancelWorkflowExecution(execution.execution_id)"
                      >
                        <font-awesome-icon icon="fa-solid fa-ban" />
                        <span class="ms-1">Cancel</span>
                      </button>
                    </li>
                    <li v-if="workflowExecutionDeletable(execution.status)">
                      <button
                        class="dropdown-item text-danger align-middle"
                        type="button"
                        data-bs-toggle="modal"
                        data-bs-target="#deleteWorkflowExecutionModal"
                        @click="executionsState.executionToDelete = execution"
                      >
                        <font-awesome-icon icon="fa-solid fa-trash" />
                        <span class="ms-1">Delete</span>
                      </button>
                    </li>
                  </ul>
                </div>
              </td>
            </tr>
          </template>
          <tr v-else>
            <td colspan="5" class="text-center"><i>No workflow executions</i></td>
          </tr>
        </tbody>
      </table>
    </template>
    
    <style scoped></style>