diff --git a/package.json b/package.json index 48e0abaae2d914245cfd26074085b57924789922..ef34c45d84544cffbc0c15a0bb49df00bab83dad 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,10 @@ "build-only": "vite build", "type-check": "vue-tsc --noEmit", "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", - "generate-s3-client": "openapi --input http://localhost:9999/api/s3proxy-service/openapi.json --output src/client/s3proxy --client axios", - "generate-auth-client": "openapi --input http://localhost:9999/api/auth-service/openapi.json --output src/client/auth --client axios", - "generate-workflow-client": "openapi --input http://localhost:9999/api/workflow-service/openapi.json --output src/client/workflow --client axios", - "generate-resource-client": "openapi --input http://localhost:9999/api/resource-service/openapi.json --output src/client/resource --client axios" + "generate-s3-client": "openapi --input https://clowm-staging.bi.denbi.de/api/s3proxy-service/openapi.json --output src/client/s3proxy --client axios", + "generate-auth-client": "openapi --input https://clowm-staging.bi.denbi.de/api/auth-service/openapi.json --output src/client/auth --client axios", + "generate-workflow-client": "openapi --input https://clowm-staging.bi.denbi.de/api/workflow-service/openapi.json --output src/client/workflow --client axios", + "generate-resource-client": "openapi --input https://clowm-staging.bi.denbi.de/api/resource-service/openapi.json --output src/client/resource --client axios" }, "dependencies": { "@aws-sdk/client-s3": "^3.440.0", diff --git a/src/components/parameter-schema/ParameterSchemaDescriptionComponent.vue b/src/components/parameter-schema/ParameterSchemaDescriptionComponent.vue index 2ad76fb820645d7f25c4fb498fee237c7d3b1227..050fa5cba2d926aa17d775a39b122c95791fae70 100644 --- a/src/components/parameter-schema/ParameterSchemaDescriptionComponent.vue +++ b/src/components/parameter-schema/ParameterSchemaDescriptionComponent.vue @@ -1,13 +1,18 @@ <script setup lang="ts"> -import { computed, ref } from "vue"; +import { computed, type PropType, ref } from "vue"; import ParameterGroupDescription from "@/components/parameter-schema/description-mode/ParameterGroupDescription.vue"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; +import type { ClowmInfo } from "@/types/ClowmInfo"; const props = defineProps({ schema: { type: Object, required: true, }, + clowmInfo: { + type: Object as PropType<ClowmInfo>, + required: false, + }, }); type ParameterGroup = { @@ -50,8 +55,10 @@ const parameterGroups = computed<Record<string, never>>( <div v-for="(group, groupName) in parameterGroups" :key="groupName"> <parameter-group-description :parameter-group="group" + class="schema-group-description" :parameter-group-name="groupName" :show-hidden="showHidden" + :resource-parameters="props.clowmInfo?.resourceParameters" /> </div> </div> @@ -98,4 +105,8 @@ const parameterGroups = computed<Record<string, never>>( </div> </template> -<style scoped></style> +<style scoped> +.schema-group-description:last-of-type { + border-bottom: var(--bs-border-width) var(--bs-border-style) !important; +} +</style> diff --git a/src/components/parameter-schema/ParameterSchemaFormComponent.vue b/src/components/parameter-schema/ParameterSchemaFormComponent.vue index 7c3881ca6fdb4a368b94acd15feaaa5352b78ba0..53e57e0d7e93b8eb615f6c171b62f16ae7c1b336 100644 --- a/src/components/parameter-schema/ParameterSchemaFormComponent.vue +++ b/src/components/parameter-schema/ParameterSchemaFormComponent.vue @@ -1,5 +1,5 @@ <script setup lang="ts"> -import { computed, ref, reactive, watch, onMounted } from "vue"; +import { computed, ref, reactive, watch, onMounted, type PropType } from "vue"; import ParameterGroupForm from "@/components/parameter-schema/form-mode/ParameterGroupForm.vue"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; import Ajv from "ajv"; @@ -11,6 +11,7 @@ import { useS3KeyStore } from "@/stores/s3keys"; import BootstrapToast from "@/components/BootstrapToast.vue"; import { useResourceStore } from "@/stores/resources"; import { useRoute, useRouter } from "vue-router"; +import type { ClowmInfo } from "@/types/ClowmInfo"; const bucketRepository = useBucketStore(); const resourceRepository = useResourceStore(); @@ -24,6 +25,10 @@ const props = defineProps({ schema: { type: Object, }, + clowmInfo: { + type: Object as PropType<ClowmInfo>, + required: false, + }, loading: { type: Boolean, }, @@ -250,13 +255,14 @@ onMounted(() => { :parameter-group="group" :showHidden="showHidden" :show-optional="showOptional" + :resource-parameters="props.clowmInfo?.resourceParameters" /> </template> <div class="card mb-3"> - <h2 class="card-header" id="pipelineGeneralOptions"> + <h3 class="card-header" id="pipelineGeneralOptions"> <font-awesome-icon icon="fa-solid fa-gear" class="me-2" /> Pipeline Options - </h2> + </h3> <div class="card-body"> <h5 class="card-title"> General Options about the pipeline execution diff --git a/src/components/parameter-schema/description-mode/ParameterDescription.vue b/src/components/parameter-schema/description-mode/ParameterDescription.vue index 7e3afda494539b73fc82a17732f878a3dfc487ee..da45df33f6ab2f700be8f7f3711383e3ef5a2db2 100644 --- a/src/components/parameter-schema/description-mode/ParameterDescription.vue +++ b/src/components/parameter-schema/description-mode/ParameterDescription.vue @@ -20,6 +20,10 @@ const props = defineProps({ type: Boolean, default: false, }, + clowmResource: { + type: Boolean, + default: false, + }, }); const randomIDSuffix = Math.random().toString(16).substring(2, 8); @@ -50,68 +54,79 @@ const showRightColum = computed<boolean>( </script> <template> - <div - class="row border-top border-bottom border-secondary align-items-start py-2" - v-if="showHidden || !hidden" - > - <div class="fs-6 col-3"> - <font-awesome-icon :icon="icon" v-if="icon" class="me-2" /> - <code class="border rounded p-1" :id="props.parameterName" - >--{{ props.parameterName }}</code - > - <br /> - <span>type: '{{ parameterType }}'</span> - </div> - <div - :class="{ 'col-7': showRightColum, 'col-9': !showRightColum }" - class="flex-fill" - > - <markdown-renderer :markdown="description" /> - </div> - <div - class="col-auto d-flex flex-column align-items-end flex-fill" - v-if="showRightColum" - > - <button - class="btn btn-info btn-sm my-1" - type="button" - data-bs-toggle="collapse" - :data-bs-target="'#helpCollapse' + randomIDSuffix" - aria-expanded="false" - :aria-controls="'helpCollapse' + randomIDSuffix" - v-if="helpText" - > - <font-awesome-icon icon="fa-solid fa-circle-info" /> - Help - </button> - <div v-if="enumValues" class="dropdown w-fit my-1"> - <a - class="rounded-1 p-1 dropdown-toggle text-reset text-decoration-none border" - href="#" - role="button" - data-bs-toggle="dropdown" - aria-expanded="false" - > - Options: - <span v-if="defaultValue" - ><code>{{ defaultValue }}</code> (default)</span + <div class="border-top border-dark" v-if="showHidden || !hidden"> + <div class="d-flex pt-2 justify-content-between"> + <div class="flex-fill ps-2"> + <div class="row"> + <div class="fs-6"> + <font-awesome-icon :icon="icon" v-if="icon" class="me-2" /> + <code class="border rounded p-1" :id="props.parameterName" + >--{{ props.parameterName }}</code + > + </div> + </div> + <div class="row align-items-start mt-2"> + <div class="col-auto"> + <span>type: '{{ parameterType }}'</span> + <br /> + <span v-if="clowmResource" + ><font-awesome-icon + icon="fa-solid fa-database" + class="me-2" + />CloWM Resource</span + > + </div> + <div class="col-9"> + <markdown-renderer :markdown="description" /> + </div> + </div> + <div class="row" v-if="enumValues || defaultValue"> + <div v-if="enumValues" class="dropdown w-fit mb-2"> + <button + class="rounded-1 dropdown-toggle text-reset text-decoration-none border" + type="button" + data-bs-toggle="dropdown" + aria-expanded="false" + > + Options: + <span v-if="defaultValue" + ><code>{{ defaultValue }}</code> (default)</span + > + </button> + <ul class="dropdown-menu shadow" v-if="enumValues"> + <li v-for="val in enumValues" :key="val" class="px-2"> + {{ val }} <span v-if="val === defaultValue">(default)</span> + </li> + </ul> + </div> + <span v-else-if="defaultValue" class="rounded-1" + >default: <code>{{ defaultValue }}</code></span > - </a> - <ul class="dropdown-menu shadow" v-if="enumValues"> - <li v-for="val in enumValues" :key="val" class="px-2"> - {{ val }} <span v-if="val === defaultValue">(default)</span> - </li> - </ul> + </div> </div> - <span v-else-if="defaultValue" class="rounded-1 py-0 px-1 my-1" - >default: <code>{{ defaultValue }}</code></span + <div + class="col-auto d-flex flex-column align-items-end flex-fill" + v-if="showRightColum" > + <button + class="btn btn-info btn-sm my-1" + type="button" + data-bs-toggle="collapse" + :data-bs-target="'#helpCollapse' + randomIDSuffix" + aria-expanded="false" + :aria-controls="'helpCollapse' + randomIDSuffix" + v-if="helpText" + > + <font-awesome-icon icon="fa-solid fa-circle-info" /> + Help + </button> - <span - v-if="props.required" - class="bg-warning rounded-1 px-1 py-0 text-white" - >required</span - > + <span + v-if="props.required" + class="bg-warning rounded-1 px-1 py-0 text-white" + >required</span + > + </div> </div> <div class="collapse" :id="'helpCollapse' + randomIDSuffix" v-if="helpText"> <div class="p-2 pb-0 border rounded m-2 flex-shrink-1"> @@ -132,9 +147,11 @@ code { li:hover { background: var(--bs-secondary); } + a:hover { filter: brightness(1.2); } + code.border { backdrop-filter: brightness(0.95); } diff --git a/src/components/parameter-schema/description-mode/ParameterGroupDescription.vue b/src/components/parameter-schema/description-mode/ParameterGroupDescription.vue index c229f8f51ab8d615af6ee06670886ed8074fcd45..125e29245ee98bf56a08c9b37239f254b1fe5190 100644 --- a/src/components/parameter-schema/description-mode/ParameterGroupDescription.vue +++ b/src/components/parameter-schema/description-mode/ParameterGroupDescription.vue @@ -1,5 +1,5 @@ <script setup lang="ts"> -import { computed } from "vue"; +import { computed, type PropType } from "vue"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; import ParameterDescription from "@/components/parameter-schema/description-mode/ParameterDescription.vue"; @@ -19,6 +19,10 @@ const props = defineProps({ type: Boolean, default: false, }, + resourceParameters: { + type: Array as PropType<string[]>, + required: false, + }, }); const title = computed<string>(() => props.parameterGroup["title"]); @@ -57,6 +61,7 @@ const parameters = computed<Record<string, never>>( props.parameterGroup['required']?.includes(parameterName) ?? false " :show-hidden="showHidden" + :clowm-resource="resourceParameters?.includes(parameterName)" /> </template> </div> diff --git a/src/components/parameter-schema/form-mode/ParameterBooleanInput.vue b/src/components/parameter-schema/form-mode/ParameterBooleanInput.vue index 71f4c7f5ec6d23e63fe36d65e440d3db5b0021ca..dbcbe47aa698410732366f1d3b5175054ddf3d8f 100644 --- a/src/components/parameter-schema/form-mode/ParameterBooleanInput.vue +++ b/src/components/parameter-schema/form-mode/ParameterBooleanInput.vue @@ -25,6 +25,7 @@ const props = defineProps({ const randomIDSuffix = Math.random().toString(16).substring(2, 8); const helpTextPresent = computed<boolean>(() => props.parameter["help_text"]); +const iconPresent = computed<boolean>(() => props.parameter["fa_icon"]); const defaultValue = computed<boolean>( () => props.parameter["default"] ?? false, ); @@ -36,8 +37,8 @@ const emit = defineEmits<{ <template> <div - class="flex-fill mb-0 text-bg-light fs-6 ps-4 d-flex align-items-center justify-content-start rounded py-1 border border-secondary" - :class="{ 'rounded-end': !helpTextPresent }" + class="flex-fill mb-0 text-bg-light fs-6 ps-4 d-flex align-items-center justify-content-start py-1 border border-secondary" + :class="{ 'rounded-end': !helpTextPresent, 'rounded-start': !iconPresent }" > <div class="form-check form-check-inline"> <label class="form-check-label" :for="'trueOption' + randomIDSuffix" diff --git a/src/components/parameter-schema/form-mode/ParameterGroupForm.vue b/src/components/parameter-schema/form-mode/ParameterGroupForm.vue index 68927d7ece763013bbd196ff20559278a9f9e428..95d09663bf78ba27591046b2398511b9a8a73c80 100644 --- a/src/components/parameter-schema/form-mode/ParameterGroupForm.vue +++ b/src/components/parameter-schema/form-mode/ParameterGroupForm.vue @@ -1,6 +1,6 @@ <script setup lang="ts"> import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; -import { computed, watch } from "vue"; +import { computed, type PropType, watch } from "vue"; import ParameterNumberInput from "@/components/parameter-schema/form-mode/ParameterNumberInput.vue"; import MarkdownRenderer from "@/components/MarkdownRenderer.vue"; import ParameterBooleanInput from "@/components/parameter-schema/form-mode/ParameterBooleanInput.vue"; @@ -31,6 +31,10 @@ const props = defineProps({ type: Boolean, default: false, }, + resourceParameters: { + type: Array as PropType<string[]>, + required: false, + }, }); const title = computed<string>(() => props.parameterGroup["title"]); const icon = computed<string>(() => props.parameterGroup["fa_icon"]); @@ -85,10 +89,10 @@ watch( class="card mb-3" :hidden="(!showHidden && groupHidden) || (!showOptional && !groupRequired)" > - <h2 class="card-header" :id="props.parameterGroupName"> + <h3 class="card-header" :id="props.parameterGroupName"> <font-awesome-icon :icon="icon" class="me-2" v-if="icon" /> {{ title }} - </h2> + </h3> <div class="card-body"> <h5 class="card-title" v-if="description">{{ description }}</h5> <template @@ -112,6 +116,7 @@ watch( parameterGroup, parameterName, ), + 'border-2': parameterRequired(parameterGroup, parameterName), }" >--{{ parameter["name"] ?? parameterName }}</code > @@ -167,6 +172,7 @@ watch( (newValue) => (formInput[parameterName] = newValue) " :remove-advanced="!showOptional" + :clowm-resource="resourceParameters?.includes(parameterName)" /> </template> <span diff --git a/src/components/parameter-schema/form-mode/ParameterStringInput.vue b/src/components/parameter-schema/form-mode/ParameterStringInput.vue index 7e3164fe523835254620cbb4396dd0a305254715..af8713531998330ed876c919451e8b00885f107c 100644 --- a/src/components/parameter-schema/form-mode/ParameterStringInput.vue +++ b/src/components/parameter-schema/form-mode/ParameterStringInput.vue @@ -33,6 +33,10 @@ const props = defineProps({ type: Boolean, default: false, }, + clowmResource: { + type: Boolean, + default: false, + }, }); const emit = defineEmits<{ @@ -63,15 +67,13 @@ const formState = reactive<{ stringVal: undefined, }); -const defaultValue = computed<string>(() => props.parameter["default"]); const pattern = computed<string>(() => props.parameter["pattern"]); 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 clowmResource = computed<boolean>(() => props.clowmResource ?? false); +const helpTextPresent = computed<boolean>(() => props.parameter["help_text"]); const filesInBucket = computed<string[]>(() => (s3objectRepository.objectMapping[s3Path.bucket ?? ""] ?? []) @@ -113,12 +115,6 @@ const keyDataList = computed<string[]>(() => { } }); -watch(defaultValue, (newVal, oldVal) => { - if (newVal != oldVal && newVal != undefined) { - emit("update:modelValue", newVal); - } -}); - watch(s3Path, () => { if (dataPath.value && !formState.advancedInput) { updateStringFromS3(); @@ -144,7 +140,7 @@ watch(selectedResource, () => { watch( () => formState.advancedInput, (newVal, oldValue) => { - if (newVal != oldValue) { + if (newVal != oldValue && !newVal) { if (clowmResource.value) { updateStringFromResource(); } else if (dataPath.value) { @@ -181,7 +177,10 @@ function updateKeysInBucket(bucketName?: string) { } onMounted(() => { - formState.stringVal = defaultValue.value; + formState.stringVal = props.modelValue; + if (formState.stringVal) { + formState.advancedInput = true; + } }); </script> @@ -203,6 +202,7 @@ onMounted(() => { </select> <select class="form-select border border-secondary" + :class="{ 'rounded-end': props.removeAdvanced && !helpTextPresent }" :required="props.required || selectedResource.resourceId != undefined" v-model="selectedResource.resourceVersionIndex" :disabled="selectedResource.resourceId.length === 0" @@ -238,6 +238,7 @@ onMounted(() => { <input class="form-control border-top border-secondary" :list="'datalistOptions2' + randomIDSuffix" + :class="{ 'rounded-end': props.removeAdvanced && !helpTextPresent }" placeholder="Type to search in bucket..." :required="props.required && dataFormat === 'file-path'" v-model="s3Path.key" @@ -251,6 +252,7 @@ onMounted(() => { <input ref="stringInput" class="form-control border border-secondary" + :class="{ 'rounded-end': props.removeAdvanced && !helpTextPresent }" type="text" v-model="formState.stringVal" :required="props.required" @@ -267,8 +269,8 @@ onMounted(() => { v-model="formState.advancedInput" /> <label - class="btn btn-outline-secondary rounded-end" - style="border-top-left-radius: 0; border-bottom-left-radius: 0" + class="btn btn-outline-secondary rounded-0" + :class="{ 'rounded-end': !helpTextPresent }" :for="'flexCheckDefault' + randomIDSuffix" >Advanced</label > diff --git a/src/components/resources/ResourceCard.vue b/src/components/resources/ResourceCard.vue index 0eef72471cd9b7da7001607db4554577ec59a724..d96ef21787ae986bf21f17d7e75abf259f25878d 100644 --- a/src/components/resources/ResourceCard.vue +++ b/src/components/resources/ResourceCard.vue @@ -26,20 +26,20 @@ const props = defineProps<{ let refreshTimeout: NodeJS.Timeout | undefined = undefined; const stateToUIMapping: Record<Status, string> = { - CLUSTER_DELETE_ERROR: "Error deleting on Cluster", - CLUSTER_DELETING: "Deleting on Cluster", - S3_DELETE_ERROR: "Error deleting in S3", - S3_DELETING: "Deleting in S3", - SETTING_LATEST: "Setting to Latest Version", - SYNC_ERROR: "Error Syncing to Cluster", - CLUSTER_DELETED: "Deleted on Cluster", - DENIED: "Rejected", - RESOURCE_REQUESTED: "Resource created", - S3_DELETED: "Deleted in S3", - SYNCHRONIZED: "Available", - SYNCHRONIZING: "Synchronizing to Cluster", - SYNC_REQUESTED: "Wait for Approval", - LATEST: "Available (Latest)", + CLUSTER_DELETE_ERROR: "Error deleting resource on cluster", + CLUSTER_DELETING: "Resource deletion on cluster in progress", + S3_DELETE_ERROR: "Error deleting tarball in S3", + S3_DELETING: "Tarball deletion in S3 in progress", + SETTING_LATEST: "Resource available", + SYNC_ERROR: "Error syncing to cluster", + CLUSTER_DELETED: "Resource deleted on cluster", + DENIED: "Resource creation rejected by reviewer", + RESOURCE_REQUESTED: "Resource creation requested", + S3_DELETED: "Tarball deleted in S3", + SYNCHRONIZED: "Resource available", + SYNCHRONIZING: "Synchronizing to cluster in progress", + SYNC_REQUESTED: "Wait for reviewer approval", + LATEST: "Resource available (latest)", }; const emit = defineEmits<{ @@ -250,7 +250,7 @@ onMounted(() => { resourceVersion.resource_version_id " data-bs-toggle="tooltip" - data-bs-title="Path on the cluster where a workflow can access the resource" + data-bs-title="Physical path to access the resource from your workflow" > <font-awesome-icon icon="fa-solid fa-circle-question" /> </div> @@ -292,7 +292,7 @@ onMounted(() => { 'tooltip-s3-path-' + resourceVersion.resource_version_id " data-bs-toggle="tooltip" - data-bs-title="S3 Path where the resource should be uploaded" + data-bs-title="S3 Path under which the resource should be uploaded" > <font-awesome-icon icon="fa-solid fa-circle-question" /> </div> diff --git a/src/components/resources/modals/UploadResourceInfoModal.vue b/src/components/resources/modals/UploadResourceInfoModal.vue index 8146fe034ef63837b5dc9988e37aac76d7dc983f..02f4d182eacce48ce8da6921414e1ea1d1b13964 100644 --- a/src/components/resources/modals/UploadResourceInfoModal.vue +++ b/src/components/resources/modals/UploadResourceInfoModal.vue @@ -143,7 +143,8 @@ onMounted(() => { <ol> <li :class="{ 'text-decoration-line-through': props.resourceVersion }"> <h6> - Prerequisite: Prepare a single compressed tar archive of your data + Prerequisite: Prepare a single compressed tar archive of your + resource data </h6> <p :hidden="props.resourceVersion != undefined"> The data of your resource must be compressed into a single tar @@ -287,7 +288,7 @@ s3 = boto3.resource( </template> </li> <li> - <h6>Request a synchronization</h6> + <h6>Request synchronization</h6> <p> Click <b>Request Synchronization</b> to request the synchronization to CloWM's compute cluster. diff --git a/src/components/workflows/WorkflowDocumentationTabs.vue b/src/components/workflows/WorkflowDocumentationTabs.vue index 2020831edf6eb340c5ca6226d325b22db2e21bb4..7ce8afc6a74f0a0cb7a494926636c43ca21257dd 100644 --- a/src/components/workflows/WorkflowDocumentationTabs.vue +++ b/src/components/workflows/WorkflowDocumentationTabs.vue @@ -3,6 +3,7 @@ import { computed } from "vue"; import MarkdownRenderer from "@/components/MarkdownRenderer.vue"; import ParameterSchemaDescriptionComponent from "@/components/parameter-schema/ParameterSchemaDescriptionComponent.vue"; import { useRoute } from "vue-router"; +import type { ClowmInfo } from "@/types/ClowmInfo"; const route = useRoute(); @@ -13,6 +14,7 @@ const props = defineProps<{ usageMarkdown?: string; outputMarkdown?: string; parameterSchema?: Record<string, never>; + clowmInfo?: ClowmInfo; }>(); const activeTab = computed<string>( @@ -112,6 +114,7 @@ const activeTab = computed<string>( <parameter-schema-description-component v-if="props.parameterSchema" :schema="props.parameterSchema" + :clowm-info="props.clowmInfo" /> <h4 v-else class="text-center mt-5">Parameters are not available</h4> </template> diff --git a/src/components/workflows/modals/ParameterModal.vue b/src/components/workflows/modals/ParameterModal.vue index 76f0d35f2ae516fd616ec0baf486931dc183fd79..fed99a85f691de4aac7032244b223bb1f9ea6001 100644 --- a/src/components/workflows/modals/ParameterModal.vue +++ b/src/components/workflows/modals/ParameterModal.vue @@ -273,9 +273,9 @@ onMounted(() => { :tabindex="parameterState.loading || parameterState.error ? -1 : 0" :aria-disabled="parameterState.loading || parameterState.error" > - <font-awesome-icon icon="fa-solid fa-download" class="me-1" /> - Parameters</a - > + Download Parameters + <font-awesome-icon icon="fa-solid fa-download" class="ms-1" + /></a> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal"> Close </button> diff --git a/src/stores/workflows.ts b/src/stores/workflows.ts index e93106a4187522f330b9694297c9ecfcee5466fe..b094280840e4f9f43ae78d36e712c47d2e4b8e6e 100644 --- a/src/stores/workflows.ts +++ b/src/stores/workflows.ts @@ -10,6 +10,7 @@ import type { WorkflowVersion, } from "@/client/workflow"; import { + DocumentationEnum, Status, WorkflowCredentialsService, WorkflowModeService, @@ -17,7 +18,7 @@ import { WorkflowVersionService, } from "@/client/workflow"; import { useAuthStore } from "@/stores/users"; -import { set, get } from "idb-keyval"; +import { get, set } from "idb-keyval"; import { useNameStore } from "@/stores/names"; export const useWorkflowStore = defineStore({ @@ -27,10 +28,13 @@ export const useWorkflowStore = defineStore({ workflowMapping: {}, comprehensiveWorkflowMapping: {}, modeMapping: {}, + documentationFiles: {}, }) as { workflowMapping: Record<string, WorkflowOut>; comprehensiveWorkflowMapping: Record<string, WorkflowOut>; modeMapping: Record<string, WorkflowModeOut>; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + documentationFiles: Record<string, Record<DocumentationEnum, any>>; }, getters: { workflows(): WorkflowOut[] { @@ -126,6 +130,50 @@ export const useWorkflowStore = defineStore({ }) .finally(onFinally); }, + fetchWorkflowDocumentation( + workflow_id: string, + workflow_version_id: string, + document = DocumentationEnum.USAGE, + mode_id?: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ): Promise<any> { + if (this.documentationFiles[workflow_version_id] == undefined) { + this.documentationFiles[workflow_version_id] = { + changelog: undefined, + clowm_info: undefined, + input: undefined, + output: undefined, + parameter_schema: undefined, + usage: undefined, + }; + } + if ( + mode_id == undefined && + this.documentationFiles[workflow_version_id]?.[document] != undefined + ) { + return Promise.resolve( + this.documentationFiles[workflow_version_id][document], + ); + } + return WorkflowVersionService.workflowVersionDownloadWorkflowDocumentation( + workflow_id, + workflow_version_id, + document, + mode_id, + ) + .then((response) => { + this.documentationFiles[workflow_version_id][document] = response; + return response; + }) + .catch((err) => { + if (document === DocumentationEnum.CLOWM_INFO) { + this.documentationFiles[workflow_version_id][ + DocumentationEnum.CLOWM_INFO + ] = {}; + } + return err; + }); + }, fetchReviewableWorkflows(onFinally?: () => void): Promise<WorkflowOut[]> { if (this.reviewableWorkflows.length > 0) { onFinally?.(); diff --git a/src/types/ClowmInfo.ts b/src/types/ClowmInfo.ts new file mode 100644 index 0000000000000000000000000000000000000000..ebd4644e6ebc5ada66e42fbd60917aa316b803fd --- /dev/null +++ b/src/types/ClowmInfo.ts @@ -0,0 +1,6 @@ +export type ClowmInfo = { + inputParameters: string[]; + outputParameters: string[]; + resourceParameters?: string[]; + exampleParameters?: Record<string, string | boolean | number>; +}; diff --git a/src/views/workflows/StartWorkflowView.vue b/src/views/workflows/StartWorkflowView.vue index 38f4f95e23edace76af8460554f130cf6947930d..0d419d447cd72d7afd609a1903d9c8683580ee69 100644 --- a/src/views/workflows/StartWorkflowView.vue +++ b/src/views/workflows/StartWorkflowView.vue @@ -1,18 +1,16 @@ <script setup lang="ts"> import ParameterSchemaFormComponent from "@/components/parameter-schema/ParameterSchemaFormComponent.vue"; import type { WorkflowVersion } from "@/client/workflow"; -import { - ApiError, - DocumentationEnum, - WorkflowVersionService, -} from "@/client/workflow"; -import { onMounted, ref, reactive, watch } from "vue"; +import { ApiError, DocumentationEnum } from "@/client/workflow"; +import { onMounted, reactive, watch } from "vue"; import { useRouter } from "vue-router"; import { Toast } from "bootstrap"; import { useWorkflowExecutionStore } from "@/stores/workflowExecutions"; import BootstrapToast from "@/components/BootstrapToast.vue"; +import { useWorkflowStore } from "@/stores/workflows"; const executionRepository = useWorkflowExecutionStore(); +const workflowRepository = useWorkflowStore(); const props = defineProps<{ versionId: string; workflowId: string; @@ -20,7 +18,6 @@ const props = defineProps<{ viewMode: string; }>(); -const parameterSchema = ref(undefined); const router = useRouter(); let errorToast: Toast | null = null; @@ -48,18 +45,25 @@ watch( function downloadParameterSchema() { versionState.loading = true; - WorkflowVersionService.workflowVersionDownloadWorkflowDocumentation( - props.workflowId, - props.versionId, - DocumentationEnum.PARAMETER_SCHEMA, - props.workflowModeId, - ) - .then((schema) => { - parameterSchema.value = schema; - }) - .finally(() => { - versionState.loading = false; - }); + Promise.all([ + workflowRepository + .fetchWorkflowDocumentation( + props.workflowId, + props.versionId, + DocumentationEnum.PARAMETER_SCHEMA, + props.workflowModeId, + ) + .then((schema) => { + versionState.parameterSchema = schema; + }), + workflowRepository.fetchWorkflowDocumentation( + props.workflowId, + props.versionId, + DocumentationEnum.CLOWM_INFO, + ), + ]).finally(() => { + versionState.loading = false; + }); } function startWorkflow( @@ -127,11 +131,16 @@ onMounted(() => { </bootstrap-toast> <parameter-schema-form-component :workflow-version-id="versionId" - :schema="parameterSchema" + :schema="versionState.parameterSchema" :loading="versionState.loading" allow-notes @start-workflow="startWorkflow" :view-mode="viewMode" + :clowm-info=" + workflowRepository.documentationFiles[versionId]?.[ + DocumentationEnum.CLOWM_INFO + ] + " /> </template> diff --git a/src/views/workflows/WorkflowVersionView.vue b/src/views/workflows/WorkflowVersionView.vue index 9aadf7b7cee1ee96c9f47510578b5f5d9fad5aaa..072ba90b87ddba6e8df00fe3e43309baa9c70fd4 100644 --- a/src/views/workflows/WorkflowVersionView.vue +++ b/src/views/workflows/WorkflowVersionView.vue @@ -1,7 +1,8 @@ <script setup lang="ts"> import { onMounted, reactive, watch } from "vue"; -import { DocumentationEnum, WorkflowVersionService } from "@/client/workflow"; +import { DocumentationEnum } from "@/client/workflow"; import WorkflowDocumentationTabs from "@/components/workflows/WorkflowDocumentationTabs.vue"; +import { useWorkflowStore } from "@/stores/workflows"; const props = defineProps<{ versionId: string; @@ -9,23 +10,15 @@ const props = defineProps<{ workflowModeId?: string; }>(); +const workflowRepository = useWorkflowStore(); + const versionState = reactive<{ loading: boolean; fileLoading: boolean; - descriptionMarkdown: string; - changelogMarkdown: string; - usageMarkdown: string; - outputMarkdown: string; - errorLoading: boolean; parameterSchema: Record<string, never>; }>({ loading: true, fileLoading: true, - errorLoading: false, - descriptionMarkdown: "", - changelogMarkdown: "", - usageMarkdown: "", - outputMarkdown: "", parameterSchema: {}, }); @@ -43,87 +36,59 @@ watch( () => props.workflowModeId, (newModeId, oldModeId) => { if (newModeId !== oldModeId) { - WorkflowVersionService.workflowVersionDownloadWorkflowDocumentation( + workflowRepository.fetchWorkflowDocumentation( props.workflowId, props.versionId, DocumentationEnum.PARAMETER_SCHEMA, newModeId, - ).then((schema) => { - versionState.parameterSchema = schema; - }); + ); } }, ); function downloadVersionFiles() { versionState.fileLoading = true; - const descriptionPromise = - WorkflowVersionService.workflowVersionDownloadWorkflowDocumentation( - props.workflowId, - props.versionId, - DocumentationEnum.USAGE, - ) - .then((markdown) => { - versionState.descriptionMarkdown = markdown; - }) - .catch(() => { - versionState.descriptionMarkdown = ""; - }); - const changelogPromise = - WorkflowVersionService.workflowVersionDownloadWorkflowDocumentation( - props.workflowId, - props.versionId, - DocumentationEnum.CHANGELOG, - ) - .then((markdown) => { - versionState.changelogMarkdown = markdown; - }) - .catch(() => { - versionState.changelogMarkdown = ""; - }); - const parameterPromise = - WorkflowVersionService.workflowVersionDownloadWorkflowDocumentation( + const descriptionPromise = workflowRepository.fetchWorkflowDocumentation( + props.workflowId, + props.versionId, + DocumentationEnum.USAGE, + ); + const changelogPromise = workflowRepository.fetchWorkflowDocumentation( + props.workflowId, + props.versionId, + DocumentationEnum.CHANGELOG, + ); + const parameterPromise = workflowRepository + .fetchWorkflowDocumentation( props.workflowId, props.versionId, DocumentationEnum.PARAMETER_SCHEMA, props.workflowModeId, ) - .then((schema) => { - versionState.parameterSchema = schema; - }) - .catch(() => { - versionState.parameterSchema = {}; - }); - const usagePromise = - WorkflowVersionService.workflowVersionDownloadWorkflowDocumentation( - props.workflowId, - props.versionId, - DocumentationEnum.INPUT, - ) - .then((markdown) => { - versionState.usageMarkdown = markdown; - }) - .catch(() => { - versionState.usageMarkdown = ""; - }); - const outputPromise = - WorkflowVersionService.workflowVersionDownloadWorkflowDocumentation( - props.workflowId, - props.versionId, - DocumentationEnum.OUTPUT, - ) - .then((markdown) => { - versionState.outputMarkdown = markdown; - }) - .catch(() => { - versionState.outputMarkdown = ""; - }); + .then((schema) => { + versionState.parameterSchema = schema; + }); + const usagePromise = workflowRepository.fetchWorkflowDocumentation( + props.workflowId, + props.versionId, + DocumentationEnum.INPUT, + ); + const outputPromise = workflowRepository.fetchWorkflowDocumentation( + props.workflowId, + props.versionId, + DocumentationEnum.OUTPUT, + ); Promise.all([ descriptionPromise, changelogPromise, parameterPromise, usagePromise, outputPromise, + workflowRepository.fetchWorkflowDocumentation( + props.workflowId, + props.versionId, + DocumentationEnum.CLOWM_INFO, + ), ]).finally(() => { versionState.fileLoading = false; }); @@ -137,11 +102,32 @@ onMounted(() => { <template> <workflow-documentation-tabs :parameter-schema="versionState.parameterSchema" + :clowm-info=" + workflowRepository.documentationFiles[props.versionId]?.[ + DocumentationEnum.CLOWM_INFO + ] + " :loading="versionState.fileLoading" - :changelog-markdown="versionState.changelogMarkdown" - :description-markdown="versionState.descriptionMarkdown" - :usage-markdown="versionState.usageMarkdown" - :output-markdown="versionState.outputMarkdown" + :changelog-markdown=" + workflowRepository.documentationFiles[props.versionId]?.[ + DocumentationEnum.CHANGELOG + ] + " + :description-markdown=" + workflowRepository.documentationFiles[props.versionId]?.[ + DocumentationEnum.USAGE + ] + " + :usage-markdown=" + workflowRepository.documentationFiles[props.versionId]?.[ + DocumentationEnum.INPUT + ] + " + :output-markdown=" + workflowRepository.documentationFiles[props.versionId]?.[ + DocumentationEnum.OUTPUT + ] + " /> </template> diff --git a/src/views/workflows/WorkflowView.vue b/src/views/workflows/WorkflowView.vue index e3984b484b877d7546febca7992d9fec824b5316..b6d573f6805d7f76753aabd6f58488d61a133a82 100644 --- a/src/views/workflows/WorkflowView.vue +++ b/src/views/workflows/WorkflowView.vue @@ -249,10 +249,10 @@ onMounted(() => { </div> <div v-else-if="workflow"> <div class="d-flex justify-content-between align-items-center"> - <h2 class="w-fit"> + <h3 class="w-fit"> {{ workflow.name }} <span v-if="activeVersionString">@{{ activeVersionString }}</span> - </h2> + </h3> <img v-if="activeVersionIcon != null" :src="activeVersionIcon" @@ -260,7 +260,7 @@ onMounted(() => { alt="Workflow icon" /> </div> - <p class="fs-4 mt-3">{{ workflow.short_description }}</p> + <p class="fs-5 mt-3">{{ workflow?.short_description }}</p> <div v-if="activeVersionModeIds.length > 0" class="row align-items-center mb-3 fs-5"