diff --git a/package-lock.json b/package-lock.json index 0f79c0580c27ea372612dcdb41b5b0f359c5bac6..5c9ff57804580929f57cc83d8533010d8cb765df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2732,27 +2732,27 @@ } }, "node_modules/@volar/language-core": { - "version": "2.4.0-alpha.12", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.0-alpha.12.tgz", - "integrity": "sha512-Dj9qTifcGGgzFLfMbU5dCo13kHyNuEyvPJhtWDnoVBBmgwW3GMwFmgWnNxBhjf63m5x0gux1okaxX2CLN7qSww==", + "version": "2.4.0-alpha.15", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.0-alpha.15.tgz", + "integrity": "sha512-mt8z4Fm2WxfQYoQHPcKVjLQV6PgPqyKLbkCVY2cr5RSaamqCHjhKEpsFX66aL4D/7oYguuaUw9Bx03Vt0TpIIA==", "dev": true, "dependencies": { - "@volar/source-map": "2.4.0-alpha.12" + "@volar/source-map": "2.4.0-alpha.15" } }, "node_modules/@volar/source-map": { - "version": "2.4.0-alpha.12", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.0-alpha.12.tgz", - "integrity": "sha512-LXATFSj4D7T9sEm7FFj6iBgHjKjrdhAgRPcechVKiNCMQdr3r3GVkkeu8aM+1peaMH3LsCqoDxVZEmh2r7CHiw==", + "version": "2.4.0-alpha.15", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.0-alpha.15.tgz", + "integrity": "sha512-8Htngw5TmBY4L3ClDqBGyfLhsB8EmoEXUH1xydyEtEoK0O6NX5ur4Jw8jgvscTlwzizyl/wsN1vn0cQXVbbXYg==", "dev": true }, "node_modules/@volar/typescript": { - "version": "2.4.0-alpha.12", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.0-alpha.12.tgz", - "integrity": "sha512-mLg+OQauMTv/+08a7WBWJo1sev/wc8t2is0zhBZIlFU+j5mG89FM4+4089c2p/zoUFZ400Q/VNg2BPfhpZ8wSA==", + "version": "2.4.0-alpha.15", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.0-alpha.15.tgz", + "integrity": "sha512-U3StRBbDuxV6Woa4hvGS4kz3XcOzrWUKgFdEFN+ba1x3eaYg7+ytau8ul05xgA+UNGLXXsKur7fTUhDFyISk0w==", "dev": true, "dependencies": { - "@volar/language-core": "2.4.0-alpha.12", + "@volar/language-core": "2.4.0-alpha.15", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } @@ -2865,12 +2865,12 @@ } }, "node_modules/@vue/language-core": { - "version": "2.0.24", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.24.tgz", - "integrity": "sha512-997YD6Lq/66LXr3ZOLNxDCmyn13z9NP8LU1UZn9hGCDWhzlbXAIP0hOgL3w3x4RKEaWTaaRtsHP9DzHvmduruQ==", + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.26.tgz", + "integrity": "sha512-/lt6SfQ3O1yDAhPsnLv9iSUgXd1dMHqUm/t3RctfqjuwQf1LnftZ414X3UBn6aXT4MiwXWtbNJ4Z0NZWwDWgJQ==", "dev": true, "dependencies": { - "@volar/language-core": "~2.4.0-alpha.2", + "@volar/language-core": "~2.4.0-alpha.15", "@vue/compiler-dom": "^3.4.0", "@vue/shared": "^3.4.0", "computeds": "^0.0.1", @@ -7210,13 +7210,13 @@ } }, "node_modules/vue-tsc": { - "version": "2.0.24", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.24.tgz", - "integrity": "sha512-1qi4P8L7yS78A7OJ7CDDxUIZPD6nVxoQEgX3DkRZNi1HI1qOfzOJwQlNpmwkogSVD6S/XcanbW9sktzpSxz6rA==", + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.26.tgz", + "integrity": "sha512-tOhuwy2bIXbMhz82ef37qeiaQHMXKQkD6mOF6CCPl3/uYtST3l6fdNyfMxipudrQTxTfXVPlgJdMENBFfC1CfQ==", "dev": true, "dependencies": { - "@volar/typescript": "~2.4.0-alpha.2", - "@vue/language-core": "2.0.24", + "@volar/typescript": "~2.4.0-alpha.15", + "@vue/language-core": "2.0.26", "semver": "^7.5.4" }, "bin": { diff --git a/src/components/workflows/modals/ArbitraryWorkflowModal.vue b/src/components/workflows/modals/ArbitraryWorkflowModal.vue index 3ac3acf3fe8979e7330bfab48afae7f0314c7e28..5cf624651cded11fb212e034cf24ba1ef468c0be 100644 --- a/src/components/workflows/modals/ArbitraryWorkflowModal.vue +++ b/src/components/workflows/modals/ArbitraryWorkflowModal.vue @@ -5,8 +5,12 @@ import { computed, onMounted, reactive, ref, watch } from "vue"; import { useRouter } from "vue-router"; import { GitRepository, determineGitIcon } from "@/utils/GitRepository"; import { Collapse, Modal } from "bootstrap"; -import { useWorkflowStore } from "@/stores/workflows"; -import { NextflowVersion, type WorkflowModeOut } from "@/client"; +import { type DocLocations, useWorkflowStore } from "@/stores/workflows"; +import { + DocumentationEnum, + NextflowVersion, + type WorkflowModeOut, +} from "@/client"; import { environment } from "@/environment"; import { useS3KeyStore } from "@/stores/s3keys"; import { useResourceStore } from "@/stores/resources"; @@ -63,6 +67,14 @@ const workflowMode = reactive<{ }, modeEnabled: false, }); +const docsLocations = reactive<DocLocations>({ + "changelog.md": "", + "clowm_info.json": "", + "input.md": "", + "output.md": "", + "parameter_schema.json": "", + "usage.md": "", +}); const activeTab = ref<Tabs>("clowm"); @@ -115,16 +127,19 @@ function modalClosed() { function viewWorkflow() { createWorkflowModal?.hide(); workflowStore - .setArbitraryWorkflow({ - ...workflow, - name: "", - short_description: "", - modes: workflowMode.modeEnabled ? [{ ...workflowMode.mode }] : [], - token: - repositoryCredentials.token.length > 0 - ? repositoryCredentials.token - : undefined, - }) + .setArbitraryWorkflow( + { + ...workflow, + name: "", + short_description: "", + modes: workflowMode.modeEnabled ? [{ ...workflowMode.mode }] : [], + token: + repositoryCredentials.token.length > 0 + ? repositoryCredentials.token + : undefined, + }, + { ...docsLocations }, + ) .then((wid) => { router.push({ name: "arbitrary-workflow", @@ -335,10 +350,10 @@ function checkRepository() { .trim() .replace(/(^\/+|\/+$)/g, ""); formState.ratelimit_reset = 0; + workflowRepositoryElement.value?.setCustomValidity(""); if (arbitraryWorkflowForm.value?.checkValidity() && !formState.allowUpload) { formState.unsupportedRepository = false; formState.missingFiles = []; - workflowRepositoryElement.value?.setCustomValidity(""); try { const repo = GitRepository.buildRepository( workflow.repository_url, @@ -349,7 +364,18 @@ function checkRepository() { ); repo .validateRepo(workflowMode.modeEnabled ? [workflowMode.mode] : []) - .then((missingFiles) => { + .then((checkRepoResult) => { + const missingFiles = []; + if (checkRepoResult.mainScriptMissing != undefined) { + missingFiles.push(checkRepoResult.mainScriptMissing); + } + for (const doc of Object.values(DocumentationEnum)) { + docsLocations[doc] = checkRepoResult.docs[doc].found ?? ""; + if (doc === DocumentationEnum.CLOWM_INFO_JSON) { + continue; + } + missingFiles.push(...(checkRepoResult.docs[doc]?.missing ?? [])); + } if (missingFiles.length === 0) { formState.allowUpload = true; } else { @@ -357,16 +383,19 @@ function checkRepository() { } }) .catch((e) => { - if ( - e instanceof AxiosError && - e.response != undefined && - parseInt(e.response.headers["x-ratelimit-remaining"] ?? "1") == 0 - ) { - formState.ratelimit_reset = parseInt( - e.response.headers["x-ratelimit-reset"] ?? "0", - ); + if (e instanceof AxiosError && e.response != undefined) { + if ( + parseInt(e.response.headers["x-ratelimit-remaining"] ?? "1") == 0 + ) { + formState.ratelimit_reset = parseInt( + e.response.headers["x-ratelimit-reset"] ?? "0", + ); + } else if (e.response.status === 404) { + workflowRepositoryElement.value?.setCustomValidity( + "Can't find combination of repository and Git commit hash", + ); + } } - console.error(e); }); // repo // .checkFilesExist( diff --git a/src/components/workflows/modals/CreateWorkflowModal.vue b/src/components/workflows/modals/CreateWorkflowModal.vue index 08065b446aa32d341b25d5e6df4df5a3250212af..a31a402100066273d4a8c89edc1acb8ddd55196e 100644 --- a/src/components/workflows/modals/CreateWorkflowModal.vue +++ b/src/components/workflows/modals/CreateWorkflowModal.vue @@ -6,6 +6,7 @@ import { type WorkflowOut, type WorkflowModeOut, NextflowVersion, + DocumentationEnum, } from "@/client"; import BootstrapModal from "@/components/modals/BootstrapModal.vue"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; @@ -251,7 +252,17 @@ function checkRepository() { ); repo .validateRepo(workflowModes.hasModes ? workflowModes.modes : []) - .then((missingFiles) => { + .then((checkRepoResult) => { + const missingFiles = []; + if (checkRepoResult.mainScriptMissing != undefined) { + missingFiles.push(checkRepoResult.mainScriptMissing); + } + for (const doc of Object.values(DocumentationEnum)) { + if (doc === DocumentationEnum.CLOWM_INFO_JSON) { + continue; + } + missingFiles.push(...(checkRepoResult.docs[doc]?.missing ?? [])); + } if (missingFiles.length === 0) { formState.allowUpload = true; } else { @@ -262,16 +273,19 @@ function checkRepository() { } }) .catch((e) => { - if ( - e instanceof AxiosError && - e.response != undefined && - parseInt(e.response.headers["x-ratelimit-remaining"] ?? "1") == 0 - ) { - formState.ratelimit_reset = parseInt( - e.response.headers["x-ratelimit-reset"] ?? "0", - ); + if (e instanceof AxiosError && e.response != undefined) { + if ( + parseInt(e.response.headers["x-ratelimit-remaining"] ?? "1") == 0 + ) { + formState.ratelimit_reset = parseInt( + e.response.headers["x-ratelimit-reset"] ?? "0", + ); + } else if (e.response.status === 404) { + workflowRepositoryElement.value?.setCustomValidity( + "Can't find combination of repository and Git commit hash", + ); + } } - console.error(e); }); } catch (e) { formState.unsupportedRepository = true; diff --git a/src/components/workflows/modals/UpdateWorkflowModal.vue b/src/components/workflows/modals/UpdateWorkflowModal.vue index 7d064315f284a3cac5098e115f04c0893a8a7c44..402ce37501f6b57d4f3782b337ce5a702c6e0955 100644 --- a/src/components/workflows/modals/UpdateWorkflowModal.vue +++ b/src/components/workflows/modals/UpdateWorkflowModal.vue @@ -9,6 +9,7 @@ import { type WorkflowVersion, type ApiError, NextflowVersion, + DocumentationEnum, } from "@/client"; import BootstrapModal from "@/components/modals/BootstrapModal.vue"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; @@ -190,27 +191,40 @@ function checkRepository() { const newModes = formState.modesEnabled ? workflowModes.addModes : []; repo .validateRepo([...oldModes, ...newModes]) - .then((missingFiles) => { + .then((checkRepoResult) => { + const missingFiles = []; + if (checkRepoResult.mainScriptMissing != undefined) { + missingFiles.push(checkRepoResult.mainScriptMissing); + } + for (const doc of Object.values(DocumentationEnum)) { + if (doc === DocumentationEnum.CLOWM_INFO_JSON) { + continue; + } + missingFiles.push(...(checkRepoResult.docs[doc]?.missing ?? [])); + } if (missingFiles.length === 0) { formState.allowUpload = true; } else { - formState.missingFiles = missingFiles; workflowGitCommitHashElement.value?.setCustomValidity( "Files are missing in the repository", ); + formState.missingFiles = missingFiles; } }) .catch((e) => { - if ( - e instanceof AxiosError && - e.response != undefined && - parseInt(e.response.headers["x-ratelimit-remaining"] ?? "1") == 0 - ) { - formState.ratelimitReset = parseInt( - e.response.headers["x-ratelimit-reset"] ?? "0", - ); + if (e instanceof AxiosError && e.response != undefined) { + if ( + parseInt(e.response.headers["x-ratelimit-remaining"] ?? "1") == 0 + ) { + formState.ratelimitReset = parseInt( + e.response.headers["x-ratelimit-reset"] ?? "0", + ); + } else if (e.response.status === 404) { + workflowGitCommitHashElement.value?.setCustomValidity( + "Can't find combination of repository and Git commit hash", + ); + } } - console.error(e); }); } } diff --git a/src/stores/workflows.ts b/src/stores/workflows.ts index c58cdf2d1b3781798d1e4fe51a02a9f3a49e1402..9b71a1df881c5561548e72d01db537e3b9c6fe6c 100644 --- a/src/stores/workflows.ts +++ b/src/stores/workflows.ts @@ -22,6 +22,13 @@ import { useUserStore } from "@/stores/users"; import { get, set } from "idb-keyval"; import { useNameStore } from "@/stores/names"; +export type DocLocations = Record<DocumentationEnum, string>; + +interface WorkflowWithDocLocations { + workflow: WorkflowIn; + docs: DocLocations; +} + export const useWorkflowStore = defineStore({ id: "workflows", state: () => @@ -73,7 +80,9 @@ export const useWorkflowStore = defineStore({ } return mapping; }, - getArbitraryWorkflow(): (wid: string) => Promise<WorkflowIn | undefined> { + getArbitraryWorkflow(): ( + wid: string, + ) => Promise<WorkflowWithDocLocations | undefined> { return (wid: string) => get(wid); }, }, @@ -300,9 +309,15 @@ export const useWorkflowStore = defineStore({ return modes; }); }, - async setArbitraryWorkflow(workflow: WorkflowIn) { + async setArbitraryWorkflow( + workflow: WorkflowIn, + docLocations: DocLocations, + ) { const wid = crypto.randomUUID(); - await set(wid, workflow); + await set(wid, { + workflow: workflow, + docs: docLocations, + } as WorkflowWithDocLocations); return wid; }, deprecateWorkflowVersion( diff --git a/src/utils/GitRepository.ts b/src/utils/GitRepository.ts index f31181d1b822ef02754d33754cb48899c4cb2bd4..f7cbd442a949b4f8c7bd9371261697b976963a5c 100644 --- a/src/utils/GitRepository.ts +++ b/src/utils/GitRepository.ts @@ -1,17 +1,33 @@ import axios from "axios"; import type { AxiosInstance, AxiosBasicCredentials } from "axios"; import type { WorkflowModeOut, WorkflowModeIn } from "@/client"; +import { DocumentationEnum } from "@/client"; -export function requiredDocumentationFiles( - modes?: WorkflowModeIn[] | WorkflowModeOut[], -): string[] { - const list = ["CHANGELOG.md", "README.md", "docs/usage.md", "docs/output.md"]; - if (modes && modes.length > 0) { - list.push(...modes.map((mode) => mode.schema_path)); - } else { - list.push("nextflow_schema.json"); +interface CheckDocResult { + found?: string; + missing?: string[]; +} + +interface CheckRepoResult { + docs: Record<DocumentationEnum, CheckDocResult>; + mainScriptMissing?: string; +} + +function repositoryPaths(document: DocumentationEnum): string[] { + switch (document) { + case DocumentationEnum.USAGE_MD: + return ["clowm/README.md", "README.md"]; + case DocumentationEnum.INPUT_MD: + return ["clowm/usage.md", "docs/usage.md"]; + case DocumentationEnum.OUTPUT_MD: + return ["clowm/output.md", "docs/output.md"]; + case DocumentationEnum.PARAMETER_SCHEMA_JSON: + return ["clowm/nextflow_schema.json", "nextflow_schema.json"]; + case DocumentationEnum.CHANGELOG_MD: + return ["clowm/CHANGELOG.md", "CHANGELOG.md"]; + case DocumentationEnum.CLOWM_INFO_JSON: + return ["clowm/clowm_info.json", "clowm_info_json"]; } - return list; } export function determineGitIcon(repo_url?: string): string { @@ -69,13 +85,26 @@ export abstract class GitRepository { async validateRepo( modes: WorkflowModeIn[] | WorkflowModeOut[], - ): Promise<string[]> { + ): Promise<CheckRepoResult> { const files = await this.getFileList(); - const missingFiles: string[] = []; const filteredFiles = files.filter( // files in base directory and docs directory - (file) => !file.includes("/") || file.startsWith("docs/"), + (file) => + !file.includes("/") || + file.startsWith("docs/") || + file.startsWith("clowm/"), ); + const checkRepoResult: CheckRepoResult = { + docs: { + "changelog.md": {}, + "usage.md": {}, + "parameter_schema.json": {}, + "clowm_info.json": {}, + "input.md": {}, + "output.md": {}, + }, + mainScriptMissing: undefined, + }; // Check if main.nf is there and if not, check for an alternative name in the nextflow.config if (filteredFiles.findIndex((file) => file === "main.nf") === -1) { try { @@ -84,45 +113,52 @@ export abstract class GitRepository { const entryScriptName = nextflowConfig.match(capturingMainScriptRegex) .groups?.["scriptname"]; if (entryScriptName == undefined) { - missingFiles.push("main.nf"); + checkRepoResult.mainScriptMissing = "main.nf"; } else if (files.findIndex((file) => file === entryScriptName) === -1) { - missingFiles.push(entryScriptName); + checkRepoResult.mainScriptMissing = entryScriptName; } } catch (e) { - console.error(e); - missingFiles.push("main.nf"); + checkRepoResult.mainScriptMissing = "main.nf"; } } - // check mandatory files - const requiredFileList = [ - "CHANGELOG.md", - "README.md", - "docs/usage.md", - "docs/output.md", - ]; - for (const requiredFile of requiredFileList) { - if (filteredFiles.findIndex((file) => file === requiredFile) === -1) { - missingFiles.push(requiredFile); + // Check all documentation files + for (const doc of Object.values(DocumentationEnum)) { + // if parameter schema and modes are present, use check modes schema instead of default one + if (doc === DocumentationEnum.PARAMETER_SCHEMA_JSON && modes.length > 0) { + continue; } - } - // Check required files for modes - if (modes && modes.length > 0) { - for (const requiredFile of modes.map((mode) => mode.schema_path)) { - if (files.findIndex((file) => file === requiredFile) === -1) { - missingFiles.push(requiredFile); + // loop over possible locations + for (const repositoryPath of repositoryPaths(doc)) { + // if a file is found, stop the search and set the found location + if (filteredFiles.findIndex((file) => file === repositoryPath) > -1) { + checkRepoResult.docs[doc].found = repositoryPath; + break; } } - } else { - // if there are no modes, check standard path for parameter schema - if ( - filteredFiles.findIndex((file) => file === "nextflow_schema.json") === - -1 - ) { - missingFiles.push("nextflow_schema.json"); + // if at no location the file is found, set the missing file string + if (checkRepoResult.docs[doc].found == undefined) { + checkRepoResult.docs[doc].missing = [repositoryPaths(doc).join(" or ")]; } } - return missingFiles; + // check schema path of each mode + for (const schemaPath of modes.map((mode) => mode.schema_path)) { + if (files.findIndex((file) => file === schemaPath) === -1) { + if ( + checkRepoResult.docs[DocumentationEnum.PARAMETER_SCHEMA_JSON] + .missing == undefined + ) { + checkRepoResult.docs[ + DocumentationEnum.PARAMETER_SCHEMA_JSON + ].missing = [schemaPath]; + } else { + checkRepoResult.docs[ + DocumentationEnum.PARAMETER_SCHEMA_JSON + ].missing.push(schemaPath); + } + } + } + return checkRepoResult; } protected abstract downloadFileUrl(filepath: string): Promise<string>; @@ -236,33 +272,28 @@ class GitlabRepository extends GitRepository { async getFileList(): Promise<string[]> { const fileList: string[] = []; - try { - let response = await this.httpClient.get( - `https://${this.host}/api/v4/projects/${ - this.project - }/repository/tree?ref=${this.gitCommitHash}&recursive=true&per_page=100&pagination=keyset&order_by=id&sort=asc`, + let response = await this.httpClient.get( + `https://${this.host}/api/v4/projects/${ + this.project + }/repository/tree?ref=${this.gitCommitHash}&recursive=true&per_page=99&pagination=keyset&order_by=id&sort=asc`, + ); + fileList.push( + ...response.data.map( + (file: Record<string, string>): string => file["path"], + ), + ); + while (response.headers?.["link"] != undefined) { + const linkHeader: string = response.headers?.["link"]; + + response = await this.httpClient.get( + linkHeader.split(";")[0].slice(1, -1), ); fileList.push( ...response.data.map( (file: Record<string, string>): string => file["path"], ), ); - while (response.headers?.["link"] != undefined) { - const linkHeader: string = response.headers?.["link"]; - - response = await this.httpClient.get( - linkHeader.split(";")[0].slice(1, -1), - ); - fileList.push( - ...response.data.map( - (file: Record<string, string>): string => file["path"], - ), - ); - } - return fileList; - } catch (e) { - console.error(e); - throw new Error("repo not reachable"); } + return fileList; } } diff --git a/src/views/workflows/ArbitraryWorkflowView.vue b/src/views/workflows/ArbitraryWorkflowView.vue index 0828836465e88fee9c16629cc66f5859b79bda4d..f16e4d5da6d77167cbb621ce162d1811627bca13 100644 --- a/src/views/workflows/ArbitraryWorkflowView.vue +++ b/src/views/workflows/ArbitraryWorkflowView.vue @@ -1,16 +1,13 @@ <script setup lang="ts"> import WorkflowDocumentationTabs from "@/components/workflows/WorkflowDocumentationTabs.vue"; import { onMounted, reactive, ref, watch } from "vue"; -import { - GitRepository, - requiredDocumentationFiles, -} from "@/utils/GitRepository"; +import { GitRepository } from "@/utils/GitRepository"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; import { useRouter } from "vue-router"; import { Toast } from "bootstrap"; -import { useWorkflowStore } from "@/stores/workflows"; -import type { WorkflowIn } from "@/client"; +import { type DocLocations, useWorkflowStore } from "@/stores/workflows"; +import { DocumentationEnum, type WorkflowIn } from "@/client"; import { useWorkflowExecutionStore } from "@/stores/workflowExecutions"; import ParameterSchemaFormComponent from "@/components/parameter-schema/ParameterSchemaFormComponent.vue"; import BootstrapToast from "@/components/BootstrapToast.vue"; @@ -20,6 +17,7 @@ import type { } from "@/types/WorkflowParameters"; import dayjs from "dayjs"; import { AxiosError } from "axios"; +import type { ClowmInfo } from "@/types/ClowmInfo"; const props = defineProps<{ wid: string; @@ -32,16 +30,19 @@ const executionRepository = useWorkflowExecutionStore(); const workflowState = reactive<{ workflow?: WorkflowIn; + docLocations?: DocLocations; loading: boolean; changelogMarkdown?: string; descriptionMarkdown?: string; usageMarkdown?: string; outputMarkdown?: string; parameterSchema?: Record<string, never>; + clowmInfo?: ClowmInfo; repo: GitRepository; ratelimit_reset: number; }>({ loading: true, + docLocations: undefined, workflow: undefined, ratelimit_reset: 0, repo: GitRepository.buildRepository( @@ -62,24 +63,40 @@ const showDocumentation = ref<boolean>(true); let errorToast: Toast | null = null; function downloadVersionFiles() { - if (workflowState.workflow) { + if (workflowState.docLocations) { workflowState.loading = true; Promise.all( - requiredDocumentationFiles(workflowState.workflow.modes).map((file) => - workflowState.repo.downloadFile(file).then((response) => { - if (file.includes("README")) { + Object.values(DocumentationEnum).map(async (doc) => { + if ( + workflowState.docLocations == undefined || + workflowState.docLocations?.[doc]?.length === 0 + ) { + return; + } + const response = await workflowState.repo.downloadFile( + workflowState.docLocations[doc], + ); + switch (doc) { + case DocumentationEnum.PARAMETER_SCHEMA_JSON: + workflowState.parameterSchema = response.data; + break; + case DocumentationEnum.CLOWM_INFO_JSON: + workflowState.clowmInfo = response.data; + break; + case DocumentationEnum.OUTPUT_MD: + workflowState.outputMarkdown = response.data; + break; + case DocumentationEnum.INPUT_MD: + workflowState.usageMarkdown = response.data; + break; + case DocumentationEnum.USAGE_MD: workflowState.descriptionMarkdown = response.data; - } else if (file.includes("CHANGELOG")) { + break; + case DocumentationEnum.CHANGELOG_MD: workflowState.changelogMarkdown = response.data; - } else if (file.includes("usage")) { - workflowState.usageMarkdown = response.data; - } else if (file.includes("output")) { - workflowState.outputMarkdown = response.data; - } else if (file.endsWith("json")) { - workflowState.parameterSchema = response.data; - } - }), - ), + break; + } + }), ) .catch((e) => { if ( @@ -163,8 +180,9 @@ function startWorkflow( onMounted(() => { errorToast = new Toast("#arbitraryWorkflowExecutionViewErrorToast"); - workflowStore.getArbitraryWorkflow(props.wid).then((workflow) => { - workflowState.workflow = workflow; + workflowStore.getArbitraryWorkflow(props.wid).then((result) => { + workflowState.workflow = result?.workflow; + workflowState.docLocations = result?.docs; }); }); </script> @@ -229,6 +247,7 @@ onMounted(() => { :description-markdown="workflowState.descriptionMarkdown" :changelog-markdown="workflowState.changelogMarkdown" :parameter-schema="workflowState.parameterSchema" + :clowm-info="workflowState.clowmInfo" /> <parameter-schema-form-component v-else diff --git a/src/views/workflows/CreateClowmInfoView.vue b/src/views/workflows/CreateClowmInfoView.vue index 5398b353cca2d746fddc18da491bdbeba81083ed..e34e19c6c07e7114e78c4145c0b5f4791590bcd2 100644 --- a/src/views/workflows/CreateClowmInfoView.vue +++ b/src/views/workflows/CreateClowmInfoView.vue @@ -216,8 +216,8 @@ onMounted(() => { usability and increase the user experience of your workflow within CloWM. This can be achieved by simply adding a file named <code>clowm_info.json</code>, containing parameter metadata, into your - repositories root directory. With the provided metadata a workflow developer - can provide + repositories in a folder called <code>clowm</code>. With the provided + metadata a workflow developer can provide <ul> <li>semantic meanings by parameter designation,</li> <li>example parameters to allow users a easy tryout of the workflow,</li> @@ -496,7 +496,10 @@ onMounted(() => { register the new workflow version in CloWM </p> <copy-to-clipboard-icon button :text="infoStateString" /> - <a class="btn btn-primary ms-2" :href="downloadUrl" download="clowm_info.json" + <a + class="btn btn-primary btn-sm ms-2" + :href="downloadUrl" + download="clowm_info.json" >Download to file <font-awesome-icon icon="fa-solid fa-download" class="ms-1" /> </a>