Skip to content
Snippets Groups Projects
WorkflowView.vue 11.24 KiB
<script setup lang="ts">
import { computed, onMounted, reactive, watch } from "vue";
import type {
  WorkflowOut,
  WorkflowStatistic,
  WorkflowVersion,
} from "@/client/workflow";
import { Status, WorkflowService } from "@/client/workflow";
import WorkflowStatisticsChart from "@/components/workflows/WorkflowStatisticsChart.vue";
import { useRoute, useRouter } from "vue-router";
import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
import {
  latestVersion as calculateLatestVersion,
  sortedVersions,
} from "@/utils/Workflow";
import { determineGitIcon } from "@/utils/GitRepository";
import { useAuthStore } from "@/stores/users";
import { useWorkflowStore } from "@/stores/workflows";

const workflowRepository = useWorkflowStore();
const userRepository = useAuthStore();

// Props
// =============================================================================
const props = defineProps<{
  workflowId: string;
  versionId?: string;
  workflowModeId?: string;
  developerView: boolean;
}>();

// Constants
// =============================================================================
const router = useRouter();
const route = useRoute();

// Reactive State
// =============================================================================
const workflowState = reactive<{
  loading: boolean;
  activeVersionId: string;
  initialOpen: boolean;
  stats: WorkflowStatistic[];
  activeModeId?: string;
}>({
  loading: true,
  activeVersionId: props.versionId ?? "",
  initialOpen: true,
  stats: [],
  activeModeId: props.workflowModeId,
});

// Watchers
// =============================================================================
watch(
  () => props.workflowId,
  (newWorkflowId, oldWorkflowId) => {
    if (newWorkflowId !== oldWorkflowId) {
      updateWorkflow(newWorkflowId);
    }
  },
);

watch(
  () => props.versionId,
  (newWorkflowId) => {
    workflowState.activeVersionId = newWorkflowId ?? "";
  },
);

watch(
  () => props.workflowModeId,
  (newModeId) => {
    if (newModeId) {
      workflowState.activeModeId = newModeId;
    }
  },
);

watch(
  () => workflowState.activeVersionId,
  (newVersionId, oldVersionId) => {
    if (
      newVersionId &&
      newVersionId !== oldVersionId &&
      route.name !== "workflow-start"
    ) {
      // If mode does not exist in other version, select another mode
      if (
        activeVersionModeIds.value.length > 0 &&
        activeVersionModeIds.value.findIndex(
          (modeId) => modeId === workflowState.activeModeId,
        ) == -1
      ) {
        workflowState.activeModeId = activeVersionModeIds.value[0];
      } else if (activeVersionModeIds.value.length == 0) {
        // If new version does not has any modes, then set mode id to none
        workflowState.activeModeId = undefined;
      }
      router.push({
        name: "workflow-version",
        params: { versionId: newVersionId },
        query: {
          tab: route.query.tab,
          workflowModeId: workflowState.activeModeId,
        },
      });
    }
  },
);

watch(
  () => workflowState.activeModeId,
  (newModeId, oldModeId) => {
    if (newModeId != oldModeId) {
      router.push({
        name: route.name ?? undefined,
        params: { versionId: workflowState.activeVersionId },
        query: { tab: route.query.tab, workflowModeId: newModeId },
      });
    }
  },
);

// Computed Properties
// =============================================================================
const workflow = computed<WorkflowOut | undefined>(() =>
  props.developerView
    ? workflowRepository.comprehensiveWorkflowMapping[props.workflowId]
    : workflowRepository.workflowMapping[props.workflowId],
);

const latestVersion = computed<WorkflowVersion | undefined>(() =>
  calculateLatestVersion(
    workflow.value?.versions?.filter(
      (version) => version.status == Status.PUBLISHED,
    ) || [],
  ),
);
const activeVersion = computed<WorkflowVersion | undefined>(
  () =>
    workflow.value?.versions.find(
      (w) => w.git_commit_hash === workflowState.activeVersionId,
    ),
);

const activeVersionModeIds = computed<string[]>(
  () => activeVersion.value?.modes ?? [],
);

const activeVersionString = computed<string>(
  () => activeVersion.value?.version ?? "",
);

const activeVersionIcon = computed<string | undefined>(
  () => activeVersion.value?.icon_url ?? undefined,
);

const versionLaunchable = computed<boolean>(
  () => activeVersion.value?.status == Status.PUBLISHED ?? false,
);

const gitIcon = computed<string>(() =>
  determineGitIcon(workflow.value?.repository_url),
);

const allowVersionDeprecation = computed<boolean>(() => {
  if (activeVersion.value?.status === Status.PUBLISHED) {
    if (userRepository.workflowReviewer || userRepository.admin) {
      return true;
    } else if (
      userRepository.workflowDev &&
      workflow.value?.developer_id === userRepository.currentUID
    ) {
      return true;
    }
  }
  return false;
});

// Functions
// =============================================================================
function updateWorkflow(workflowId: string) {
  workflowState.loading = true;
  workflowRepository
    .fetchWorkflow(props.workflowId, props.developerView, () => {
      workflowState.loading = false;
      workflowState.initialOpen = false;
    })
    .then((workflow) => {
      if (!workflowState.initialOpen || !route.params.versionId) {
        workflowState.activeVersionId =
          workflow.versions[workflow.versions.length - 1].git_commit_hash;
      } else {
        workflowState.activeVersionId = route.params.versionId as string;
      }
      workflowState.activeModeId = activeVersionModeIds.value[0] ?? undefined;
    });

  WorkflowService.workflowGetWorkflowStatistics(workflowId).then((stats) => {
    workflowState.stats = stats;
  });
}

