diff --git a/src/components/parameter-schema/ParameterSchemaFormComponent.vue b/src/components/parameter-schema/ParameterSchemaFormComponent.vue index 69dd3cd636bd7fc91331f36de104d7f2ab203b65..aed548155d295a5a6e57765799f9626a3661e422 100644 --- a/src/components/parameter-schema/ParameterSchemaFormComponent.vue +++ b/src/components/parameter-schema/ParameterSchemaFormComponent.vue @@ -9,9 +9,11 @@ import { Toast } from "bootstrap"; import { useBucketStore } from "@/stores/buckets"; import { useS3KeyStore } from "@/stores/s3keys"; import BootstrapToast from "@/components/BootstrapToast.vue"; +import { useResourceStore } from "@/stores/resources"; const bucketRepository = useBucketStore(); -const s3KeyRepository = useS3KeyStore(); +const resourceRepository = useResourceStore(); +const keyRepository = useS3KeyStore(); // Props // ============================================================================= @@ -181,7 +183,8 @@ onMounted(() => { if (props.schema) updateSchema(props.schema); bucketRepository.fetchBuckets(); bucketRepository.fetchOwnPermissions(); - s3KeyRepository.fetchS3Keys(); + keyRepository.fetchS3Keys(); + resourceRepository.fetchPublicResources(); errorToast = new Toast("#workflowExecutionErrorToast"); }); </script> @@ -230,14 +233,11 @@ onMounted(() => { <h5 class="card-title"> General Options about the pipeline execution </h5> - <template v-if="props.allowNotes"> + <div v-if="props.allowNotes"> + <code class="bg-secondary-subtle p-2 rounded-top">--notes</code> <div class="input-group"> <span class="input-group-text" id="pipelineNotes"> - <font-awesome-icon - class="me-2" - icon="fa-solid fa-sticky-note" - /> - <code>--notes</code> + <font-awesome-icon icon="fa-solid fa-sticky-note" /> </span> <textarea class="form-control" @@ -248,60 +248,75 @@ onMounted(() => { <label class="mb-3" for="pipelineNotes" >Personal notes about the pipeline execution</label > - </template> - <div class="input-group"> - <span class="input-group-text" id="logsS3Path"> - <font-awesome-icon class="me-2" icon="fa-solid fa-folder" /> - <code>--logs_s3_path</code> - </span> - <parameter-string-input - parameter-name="logs_s3_path" - v-model="formState.logs_s3_path" - :parameter="{ - format: 'directory-path', - type: 'string', - }" - /> </div> - <label class="mb-3" for="logsS3Path"> - Directory in bucket where to save Nextflow log and reports - </label> - <div class="input-group"> - <span class="input-group-text" id="provenanceS3Path"> - <font-awesome-icon class="me-2" icon="fa-solid fa-folder" /> - <code>--provenance_s3_path</code> - </span> - <parameter-string-input - parameter-name="provenance_s3_path" - v-model="formState.provenance_s3_path" - :parameter="{ - format: 'directory-path', - type: 'string', - }" - /> + <div> + <code class="bg-secondary-subtle p-2 rounded-top" + >--logs_s3_path</code + > + <div class="input-group"> + <span class="input-group-text"> + <font-awesome-icon icon="fa-solid fa-folder" /> + </span> + <parameter-string-input + parameter-name="logs_s3_path" + v-model="formState.logs_s3_path" + :parameter="{ + format: 'directory-path', + type: 'string', + }" + remove-advanced + /> + </div> + <label class="mb-3" for="logsS3Path"> + Directory in bucket where to save Nextflow log and reports + </label> </div> - <label class="mb-3" for="provenanceS3Path"> - Directory in bucket where to save provenance information about the - workflow execution - </label> - <div class="input-group"> - <span class="input-group-text" id="debugS3Path"> - <font-awesome-icon class="me-2" icon="fa-solid fa-folder" /> - <code>--debug_s3_path</code> - </span> - <parameter-string-input - parameter-name="debug_s3_path" - v-model="formState.debug_s3_path" - :parameter="{ - format: 'directory-path', - type: 'string', - }" - /> + <div> + <code class="bg-secondary-subtle p-2 rounded-top" + >--provenance_s3_path</code + > + <div class="input-group"> + <span class="input-group-text"> + <font-awesome-icon icon="fa-solid fa-folder" /> + </span> + <parameter-string-input + parameter-name="provenance_s3_path" + v-model="formState.provenance_s3_path" + :parameter="{ + format: 'directory-path', + type: 'string', + }" + remove-advanced + /> + </div> + <label class="mb-3" for="provenanceS3Path"> + Directory in bucket where to save provenance information about the + workflow execution + </label> + </div> + <div> + <code class="bg-secondary-subtle p-2 rounded-top" + >--debug_s3_path</code + > + <div class="input-group"> + <span class="input-group-text"> + <font-awesome-icon icon="fa-solid fa-folder" /> + </span> + <parameter-string-input + parameter-name="debug_s3_path" + v-model="formState.debug_s3_path" + :parameter="{ + format: 'directory-path', + type: 'string', + }" + remove-advanced + /> + </div> + <label class="mb-3" for="debugS3Path"> + Directory in bucket where to save debug information about the + workflow execution + </label> </div> - <label class="mb-3" for="debugS3Path"> - Directory in bucket where to save debug information about the - workflow execution - </label> </div> </div> </form> diff --git a/src/components/parameter-schema/form-mode/ParameterGroupForm.vue b/src/components/parameter-schema/form-mode/ParameterGroupForm.vue index 2f45dc9c08ce436ba9a7939c75df0a8df8cfa6aa..7789da50ea7c040b399271b5d35d7cf5706a9fff 100644 --- a/src/components/parameter-schema/form-mode/ParameterGroupForm.vue +++ b/src/components/parameter-schema/form-mode/ParameterGroupForm.vue @@ -86,14 +86,12 @@ watch( :key="parameterName" > <div :hidden="!showHidden && parameter['hidden']"> + <code class="bg-secondary-subtle p-2 rounded-top" + >--{{ parameter["name"] ?? parameterName }}</code + > <div class="input-group"> - <span class="input-group-text" :id="parameterName + '-help'"> - <font-awesome-icon - class="me-2" - :icon="parameter['fa_icon']" - v-if="parameter['fa_icon']" - /> - <code>--{{ parameterName }}</code> + <span class="input-group-text" v-if="parameter['fa_icon']"> + <font-awesome-icon :icon="parameter['fa_icon']" /> </span> <parameter-number-input v-if=" @@ -145,9 +143,11 @@ watch( class="input-group-text cursor-pointer px-2" v-if="parameter['help_text']" data-bs-toggle="collapse" - :data-bs-target="'#helpCollapse' + parameterName" + :data-bs-target=" + '#helpCollapse' + parameterName.replace(/\./g, '') + " aria-expanded="false" - :aria-controls="'helpCollapse' + parameterName" + :aria-controls="'helpCollapse' + parameterName.replace(/\./g, '')" > <font-awesome-icon class="cursor-pointer" @@ -160,7 +160,7 @@ watch( </label> <div class="collapse" - :id="'helpCollapse' + parameterName" + :id="'helpCollapse' + parameterName.replace(/\./g, '')" v-if="parameter['help_text']" > <div class="p-2 pb-0 mx-2 mb-2 mt-1 flex-shrink-1 border rounded"> diff --git a/src/components/parameter-schema/form-mode/ParameterStringInput.vue b/src/components/parameter-schema/form-mode/ParameterStringInput.vue index 0ba406b7f1eb838040cd2b60dc2101f1652bebb9..7d38aae038ea9d12f597f758a377fa08d7d42f81 100644 --- a/src/components/parameter-schema/form-mode/ParameterStringInput.vue +++ b/src/components/parameter-schema/form-mode/ParameterStringInput.vue @@ -1,10 +1,14 @@ <script setup lang="ts"> -import { computed, watch, ref, onMounted, reactive } from "vue"; +import { computed, watch, onMounted, reactive } from "vue"; import { useBucketStore } from "@/stores/buckets"; import { useS3ObjectStore } from "@/stores/s3objects"; +import { useResourceStore } from "@/stores/resources"; +import { Status } from "@/client/resource"; const bucketRepository = useBucketStore(); const s3objectRepository = useS3ObjectStore(); +const resourceRepository = useResourceStore(); +const randomIDSuffix = Math.random().toString(16).substring(2, 8); const props = defineProps({ parameter: { @@ -25,50 +29,49 @@ const props = defineProps({ helpId: { type: String, }, + removeAdvanced: { + type: Boolean, + default: false, + }, }); -const randomIDSuffix = Math.random().toString(16).substring(2, 8); - -const defaultValue = computed<string>(() => props.parameter["default"]); +const emit = defineEmits<{ + (e: "update:modelValue", value?: string): void; +}>(); const s3Path = reactive<{ - bucket: string | undefined; - key: string | undefined; + bucket: string; + key?: string; }>({ - bucket: undefined, + bucket: "", key: undefined, }); -watch(defaultValue, (newVal, oldVal) => { - if (newVal != oldVal && newVal != undefined) { - emit("update:modelValue", newVal); - } +const selectedResource = reactive<{ + resourceId: string; + resourceVersionIndex: number; +}>({ + resourceId: "", + resourceVersionIndex: 0, }); -watch(s3Path, () => { - if (format.value) { - updateValue(); - } +const formState = reactive<{ + advancedInput: boolean; + stringVal?: string; +}>({ + advancedInput: false, + stringVal: undefined, }); -watch( - () => s3Path.bucket, - (newVal, oldVal) => { - if (newVal !== oldVal) { - updateKeysInBucket(newVal); - } - }, -); - +const defaultValue = computed<string>(() => props.parameter["default"]); const pattern = computed<string>(() => props.parameter["pattern"]); - -const emit = defineEmits<{ - (e: "update:modelValue", value: string | undefined): void; -}>(); - -const stringInput = ref<HTMLInputElement | undefined>(undefined); - -const format = computed<string | undefined>(() => props.parameter["format"]); +const dataFormat = computed<string | undefined>( + () => props.parameter["format"], +); +const dataPath = computed<boolean>(() => dataFormat.value != undefined); +const clowmResource = computed<boolean>( + () => props.parameter["clowm-resource"] ?? false, +); const filesInBucket = computed<string[]>(() => (s3objectRepository.objectMapping[s3Path.bucket ?? ""] ?? []) @@ -98,7 +101,7 @@ const filesAndFoldersInBucket = computed<string[]>(() => ); const keyDataList = computed<string[]>(() => { - switch (format.value) { + switch (dataFormat.value) { case "file-path": return filesInBucket.value; case "directory-path": @@ -110,23 +113,63 @@ const keyDataList = computed<string[]>(() => { } }); -function updateValue() { - if (format.value) { - emit( - "update:modelValue", - !s3Path.bucket && s3Path.key - ? undefined - : `s3://${s3Path.bucket}${s3Path.key ? "/" + s3Path.key : ""}`, - ); - } else { - emit( - "update:modelValue", - stringInput.value?.value ? stringInput.value?.value : undefined, - ); +watch(defaultValue, (newVal, oldVal) => { + if (newVal != oldVal && newVal != undefined) { + emit("update:modelValue", newVal); } +}); + +watch(s3Path, () => { + if (dataPath.value && !formState.advancedInput) { + updateStringFromS3(); + } +}); + +watch( + () => s3Path.bucket, + (newVal, oldVal) => { + if (newVal !== oldVal) { + updateKeysInBucket(newVal); + } + }, +); + +watch(() => formState.stringVal, updateValue); +watch(selectedResource, () => { + if (clowmResource.value && !formState.advancedInput) { + updateStringFromResource(); + } +}); + +watch( + () => formState.advancedInput, + (newVal, oldValue) => { + if (newVal != oldValue) { + if (clowmResource.value) { + updateStringFromResource(); + } else if (dataPath.value) { + updateStringFromS3(); + } + } + }, +); + +function updateValue() { + emit("update:modelValue", formState.stringVal); } -const helpTextPresent = computed<boolean>(() => props.parameter["help_text"]); +function updateStringFromS3() { + formState.stringVal = !s3Path.bucket + ? undefined + : `s3://${s3Path.bucket}${s3Path.key ? "/" + s3Path.key : ""}`; +} + +function updateStringFromResource() { + formState.stringVal = + resourceRepository.resourceMapping[selectedResource.resourceId]?.versions[ + selectedResource.resourceVersionIndex + ]?.cluster_path ?? undefined; +} function updateKeysInBucket(bucketName?: string) { if (bucketName != null) { @@ -138,14 +181,46 @@ function updateKeysInBucket(bucketName?: string) { } onMounted(() => { - if (format.value) { - s3Path.key = defaultValue.value; - } + formState.stringVal = defaultValue.value; }); </script> <template> - <template v-if="format"> + <template v-if="clowmResource && !formState.advancedInput"> + <select + class="form-select" + :required="props.required" + v-model="selectedResource.resourceId" + > + <option selected disabled value="">Please select a resource</option> + <option + v-for="resource in resourceRepository.resources" + :key="resource.resource_id" + :value="resource.resource_id" + > + {{ resource.name }} + </option> + </select> + <select + class="form-select" + :required="props.required || selectedResource.resourceId != undefined" + v-model="selectedResource.resourceVersionIndex" + :disabled="selectedResource.resourceId.length === 0" + > + <option disabled>Please select a version</option> + <option + v-for="(version, index) in resourceRepository.resourceMapping[ + selectedResource.resourceId + ]?.versions ?? []" + :key="version.resource_version_id" + :value="index" + > + {{ version.release }} + {{ version.status === Status.LATEST ? "- Latest" : "" }} + </option> + </select> + </template> + <template v-else-if="dataPath && !formState.advancedInput"> <select class="form-select" :required="props.required" @@ -162,10 +237,9 @@ onMounted(() => { </select> <input class="form-control" - :class="{ 'rounded-end': !helpTextPresent }" :list="'datalistOptions2' + randomIDSuffix" placeholder="Type to search in bucket..." - :required="props.required && format === 'file-path'" + :required="props.required && dataFormat === 'file-path'" v-model="s3Path.key" :pattern="pattern" /> @@ -173,17 +247,32 @@ onMounted(() => { <option v-for="obj in keyDataList" :value="obj" :key="obj" /> </datalist> </template> - <input - v-else - ref="stringInput" - class="form-control" - type="text" - :value="props.modelValue" - :required="props.required" - :aria-describedby="props.helpId" - :pattern="pattern" - @input="updateValue" - /> + <template v-else> + <input + ref="stringInput" + class="form-control" + type="text" + v-model="formState.stringVal" + :required="props.required" + :aria-describedby="props.helpId" + :pattern="pattern" + /> + </template> + <div v-if="(clowmResource || dataPath) && !props.removeAdvanced"> + <input + type="checkbox" + class="btn-check" + :id="'flexCheckDefault' + randomIDSuffix" + autocomplete="off" + v-model="formState.advancedInput" + /> + <label + class="btn btn-outline-secondary rounded-end" + style="border-top-left-radius: 0; border-bottom-left-radius: 0" + :for="'flexCheckDefault' + randomIDSuffix" + >Advanced</label + > + </div> </template> <style scoped></style> diff --git a/src/components/resources/ResourceCard.vue b/src/components/resources/ResourceCard.vue index c78706a69d815bc84f294e7979f00d1b554eb079..ad96e63899a6a8b0f5f47a235e39d29658b4afa3 100644 --- a/src/components/resources/ResourceCard.vue +++ b/src/components/resources/ResourceCard.vue @@ -158,7 +158,8 @@ onMounted(() => { resourceVersion.resource_version_id " > - {{ resourceVersion.release }} - {{ stateToUIMapping[resourceVersion.status] }} + {{ resourceVersion.release }} - + {{ stateToUIMapping[resourceVersion.status] }} </button> </h2> <div diff --git a/src/components/resources/ResourceVersionInfoModal.vue b/src/components/resources/ResourceVersionInfoModal.vue index c1f38e4bc77404339ffb2f2391f437fc86d6d08a..1fb0e2fdff5f56d9899938a9d9febcdb9b49c1ef 100644 --- a/src/components/resources/ResourceVersionInfoModal.vue +++ b/src/components/resources/ResourceVersionInfoModal.vue @@ -27,7 +27,9 @@ const resourceVersion = computed<ResourceVersionOut | undefined>( > <template #header >Resource - <b v-if="resourceVersion">{{resource?.name}}@{{ resourceVersion?.release }}</b> + <b v-if="resourceVersion" + >{{ resource?.name }}@{{ resourceVersion?.release }}</b + > </template> <template #body> <h5>Resource</h5> diff --git a/src/components/workflows/WorkflowStatisticsChart.vue b/src/components/workflows/WorkflowStatisticsChart.vue index ee4711cfc8829daa8390973a303b1af6aaf1da07..56a9648686e8d2b153373cc096bc43fdf0366cc5 100644 --- a/src/components/workflows/WorkflowStatisticsChart.vue +++ b/src/components/workflows/WorkflowStatisticsChart.vue @@ -135,7 +135,7 @@ onMounted(() => { chart = new Chart(canvas.value, { type: "bar", data: { - labels: [], // Days as lables + labels: [], // Days as labels datasets: [ { // Workflow count per day diff --git a/src/views/resources/ReviewResourceView.vue b/src/views/resources/ReviewResourceView.vue index 03c361450718b1ab9069d2429603390b176432b7..4e9a33a08a34f0fe619d57d0e1ca129f19e75d14 100644 --- a/src/views/resources/ReviewResourceView.vue +++ b/src/views/resources/ReviewResourceView.vue @@ -6,12 +6,10 @@ import { type ResourceVersionOut, Status, } from "@/client/resource"; -import CopyToClipboardIcon from "@/components/CopyToClipboardIcon.vue"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; import { Tooltip } from "bootstrap"; import ResourceVersionInfoModal from "@/components/resources/ResourceVersionInfoModal.vue"; -import BucketDetailModal from "@/components/object-storage/modals/BucketDetailModal.vue"; -import {useNameStore} from "@/stores/names"; +import { useNameStore } from "@/stores/names"; const resourceRepository = useResourceStore(); const nameRepository = useNameStore();