From 36c96893e3f459955e199ef28137a1e1e6fac537 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Mon, 16 Sep 2024 08:47:22 +0200 Subject: [PATCH] Refactor BioCompute send to BCODB Extract logic into a service module and Integrate it into the wizard process. --- .../Export/InvocationExportWizard.vue | 129 +++++++++++-- .../Plugins/BioComputeObject/SendForm.vue | 181 ------------------ .../Plugins/BioComputeObject/service.ts | 76 ++++++++ 3 files changed, 191 insertions(+), 195 deletions(-) delete mode 100644 client/src/components/Workflow/Invocation/Export/Plugins/BioComputeObject/SendForm.vue create mode 100644 client/src/components/Workflow/Invocation/Export/Plugins/BioComputeObject/service.ts diff --git a/client/src/components/Workflow/Invocation/Export/InvocationExportWizard.vue b/client/src/components/Workflow/Invocation/Export/InvocationExportWizard.vue index 013fee3fcab9..824d72f4e84a 100644 --- a/client/src/components/Workflow/Invocation/Export/InvocationExportWizard.vue +++ b/client/src/components/Workflow/Invocation/Export/InvocationExportWizard.vue @@ -21,6 +21,10 @@ import { getInvocationExportPluginByType, type InvocationExportPluginType, } from "@/components/Workflow/Invocation/Export/Plugins"; +import { + type BcoDatabaseExportData, + saveInvocationBCOToDatabase, +} from "@/components/Workflow/Invocation/Export/Plugins/BioComputeObject/service"; import { useFileSources } from "@/composables/fileSources"; import { useMarkdown } from "@/composables/markdown"; import { useShortTermStorageMonitor } from "@/composables/shortTermStorageMonitor"; @@ -44,20 +48,21 @@ const { hasWritable: hasWritableRDMFileSources } = useFileSources({ include: ["r const resource = "workflow invocation"; -type ExportDestination = "download" | "remote-source" | "rdm-repository"; +type InvocationExportDestination = "download" | "remote-source" | "rdm-repository" | "bco-database"; interface ExportDestinationInfo { - destination: ExportDestination; + destination: InvocationExportDestination; label: string; markdownDescription: string; } -interface ExportData { +interface InvocationExportData { exportPluginFormat: InvocationExportPluginType; - destination: ExportDestination; + destination: InvocationExportDestination; remoteUri: string; outputFileName: string; includeData: boolean; + bcoDatabase: BcoDatabaseExportData; } interface Props { @@ -70,12 +75,20 @@ const exportToRemoteTaskId = ref(); const exportToStsRequestId = ref(); const errorMessage = ref(); -const exportData: ExportData = reactive({ +const existingProgress = ref>(); + +const exportData: InvocationExportData = reactive({ exportPluginFormat: "ro-crate", destination: "download", remoteUri: "", outputFileName: "", includeData: true, + bcoDatabase: { + serverBaseUrl: "https://biocomputeobject.org", + table: "GALXY", + ownerGroup: "", + authorization: "", + }, }); const exportButtonLabel = computed(() => { @@ -86,6 +99,8 @@ const exportButtonLabel = computed(() => { return "Export to Remote Source"; case "rdm-repository": return "Export to RDM Repository"; + case "bco-database": + return "Export to BCODB"; default: return "Export"; } @@ -151,10 +166,25 @@ const stepper = useStepper({ isValid: () => Boolean(exportData.remoteUri), isSkippable: () => exportData.destination !== "rdm-repository", }, + "setup-bcodb": { + label: "Select BCODB Server", + instructions: "Provide BCODB server and authentication details", + isValid: () => + Boolean( + exportData.bcoDatabase.serverBaseUrl && + exportData.bcoDatabase.authorization && + exportData.bcoDatabase.table && + exportData.bcoDatabase.ownerGroup + ), + isSkippable: () => exportData.destination !== "bco-database", + }, "export-summary": { label: "Export", instructions: "Summary", - isValid: () => Boolean(exportData.outputFileName) || exportData.destination === "download", + isValid: () => + Boolean(exportData.outputFileName) || + exportData.destination === "download" || + exportData.destination === "bco-database", isSkippable: () => false, }, }); @@ -198,10 +228,17 @@ function onRecordSelected(recordUri: string) { } async function exportInvocation() { - if (exportData.destination === "download") { - await exportToSts(); - } else { - await exportToFileSource(); + switch (exportData.destination) { + case "download": + await exportToSts(); + break; + case "remote-source": + case "rdm-repository": + await exportToFileSource(); + break; + case "bco-database": + await saveInvocationBCOToDatabase(props.invocationId, exportData.bcoDatabase); + break; } //@ts-ignore incorrect property does not exist on type error existingProgress.value?.updateExistingExportProgress(); @@ -275,6 +312,17 @@ function initializeExportDestinations(): ExportDestinationInfo[] { }, ]; + if (exportData.exportPluginFormat === "bco") { + destinations.push({ + destination: "bco-database", + label: "BCO Database", + markdownDescription: `You can upload your ${resource} to a **BCODB** server here. + +Submission to the BCODB **requires that a user already has an authenticated account** at the server they wish to submit to. +More information about how to set up an account and submit data to a BCODB server can be found [here](https://w3id.org/biocompute/tutorials/galaxy_quick_start).`, + }); + } + if (hasWritableFileSources.value) { destinations.push({ destination: "remote-source", @@ -296,13 +344,9 @@ Examples of RDM repositories include [Zenodo](https://zenodo.org/), [Invenio RDM }); } - //TODO: Add BCO-database as a destination if BCO selected as export format - return destinations; } -const existingProgress = ref>(); - /** * This is a workaround to make the grid columns template dynamic based on the number of visible steps. */ @@ -427,6 +471,57 @@ const stepsGridColumnsTemplate = computed(() => { +
+

+ To submit to a BCODB you need to already have an authenticated account. Instructions on + submitting a BCO from Galaxy are available + + here + +

+ + + + + + + + + + + + + + + +
+
{ + + {{ exportData.bcoDatabase.table }} + + {{ exportData.bcoDatabase.serverBaseUrl }} + +
diff --git a/client/src/components/Workflow/Invocation/Export/Plugins/BioComputeObject/SendForm.vue b/client/src/components/Workflow/Invocation/Export/Plugins/BioComputeObject/SendForm.vue deleted file mode 100644 index ef219e57601c..000000000000 --- a/client/src/components/Workflow/Invocation/Export/Plugins/BioComputeObject/SendForm.vue +++ /dev/null @@ -1,181 +0,0 @@ - - - diff --git a/client/src/components/Workflow/Invocation/Export/Plugins/BioComputeObject/service.ts b/client/src/components/Workflow/Invocation/Export/Plugins/BioComputeObject/service.ts new file mode 100644 index 000000000000..44e94d904e4e --- /dev/null +++ b/client/src/components/Workflow/Invocation/Export/Plugins/BioComputeObject/service.ts @@ -0,0 +1,76 @@ +import axios from "axios"; + +import { Toast } from "@/composables/toast"; +import { withPrefix } from "@/utils/redirect"; +import { errorMessageAsString } from "@/utils/simple-error"; +import { wait } from "@/utils/utils"; + +/** + * Data needed to send a BioCompute Object to a BCODB server. + */ +export interface BcoDatabaseExportData { + serverBaseUrl: string; + authorization: string; + table: string; + ownerGroup: string; +} + +/** + * Generate a BioCompute Object from an invocation and send it to a BCODB server. + * @param invocationId The ID of the invocation to export. + * @param inputData The data needed to send the BCO to the BCODB. + */ +export async function saveInvocationBCOToDatabase(invocationId: string, inputData: BcoDatabaseExportData) { + try { + const bcoContent = await generateBcoContent(invocationId); + await sendBco(bcoContent, inputData); + Toast.success(`Invocation successfully sent to: ${inputData.serverBaseUrl}`); + } catch (err) { + Toast.error(errorMessageAsString(err), "Error sending BioCompute Object to BCODB"); + } +} + +async function generateBcoContent(invocationId: string) { + const data = { + model_store_format: "bco.json", + include_files: false, + include_deleted: false, + include_hidden: false, + }; + const response = await axios.post(withPrefix(`/api/invocations/${invocationId}/prepare_store_download`), data); + const storage_request_id = response.data.storage_request_id; + const pollUrl = withPrefix(`/api/short_term_storage/${storage_request_id}/ready`); + const resultUrl = withPrefix(`/api/short_term_storage/${storage_request_id}`); + let pollingResponse = await axios.get(pollUrl); + let maxRetries = 120; + while (!pollingResponse.data && maxRetries) { + await wait(2000); + pollingResponse = await axios.get(pollUrl); + maxRetries -= 1; + } + if (!pollingResponse.data) { + throw Error("Timeout waiting for BioCompute Object export result!"); + } else { + const resultResponse = await axios.get(resultUrl); + return resultResponse.data; + } +} + +async function sendBco(bcoContent: string, inputData: BcoDatabaseExportData) { + const bcoData = { + POST_api_objects_draft_create: [ + { + contents: bcoContent, + owner_group: inputData.ownerGroup, + schema: "IEEE", + prefix: inputData.table, + }, + ], + }; + + const headers = { + Authorization: "Token " + inputData.authorization, + "Content-type": "application/json; charset=UTF-8", + }; + await axios.post(`${inputData.serverBaseUrl}/api/objects/drafts/create/`, bcoData, { headers: headers }); +}