function deprecateCurrentWorkflowVersion() {
  if (props.versionId) {
    workflowRepository.deprecateWorkflowVersion(
      props.workflowId,
      props.versionId,
    );
  }
}

// Lifecycle Events
// =============================================================================
onMounted(() => {
  updateWorkflow(props.workflowId);
});
</script>

<template>
  <div v-if="workflowState.loading">
    <div
      class="d-flex mt-5 justify-content-between align-items-center placeholder-glow"
    >
      <span class="fs-0 placeholder col-6"></span>
      <span class="fs-0 placeholder col-1"></span>
    </div>
    <div class="fs-4 mb-5 mt-4 placeholder-glow">
      <span class="placeholder col-10"></span>
    </div>
    <div class="row align-items-center placeholder-glow my-1">
      <span class="mx-auto col-2 placeholder bg-success fs-0"></span>
      <span class="position-absolute end-0 col-1 placeholder fs-2"></span>
    </div>
    <div class="row w-100 mb-4 mt-3 mx-0 placeholder-glow">
      <span class="placeholder col-3 mx-auto"></span>
    </div>
  </div>
  <div v-else-if="workflow">
    <div class="d-flex justify-content-between align-items-center">
      <h2 class="w-fit">
        {{ workflow.name }}
        <span v-if="activeVersionString">@{{ activeVersionString }}</span>
      </h2>
      <img
        v-if="activeVersionIcon != null"
        :src="activeVersionIcon"
        class="img-fluid icon"
        alt="Workflow icon"
      />
    </div>
    <p class="fs-4 mt-3">{{ workflow.short_description }}</p>
    <div
      v-if="activeVersionModeIds.length > 0"
      class="row align-items-center mb-3 fs-5"
    >
      <label class="col-sm-1 col-form-label">Mode:</label>
      <div class="col-sm-11">
        <select class="form-select w-fit" v-model="workflowState.activeModeId">
          <option
            v-for="modeId of activeVersionModeIds"
            :key="modeId"
            :value="modeId"
          >
            {{ workflowRepository.modeMapping[modeId]?.name }}
          </option>
        </select>
      </div>
    </div>
    <template v-if="route.name !== 'workflow-start'">
      <div
        v-if="!versionLaunchable"
        class="alert alert-warning w-fit mx-auto"
        role="alert"
      >
        This version can not be used.
        <router-link
          v-if="latestVersion"
          class="alert-link"
          :to="{
            name: 'workflow-version',
            params: {
              versionId: latestVersion.git_commit_hash,
            },
            query: { tab: route.query.tab },
          }"
          >Try the latest version {{ latestVersion.version }}.
        </router-link>
      </div>
      <div class="row align-items-center">
        <div class="w-fit position-absolute start-0">
          <button
            v-if="props.versionId && allowVersionDeprecation"
            type="button"
            class="btn btn-warning"
            @click="deprecateCurrentWorkflowVersion"
          >
            Deprecate version
          </button>
        </div>
        <router-link
          role="button"
          class="btn btn-success btn-lg w-fit mx-auto"
          :class="{ disabled: !versionLaunchable }"
          :to="{
            name: 'workflow-start',
            params: {
              versionId: props.versionId,
              workflowId: props.workflowId,
            },
            query: {
              workflowModeId: workflowState.activeModeId,
            },
          }"
        >
          <font-awesome-icon icon="fa-solid fa-rocket" class="me-2" />
          <span class="align-middle">Launch {{ activeVersionString }}</span>
        </router-link>
        <div
          v-if="latestVersion"
          class="input-group w-fit position-absolute end-0"
        >
          <span class="input-group-text px-2" id="workflow-version-wrapping"
            ><font-awesome-icon icon="fa-solid fa-tags" class="text-secondary"
          /></span>
          <select
            class="form-select form-select-sm"
            aria-label="Workflow version selection"
            aria-describedby="workflow-version-wrapping"
            v-model="workflowState.activeVersionId"
          >
            <option
              v-for="version in sortedVersions(workflow.versions)"
              :key="version.git_commit_hash"
              :value="version.git_commit_hash"
            >
              {{ version.version }}
            </option>
          </select>
        </div>
      </div>
      <div class="row w-100 mb-4 mt-2 mx-0 border-bottom pb-2">
        <a
          :href="workflow.repository_url"
          target="_blank"
          class="text-secondary text-decoration-none mx-auto w-fit p-0"
        >
          <font-awesome-icon :icon="gitIcon" class="me-1" />
          <span class="align-middle"> {{ workflow.repository_url }}</span>
          <font-awesome-icon
            v-if="workflow?.private"
            icon="fa-solid fa-lock"
            class="ms-1"
          />
        </a>
      </div>
      <workflow-statistics-chart
        :stats="workflowState.stats"
        v-if="workflowState.stats"
      />
    </template>
  </div>
  <router-view v-if="workflowState.loading || workflow" />
  <div v-else class="text-center fs-1 mt-5">
    <font-awesome-icon
      icon="fa-solid fa-magnifying-glass"
      class="my-5 fs-0"
      style="color: var(--bs-secondary)"
    />
    <p class="my-5">
      Could not find any Workflow with ID <br />'{{ workflowId }}'
    </p>
    <router-link :to="{ name: 'workflows' }" class="mt-5">Back</router-link>
  </div>
</template>

<style scoped>
.icon {
  max-width: 64px;
  max-height: 64px;
  min-width: 50px;
  min-height: 50px;
}
</style>