From 2ba00971acbcb2c0f89ff214fbe4b44e67d1ff86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20G=C3=B6bel?= <dgoebel@techfak.uni-bielefeld.de> Date: Thu, 12 Oct 2023 14:20:37 +0200 Subject: [PATCH] Use object repository in parameter schema form #71 --- .../ParameterSchemaFormComponent.vue | 44 +++++++++++-------- .../form-mode/ParameterStringInput.vue | 27 ++++++------ src/stores/s3keys.ts | 4 +- src/views/LoginView.vue | 12 ++--- src/views/object-storage/BucketView.vue | 14 +++--- src/views/object-storage/S3KeysView.vue | 2 +- .../workflows/ListWorkflowExecutionsView.vue | 18 +++++++- src/views/workflows/WorkflowView.vue | 2 +- 8 files changed, 74 insertions(+), 49 deletions(-) diff --git a/src/components/parameter-schema/ParameterSchemaFormComponent.vue b/src/components/parameter-schema/ParameterSchemaFormComponent.vue index ae1a09d..fea414f 100644 --- a/src/components/parameter-schema/ParameterSchemaFormComponent.vue +++ b/src/components/parameter-schema/ParameterSchemaFormComponent.vue @@ -6,6 +6,11 @@ import Ajv from "ajv"; import type { ValidateFunction } from "ajv"; import ParameterStringInput from "@/components/parameter-schema/form-mode/ParameterStringInput.vue"; import { Toast } from "bootstrap"; +import { useBucketStore } from "@/stores/buckets"; +import { useS3KeyStore } from "@/stores/s3keys"; + +const bucketRepository = useBucketStore(); +const s3KeyRepository = useS3KeyStore(); // Props // ============================================================================= @@ -165,6 +170,9 @@ function startWorkflow() { // ============================================================================= onMounted(() => { if (props.schema) updateSchema(props.schema); + bucketRepository.fetchBuckets(); + bucketRepository.fetchOwnPermissions(); + s3KeyRepository.fetchS3Keys(); errorToast = new Toast("#workflowExecutionErrorToast"); }); </script> @@ -208,6 +216,18 @@ onMounted(() => { @submit.prevent="startWorkflow" novalidate > + <template v-for="(group, groupName) in parameterGroups" :key="groupName"> + <parameter-group-form + :modelValue="formState.formInput[groupName]" + @update:model-value=" + (newValue) => (formState.formInput[groupName] = newValue) + " + v-if="formState.formInput[groupName]" + :parameter-group-name="groupName" + :parameter-group="group" + :showHidden="formState.showHidden" + /> + </template> <div class="card mb-3"> <h2 class="card-header" id="pipelineGeneralOptions"> <font-awesome-icon icon="fa-solid fa-gear" class="me-2" /> @@ -256,18 +276,6 @@ onMounted(() => { </label> </div> </div> - <template v-for="(group, groupName) in parameterGroups" :key="groupName"> - <parameter-group-form - :modelValue="formState.formInput[groupName]" - @update:model-value=" - (newValue) => (formState.formInput[groupName] = newValue) - " - v-if="formState.formInput[groupName]" - :parameter-group-name="groupName" - :parameter-group="group" - :showHidden="formState.showHidden" - /> - </template> </form> <!-- Loading card --> <div v-else class="col-9"> @@ -317,12 +325,6 @@ onMounted(() => { <nav class="h-100"> <nav v-if="props.schema" class="nav"> <ul class="ps-0"> - <li class="nav-link"> - <a href="#pipelineGeneralOptions"> - <font-awesome-icon icon="fa-solid fa-gear" class="me-2" /> - General Pipeline Options - </a> - </li> <li class="nav-link" v-for="group in navParameterGroups" @@ -337,6 +339,12 @@ onMounted(() => { {{ group.title }}</a > </li> + <li class="nav-link"> + <a href="#pipelineGeneralOptions"> + <font-awesome-icon icon="fa-solid fa-gear" class="me-2" /> + General Pipeline Options + </a> + </li> </ul> <div class="mx-auto mb-3"> <input diff --git a/src/components/parameter-schema/form-mode/ParameterStringInput.vue b/src/components/parameter-schema/form-mode/ParameterStringInput.vue index 7eca836..cbfa064 100644 --- a/src/components/parameter-schema/form-mode/ParameterStringInput.vue +++ b/src/components/parameter-schema/form-mode/ParameterStringInput.vue @@ -1,9 +1,10 @@ <script setup lang="ts"> import { computed, watch, ref, onMounted, reactive } from "vue"; import { useBucketStore } from "@/stores/buckets"; -import { ObjectService } from "@/client/s3proxy"; +import { useS3ObjectStore } from "@/stores/s3objects"; const bucketRepository = useBucketStore(); +const s3objectRepository = useS3ObjectStore(); const props = defineProps({ parameter: { @@ -38,8 +39,6 @@ const s3Path = reactive<{ key: undefined, }); -const keysInBucket = ref<string[]>([]); - watch(defaultValue, (newVal, oldVal) => { if (newVal != oldVal && newVal != undefined) { emit("update:modelValue", newVal); @@ -72,13 +71,18 @@ const stringInput = ref<HTMLInputElement | undefined>(undefined); const format = computed<string | undefined>(() => props.parameter["format"]); const filesInBucket = computed<string[]>(() => - keysInBucket.value.filter((obj) => !obj.endsWith("/")), + (s3objectRepository.objectMapping[s3Path.bucket ?? ""] ?? []) + .filter((obj) => !obj.Key?.endsWith("/")) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + .map((obj) => obj.Key!), ); const foldersInBucket = computed<string[]>(() => - keysInBucket.value + (s3objectRepository.objectMapping[s3Path.bucket ?? ""] ?? []) + .filter((obj) => obj.Key != undefined) .map((obj) => { - const parts = obj.split("/"); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const parts = obj.Key!.split("/"); return parts .slice(0, parts.length - 1) .map((part, index) => @@ -126,17 +130,14 @@ const helpTextPresent = computed<boolean>(() => props.parameter["help_text"]); function updateKeysInBucket(bucketName?: string) { if (bucketName != null) { - ObjectService.objectGetBucketObjects(bucketName).then((objs) => { - keysInBucket.value = objs.map((obj) => obj.key); - }); - } else { - keysInBucket.value = []; + s3objectRepository.fetchS3Objects( + bucketName, + bucketRepository.ownPermissions[bucketName]?.file_prefix ?? undefined, + ); } } onMounted(() => { - bucketRepository.fetchBuckets(); - bucketRepository.fetchOwnPermissions(); if (format.value) { s3Path.key = defaultValue.value; } diff --git a/src/stores/s3keys.ts b/src/stores/s3keys.ts index c174a6e..915426f 100644 --- a/src/stores/s3keys.ts +++ b/src/stores/s3keys.ts @@ -22,11 +22,11 @@ export const useS3KeyStore = defineStore({ }, }, actions: { - fetchS3Keys(uid: string, onFinally?: () => void): Promise<S3Key[]> { + fetchS3Keys(onFinally?: () => void): Promise<S3Key[]> { if (this.keys.length > 0) { onFinally?.(); } - return S3KeyService.s3KeyGetUserKeys(uid) + return S3KeyService.s3KeyGetUserKeys(useAuthStore().currentUID) .then((keys) => { const s3ObjectRepository = useS3ObjectStore(); s3ObjectRepository.updateS3Client(keys[0]); diff --git a/src/views/LoginView.vue b/src/views/LoginView.vue index 20c717d..2f0d18a 100644 --- a/src/views/LoginView.vue +++ b/src/views/LoginView.vue @@ -64,7 +64,7 @@ onMounted(() => { class="img-fluid mb-3" width="128" height="128" - alt="..." + alt="CloWM Logo" /> <h1> <span class="blue fw-bold">Clo</span><span class="red fw-bold">W</span @@ -98,7 +98,7 @@ onMounted(() => { <img src="/src/assets/images/nfdi.svg" alt="NFDI4Microbiota Logo" - height="70" + height="50" /> </a> </div> @@ -108,25 +108,25 @@ onMounted(() => { <img src="/src/assets/images/denbi.svg" alt="de.NBI Logo" - height="70" + height="50" /> </a> </div> <div class="border rounded p-4 icon text-center"> <h4 class="mb-4">Hosted By</h4> <a href="https://bibi.uni-bielefeld.de/"> - <img src="/src/assets/images/bibi.png" alt="BiBi Logo" height="70" /> + <img src="/src/assets/images/bibi.png" alt="BiBi Logo" height="50" /> </a> </div> <div class="border rounded p-4 icon text-center"> <h4 class="mb-4">Funded By</h4> - <img src="/src/assets/images/dfg.png" alt="DFG Logo" height="70" /> + <img src="/src/assets/images/dfg.png" alt="DFG Logo" height="50" /> </div> <div class="border rounded p-4 icon text-center"> <img src="/src/assets/images/unibi.svg" alt="Bielefeld University Logo" - height="70" + height="50" /> </div> </div> diff --git a/src/views/object-storage/BucketView.vue b/src/views/object-storage/BucketView.vue index 5966934..39d238f 100644 --- a/src/views/object-storage/BucketView.vue +++ b/src/views/object-storage/BucketView.vue @@ -267,7 +267,7 @@ onMounted(() => { } }; // wait till s3keys and ownPermissions are available before fetching objects - s3KeyRepository.fetchS3Keys(authStore.currentUID, onFinally); + s3KeyRepository.fetchS3Keys(onFinally); bucketRepository.fetchOwnPermissions(onFinally); document @@ -488,9 +488,9 @@ function getObjectFileName(key: string): string { </nav> <!-- Inputs on top --> <!-- Search bucket text input --> - <div class="row"> - <div class="col-5 me-auto"> - <div class="input-group mt-2 rounded shadow-sm"> + <div class="d-flex justify-content-between align-items-center"> + <div class="flex-grow-1 me-2"> + <div class="input-group rounded shadow-sm"> <span class="input-group-text" id="objects-search-wrapping" ><font-awesome-icon icon="fa-solid fa-magnifying-glass" /></span> @@ -506,10 +506,10 @@ function getObjectFileName(key: string): string { </div> </div> <!-- Upload object button --> - <div id="BucketViewButtons" class="col-auto"> + <div id="BucketViewButtons" class=""> <button type="button" - class="btn btn-light me-4 tooltip-container border shadow-sm" + class="btn btn-light me-3 tooltip-container border shadow-sm" :disabled="errorLoadingObjects" data-bs-toggle="tooltip" data-bs-title="Refresh Objects" @@ -538,7 +538,7 @@ function getObjectFileName(key: string): string { <!-- Add folder button --> <button type="button" - class="btn btn-light me-4 tooltip-container border shadow-sm" + class="btn btn-light me-3 tooltip-container border shadow-sm" :disabled="errorLoadingObjects || !writableBucket" data-bs-toggle="modal" data-bs-title="Create Folder" diff --git a/src/views/object-storage/S3KeysView.vue b/src/views/object-storage/S3KeysView.vue index a9072b8..ffade65 100644 --- a/src/views/object-storage/S3KeysView.vue +++ b/src/views/object-storage/S3KeysView.vue @@ -26,7 +26,7 @@ const allowKeyDeletion = computed<boolean>(() => keyRepository.keys.length > 1); function fetchKeys() { keyRepository - .fetchS3Keys(authStore.currentUID, () => (keyState.initialLoading = false)) + .fetchS3Keys(() => (keyState.initialLoading = false)) .then((keys) => { if (keyState.activeKey >= keys.length) { keyState.activeKey = keys.length - 1; diff --git a/src/views/workflows/ListWorkflowExecutionsView.vue b/src/views/workflows/ListWorkflowExecutionsView.vue index 5144545..691068c 100644 --- a/src/views/workflows/ListWorkflowExecutionsView.vue +++ b/src/views/workflows/ListWorkflowExecutionsView.vue @@ -1,6 +1,6 @@ <script setup lang="ts"> import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; -import { onMounted, reactive, computed } from "vue"; +import { onMounted, reactive, computed, onUnmounted } from "vue"; import type { WorkflowExecutionOut } from "@/client/workflow"; import { WorkflowExecutionStatus } from "@/client/workflow"; import dayjs from "dayjs"; @@ -13,6 +13,7 @@ const workflowRepository = useWorkflowStore(); const executionRepository = useWorkflowExecutionStore(); let refreshTimeout: NodeJS.Timeout | undefined = undefined; +let intervalId: NodeJS.Timer | undefined = undefined; const executionsState = reactive<{ loading: boolean; @@ -115,11 +116,26 @@ function cancelWorkflowExecution(executionId: string) { executionRepository.cancelExecution(executionId); } +function refreshRunningWorkflowExecution() { + Promise.all( + executionRepository.executions + .filter((execution) => workflowExecutionCancelable(execution.status)) + .map((execution) => + executionRepository.fetchExecution(execution.execution_id), + ), + ); +} + onMounted(() => { workflowRepository.fetchWorkflows(); updateExecutions(); + intervalId = setInterval(refreshRunningWorkflowExecution, 5000); new Tooltip("#refreshExecutionsButton"); }); + +onUnmounted(() => { + clearInterval(intervalId); +}); </script> <template> diff --git a/src/views/workflows/WorkflowView.vue b/src/views/workflows/WorkflowView.vue index ceea0ba..4b66830 100644 --- a/src/views/workflows/WorkflowView.vue +++ b/src/views/workflows/WorkflowView.vue @@ -255,7 +255,7 @@ onMounted(() => { v-if="activeVersionModeIds.length > 0" class="row align-items-center mb-3 fs-5" > - <label class="col-sm-1 col-form-label">Mode:</label> + <label class="col-sm-1 col-form-label"><b>Mode:</b></label> <div class="col-sm-11"> <select class="form-select w-fit" v-model="workflowState.activeModeId"> <option -- GitLab