Unverified Commit 85df98a7 authored by Dannon's avatar Dannon Committed by GitHub
Browse files

Merge pull request #15991 from davelopez/23.0_fix_history_export_papercuts

[23.0] Fix history export papercuts
parents e6757c2f 18891fd7
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -19,7 +19,13 @@ const mockGetExportRecords = getExportRecords as jest.MockedFunction<typeof getE
mockGetExportRecords.mockResolvedValue([]);

const FAKE_HISTORY_ID = "fake-history-id";
const FAKE_HISTORY = {
    id: FAKE_HISTORY_ID,
    name: "fake-history-name",
};

const REMOTE_FILES_API_ENDPOINT = new RegExp("/api/remote_files/plugins");
const GET_HISTORY_ENDPOINT = new RegExp(`/api/histories/${FAKE_HISTORY_ID}`);

type FilesSourcePluginList = components["schemas"]["FilesSourcePlugin"][];
const REMOTE_FILES_API_RESPONSE: FilesSourcePluginList = [
@@ -50,12 +56,19 @@ describe("HistoryExport.vue", () => {
    beforeEach(async () => {
        axiosMock = new MockAdapter(axios);
        axiosMock.onGet(REMOTE_FILES_API_ENDPOINT).reply(200, []);
        axiosMock.onGet(GET_HISTORY_ENDPOINT).reply(200, FAKE_HISTORY);
    });

    afterEach(() => {
        axiosMock.restore();
    });

    it("should render the history name", async () => {
        const wrapper = await mountHistoryExport();

        expect(wrapper.find("#history-name").text()).toBe(FAKE_HISTORY.name);
    });

    it("should render export options", async () => {
        const wrapper = await mountHistoryExport();

+56 −36
Original line number Diff line number Diff line
@@ -5,7 +5,8 @@ import LoadingSpan from "components/LoadingSpan";
import ExportRecordDetails from "components/Common/ExportRecordDetails.vue";
import ExportRecordTable from "components/Common/ExportRecordTable.vue";
import ExportOptions from "./ExportOptions.vue";
import ExportToFileSourceForm from "components/Common/ExportForm.vue";
import ExportForm from "components/Common/ExportForm.vue";
import { getHistoryById } from "@/store/historyStore/model/queries";
import { getExportRecords, exportToFileSource, reimportHistoryFromRecord } from "./services";
import { useTaskMonitor } from "composables/taskMonitor";
import { useFileSources } from "composables/fileSources";
@@ -13,6 +14,9 @@ import { useShortTermStorage, DEFAULT_EXPORT_PARAMS } from "composables/shortTer
import { useConfirmDialog } from "composables/confirmDialog";
import { copy as sendToClipboard } from "utils/clipboard";
import { absPath } from "@/utils/redirect";
import { faFileExport } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { library } from "@fortawesome/fontawesome-svg-core";

const {
    isRunning: isExportTaskRunning,
@@ -25,7 +29,7 @@ const { hasWritable: hasWritableFileSources } = useFileSources();

const {
    isPreparing: isPreparingDownload,
    downloadHistory,
    prepareHistoryDownload,
    downloadObjectByRequestId,
    getDownloadObjectUrl,
} = useShortTermStorage();
@@ -39,12 +43,27 @@ const props = defineProps({
    },
});

library.add(faFileExport);

const POLLING_DELAY = 3000;

const history = ref(null);
const isLoadingHistory = ref(true);

const exportParams = reactive(DEFAULT_EXPORT_PARAMS);
const isLoadingRecords = ref(true);
const exportRecords = ref(null);

const historyName = computed(() => history.value?.name ?? props.historyId);
const latestExportRecord = computed(() => (exportRecords.value?.length ? exportRecords.value.at(0) : null));
const isLatestExportRecordReadyToDownload = computed(
    () =>
        latestExportRecord.value &&
        latestExportRecord.value.isUpToDate &&
        latestExportRecord.value.canDownload &&
        latestExportRecord.value.exportParams?.equals(exportParams)
);
const canGenerateDownload = computed(() => !isPreparingDownload.value && !isLatestExportRecordReadyToDownload.value);
const previousExportRecords = computed(() => (exportRecords.value ? exportRecords.value.slice(1) : null));
const hasPreviousExports = computed(() => previousExportRecords.value?.length > 0);
const availableRecordsMessage = computed(() =>
@@ -58,6 +77,10 @@ const actionMessageVariant = ref(null);

onMounted(async () => {
    updateExports();
    //TODO: replace direct query with useHistoriesStore in 23.1
    isLoadingHistory.value = true;
    history.value = await getHistoryById(props.historyId);
    isLoadingHistory.value = false;
});

watch(isExportTaskRunning, (newValue, oldValue) => {
@@ -99,12 +122,7 @@ async function doExportToFileSource(exportDirectory, fileName) {
}

async function prepareDownload() {
    const upToDateDownloadRecord = findValidUpToDateDownloadRecord();
    if (upToDateDownloadRecord) {
        downloadObjectByRequestId(upToDateDownloadRecord.stsDownloadId);
        return;
    }
    await downloadHistory(props.historyId, { pollDelayInMs: POLLING_DELAY, exportParams: exportParams });
    await prepareHistoryDownload(props.historyId, { pollDelayInMs: POLLING_DELAY, exportParams: exportParams });
    updateExports();
}

@@ -121,14 +139,6 @@ function copyDownloadLinkFromRecord(record) {
    }
}

function findValidUpToDateDownloadRecord() {
    return exportRecords.value
        ? exportRecords.value.find(
              (record) => record.canDownload && record.isUpToDate && record.exportParams?.equals(exportParams)
          )
        : null;
}

async function reimportFromRecord(record) {
    const confirmed = await confirm(
        `Do you really want to import a new copy of this history exported ${record.elapsedTime}?`
@@ -161,15 +171,22 @@ function updateExportParams(newParams) {
</script>
<template>
    <span class="history-export-component">
        <h1 class="h-lg">Export history {{ props.historyId }}</h1>
        <font-awesome-icon icon="file-export" size="2x" class="text-primary float-left mr-2" />
        <h1 class="h-lg">
            Export
            <loading-span v-if="isLoadingHistory" spinner-only />
            <b v-else id="history-name">{{ historyName }}</b>
        </h1>

        <export-options
            id="history-export-options"
            class="mt-3"
            :export-params="exportParams"
            @onValueChanged="updateExportParams" />

        <h2 class="h-md mt-3">How do you want to export this history?</h2>
        <b-card no-body class="mt-3">
            <b-tabs pills card>
            <b-tabs pills card vertical>
                <b-tab id="direct-download-tab" title="to direct download" title-link-class="tab-export-to-link" active>
                    <p>
                        Here you can generate a temporal download for your history. When your download link expires or
@@ -181,15 +198,19 @@ function updateExportParams(newParams) {
                        Galaxy server.
                    </b-alert>
                    <b-button
                        class="direct-download-btn"
                        :disabled="isPreparingDownload"
                        class="gen-direct-download-btn"
                        :disabled="!canGenerateDownload"
                        variant="primary"
                        @click="prepareDownload">
                        Download
                        Generate direct download
                    </b-button>
                    <span v-if="isPreparingDownload">
                        <loading-span message="Galaxy is preparing your download, this will likely take a while" />
                    </span>
                    <b-alert v-else-if="isLatestExportRecordReadyToDownload" variant="success" class="mt-3" show>
                        The latest export record is ready. Use the download button below to download it or change the
                        advanced export options above to generate a new one.
                    </b-alert>
                </b-tab>
                <b-tab
                    v-if="hasWritableFileSources"
@@ -201,10 +222,7 @@ function updateExportParams(newParams) {
                        one of the available remote file sources here. You will be able to re-import it later as long as
                        it remains available on the remote server.
                    </p>
                    <export-to-file-source-form
                        what="history"
                        :clear-input-after-export="true"
                        @export="doExportToFileSource" />
                    <export-form what="history" :clear-input-after-export="true" @export="doExportToFileSource" />
                </b-tab>
            </b-tabs>
        </b-card>
@@ -212,8 +230,9 @@ function updateExportParams(newParams) {
        <b-alert v-if="errorMessage" id="last-export-record-error-alert" variant="danger" class="mt-3" show>
            {{ errorMessage }}
        </b-alert>
        <div v-else-if="latestExportRecord">
            <h2 class="h-md mt-3">Latest Export Record</h2>
            <export-record-details
            v-else-if="latestExportRecord"
                :record="latestExportRecord"
                object-type="history"
                class="mt-3"
@@ -223,6 +242,7 @@ function updateExportParams(newParams) {
                @onCopyDownloadLink="copyDownloadLinkFromRecord"
                @onReimport="reimportFromRecord"
                @onActionMessageDismissed="onActionMessageDismissedFromRecord" />
        </div>
        <b-alert v-else id="no-export-records-alert" variant="info" class="mt-3" show>
            {{ availableRecordsMessage }}
        </b-alert>
+34 −12
Original line number Diff line number Diff line
@@ -22,12 +22,20 @@ export function useShortTermStorage() {

    const isPreparing = ref(false);

    async function prepareHistoryDownload(historyId, options = DEFAULT_OPTIONS) {
        return prepareObjectDownload(historyId, "histories", options, false);
    }

    async function downloadHistory(historyId, options = DEFAULT_OPTIONS) {
        return prepareObjectDownload(historyId, "histories", options);
        return prepareObjectDownload(historyId, "histories", options, true);
    }

    async function prepareWorkflowInvocationDownload(invocationId, options = DEFAULT_OPTIONS) {
        return prepareObjectDownload(invocationId, "invocations", options, false);
    }

    async function downloadWorkflowInvocation(invocationId, options = DEFAULT_OPTIONS) {
        return prepareObjectDownload(invocationId, "invocations", options);
        return prepareObjectDownload(invocationId, "invocations", options, true);
    }

    function getDownloadObjectUrl(storageRequestId) {
@@ -40,7 +48,7 @@ export function useShortTermStorage() {
        window.location.assign(url);
    }

    async function prepareObjectDownload(object_id, object_api, options = DEFAULT_OPTIONS) {
    async function prepareObjectDownload(object_id, object_api, options = DEFAULT_OPTIONS, downloadWhenReady = true) {
        const finalOptions = Object.assign(DEFAULT_OPTIONS, options);
        resetTimeout();
        isPreparing.value = true;
@@ -54,29 +62,31 @@ export function useShortTermStorage() {
        };

        const response = await axios.post(url, exportParams).catch(handleError);
        handleInitialize(response);
        handleInitialize(response, downloadWhenReady);
    }

    function handleInitialize(response) {
    function handleInitialize(response, downloadWhenReady) {
        const storageRequestId = response.data.storage_request_id;
        pollStorageRequestId(storageRequestId);
        pollStorageRequestId(storageRequestId, downloadWhenReady);
    }

    function pollStorageRequestId(storageRequestId) {
    function pollStorageRequestId(storageRequestId, downloadWhenReady) {
        const url = withPrefix(`/api/short_term_storage/${storageRequestId}/ready`);
        axios
            .get(url)
            .then((r) => {
                handlePollResponse(r, storageRequestId);
                handlePollResponse(r, storageRequestId, downloadWhenReady);
            })
            .catch(handleError);
    }

    function handlePollResponse(response, storageRequestId) {
    function handlePollResponse(response, storageRequestId, downloadWhenReady) {
        const ready = response.data;
        if (ready) {
            isPreparing.value = false;
            if (downloadWhenReady) {
                downloadObjectByRequestId(storageRequestId);
            }
        } else {
            pollAfterDelay(storageRequestId);
        }
@@ -103,13 +113,25 @@ export function useShortTermStorage() {

    return {
        /**
         * Starts preparing a history download file. When `isPreparing` is false the download will start automatically.
         * Starts preparing a history download file in the short term storage.
         * @param {String} historyId The ID of the history to be prepared for download
         * @param {Object} options Options for the download preparation
         */
        prepareHistoryDownload,
        /**
         * Prepares a history download file in the short term storage and starts the download when ready.
         * @param {String} historyId The ID of the history to be downloaded
         * @param {Object} options Options for the download preparation
         */
        downloadHistory,
        /**
         * Starts preparing a workflow invocation download file. When `isPreparing` is false the download will start automatically.
         * Starts preparing a workflow invocation download file in the short term storage.
         * @param {String} invocationId The ID of the workflow invocation to be prepared for download
         * @param {Object} options Options for the download preparation
         */
        prepareWorkflowInvocationDownload,
        /**
         * Starts preparing a workflow invocation download file in the short term storage and starts the download when ready.
         * @param {String} invocationId The ID of the workflow invocation to be downloaded
         * @param {Object} options Options for the download preparation
         */
+1 −1
Original line number Diff line number Diff line
@@ -425,7 +425,7 @@ history_export:

history_export_tasks:
  selectors:
    direct_download: '.direct-download-btn'
    direct_download: '.gen-direct-download-btn'
    file_source_tab: '.tab-export-to-file'
    remote_file_name_input: '#file-source-tab #name'
    toggle_options_link: '#toggle-options-link'