mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-24 12:13:33 +08:00
Update Markdown UI to use STS for PDF export if tasks are enabled.
This commit is contained in:
@@ -153,6 +153,7 @@
|
||||
"ignore-loader": "^0.1.2",
|
||||
"imports-loader": "^3.1.1",
|
||||
"jest": "^27.4.7",
|
||||
"jest-location-mock": "^1.0.9",
|
||||
"jest-raw-loader": "^1.0.1",
|
||||
"jest-transform-yaml": "1.0.0",
|
||||
"json-loader": "^0.5.7",
|
||||
|
||||
@@ -3,16 +3,12 @@
|
||||
<LoadingSpan v-if="loading" />
|
||||
<div v-else>
|
||||
<div>
|
||||
<b-button
|
||||
<sts-download-button
|
||||
v-if="effectiveExportLink"
|
||||
class="float-right markdown-pdf-export"
|
||||
title="Download PDF"
|
||||
variant="link"
|
||||
role="button"
|
||||
v-b-tooltip.hover.bottom
|
||||
@click="onDownload">
|
||||
<font-awesome-icon icon="download" />
|
||||
</b-button>
|
||||
:fallback-url="exportLink"
|
||||
:download-endpoint="downloadEndpoint"
|
||||
title="Download PDF"></sts-download-button>
|
||||
<b-button
|
||||
v-if="!readOnly"
|
||||
class="float-right markdown-edit"
|
||||
@@ -114,6 +110,7 @@ import JobParameters from "./Elements/JobParameters";
|
||||
import ToolStd from "./Elements/ToolStd";
|
||||
import WorkflowDisplay from "./Elements/Workflow/WorkflowDisplay";
|
||||
import Visualization from "./Elements/Visualization";
|
||||
import StsDownloadButton from "./StsDownloadButton";
|
||||
|
||||
const FUNCTION_VALUE_REGEX = `\\s*(?:[\\w_\\-]+|\\"[^\\"]+\\"|\\'[^\\']+\\')\\s*`;
|
||||
const FUNCTION_CALL = `\\s*[\\w\\|]+\\s*=` + FUNCTION_VALUE_REGEX;
|
||||
@@ -149,6 +146,7 @@ export default {
|
||||
Visualization,
|
||||
InvocationTime,
|
||||
FontAwesomeIcon,
|
||||
StsDownloadButton,
|
||||
},
|
||||
props: {
|
||||
markdownConfig: {
|
||||
@@ -159,6 +157,10 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
downloadEndpoint: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import MockConfigProvider from "components/providers/MockConfigProvider";
|
||||
import { mount } from "@vue/test-utils";
|
||||
import flushPromises from "flush-promises";
|
||||
import { getLocalVue } from "jest/helpers";
|
||||
import StsDownloadButton from "./StsDownloadButton";
|
||||
import axios from "axios";
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
|
||||
import "jest-location-mock";
|
||||
|
||||
const localVue = getLocalVue();
|
||||
const NO_TASKS_CONFIG = {
|
||||
enable_celery_tasks: false,
|
||||
};
|
||||
const TASKS_CONFIG = {
|
||||
enable_celery_tasks: true,
|
||||
};
|
||||
const FALLBACK_URL = "http://cow.com/direct_download";
|
||||
const DOWNLOAD_ENDPOINT = "http://cow.com/prepare_download";
|
||||
const STORAGE_REQUEST_ID = "moocow1235";
|
||||
|
||||
async function mountStsDownloadButtonWrapper(config) {
|
||||
const wrapper = mount(StsDownloadButton, {
|
||||
propsData: {
|
||||
title: "my title",
|
||||
fallbackUrl: FALLBACK_URL,
|
||||
downloadEndpoint: DOWNLOAD_ENDPOINT,
|
||||
},
|
||||
stubs: {
|
||||
ConfigProvider: MockConfigProvider(config),
|
||||
},
|
||||
localVue,
|
||||
});
|
||||
await flushPromises();
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
describe("StsDownloadButton", () => {
|
||||
let axiosMock;
|
||||
|
||||
beforeEach(async () => {
|
||||
axiosMock = new MockAdapter(axios);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
axiosMock.reset();
|
||||
});
|
||||
|
||||
it("should fallback to a URL if tasks not enabled", async () => {
|
||||
const wrapper = await mountStsDownloadButtonWrapper(NO_TASKS_CONFIG);
|
||||
wrapper.vm.onDownload(NO_TASKS_CONFIG);
|
||||
await flushPromises();
|
||||
expect(window.location).toBeAt(FALLBACK_URL);
|
||||
});
|
||||
|
||||
it("should poll until ready", async () => {
|
||||
axiosMock.onPost(DOWNLOAD_ENDPOINT).reply(200, { storage_request_id: STORAGE_REQUEST_ID });
|
||||
const wrapper = await mountStsDownloadButtonWrapper(TASKS_CONFIG);
|
||||
axiosMock.onGet(`api/short_term_storage/${STORAGE_REQUEST_ID}/ready`).reply(200, true);
|
||||
|
||||
wrapper.vm.onDownload(TASKS_CONFIG);
|
||||
await flushPromises();
|
||||
expect(window.location).toBeAt(`api/short_term_storage/${STORAGE_REQUEST_ID}`);
|
||||
});
|
||||
|
||||
it("should be in a waiting state while polling", async () => {
|
||||
axiosMock.onPost(DOWNLOAD_ENDPOINT).reply(200, { storage_request_id: STORAGE_REQUEST_ID });
|
||||
const wrapper = await mountStsDownloadButtonWrapper(TASKS_CONFIG);
|
||||
axiosMock.onGet(`api/short_term_storage/${STORAGE_REQUEST_ID}/ready`).reply(200, false);
|
||||
|
||||
expect(wrapper.vm.waiting).toBeFalsy();
|
||||
wrapper.vm.onDownload(TASKS_CONFIG);
|
||||
await flushPromises();
|
||||
expect(wrapper.vm.waiting).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<config-provider v-slot="{ config, loading }">
|
||||
<b-button
|
||||
v-if="!loading && canDownload(config)"
|
||||
class="float-right markdown-pdf-export"
|
||||
:title="title"
|
||||
variant="link"
|
||||
role="button"
|
||||
v-b-tooltip.hover.bottom
|
||||
@click="onDownload(config)">
|
||||
<font-awesome-icon icon="spinner" spin v-if="waiting" />
|
||||
<font-awesome-icon icon="download" v-else />
|
||||
</b-button>
|
||||
</config-provider>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/*
|
||||
A Bootstrap Button with logic for interfacing with Galaxy's short term storage
|
||||
component (STS).
|
||||
*/
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { BButton } from "bootstrap-vue";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faDownload, faSpinner } from "@fortawesome/free-solid-svg-icons";
|
||||
library.add(faDownload, faSpinner);
|
||||
import ConfigProvider from "components/providers/ConfigProvider";
|
||||
import { Toast } from "ui/toast";
|
||||
import axios from "axios";
|
||||
export default {
|
||||
components: {
|
||||
ConfigProvider,
|
||||
FontAwesomeIcon,
|
||||
BButton,
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
downloadEndpoint: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
fallbackUrl: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
waiting: false,
|
||||
delay: 200,
|
||||
};
|
||||
},
|
||||
destroyed() {
|
||||
this.clearTimeout();
|
||||
},
|
||||
methods: {
|
||||
canDownload(config) {
|
||||
if (!config.enable_celery_tasks) {
|
||||
return this.fallbackUrl != null;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
onDownload(config) {
|
||||
if (!config.enable_celery_tasks) {
|
||||
console.log("celery tasks not enabled - setting href to fallback URL.");
|
||||
window.location.assign(this.fallbackUrl);
|
||||
return;
|
||||
}
|
||||
this.waiting = true;
|
||||
axios.post(this.downloadEndpoint).then(this.handleInitialize).catch(this.handleError);
|
||||
},
|
||||
handleInitialize(response) {
|
||||
const storageRequestId = response.data.storage_request_id;
|
||||
this.pollStorageRequestId(storageRequestId);
|
||||
},
|
||||
pollStorageRequestId(storageRequestId) {
|
||||
const url = `${getAppRoot()}api/short_term_storage/${storageRequestId}/ready`;
|
||||
axios
|
||||
.get(url)
|
||||
.then((r) => {
|
||||
this.handlePollResponse(r, storageRequestId);
|
||||
})
|
||||
.catch(this.handleError);
|
||||
},
|
||||
handlePollResponse(response, storageRequestId) {
|
||||
const ready = response.data;
|
||||
if (ready) {
|
||||
const url = `${getAppRoot()}api/short_term_storage/${storageRequestId}`;
|
||||
window.location.assign(url);
|
||||
this.waiting = false;
|
||||
} else {
|
||||
this.pollAfterDelay(storageRequestId);
|
||||
}
|
||||
},
|
||||
handleError(err) {
|
||||
Toast.error(`Failed to generate download: ${err}`);
|
||||
this.waiting = false;
|
||||
},
|
||||
clearTimeout() {
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
}
|
||||
},
|
||||
pollAfterDelay(storageRequestId) {
|
||||
this.clearTimeout();
|
||||
this.timeout = setTimeout(() => {
|
||||
this.pollStorageRequestId(storageRequestId);
|
||||
}, this.delay);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -4,6 +4,7 @@
|
||||
v-if="!loading"
|
||||
:markdown-config="markdownConfig"
|
||||
:enable_beta_markdown_export="config.enable_beta_markdown_export"
|
||||
:download-endpoint="stsUrl(config)"
|
||||
:export-link="exportUrl"
|
||||
@onEdit="onEdit" />
|
||||
</config-provider>
|
||||
@@ -51,6 +52,9 @@ export default {
|
||||
onEdit() {
|
||||
window.location = this.editUrl;
|
||||
},
|
||||
stsUrl(config) {
|
||||
return `${this.dataUrl}/prepare_download`;
|
||||
},
|
||||
async getContent() {
|
||||
try {
|
||||
const response = await axios.get(this.dataUrl);
|
||||
|
||||
@@ -1216,6 +1216,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98"
|
||||
integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==
|
||||
|
||||
"@jedmao/location@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@jedmao/location/-/location-3.0.0.tgz#f2b24e937386f95252f3a1fefbf7ca2e0a4b87e9"
|
||||
integrity sha512-p7mzNlgJbCioUYLUEKds3cQG4CHONVFJNYqMe6ocEtENCL/jYmMo1Q3ApwsMmU+L0ZkaDJEyv4HokaByLoPwlQ==
|
||||
|
||||
"@jest/console@^27.4.6":
|
||||
version "27.4.6"
|
||||
resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.4.6.tgz#0742e6787f682b22bdad56f9db2a8a77f6a86107"
|
||||
@@ -3758,6 +3763,11 @@ diff-sequences@^27.4.0:
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.4.0.tgz#d783920ad8d06ec718a060d00196dfef25b132a5"
|
||||
integrity sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww==
|
||||
|
||||
diff-sequences@^27.5.1:
|
||||
version "27.5.1"
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327"
|
||||
integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==
|
||||
|
||||
diff@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
|
||||
@@ -5913,6 +5923,16 @@ jest-diff@^27.0.0, jest-diff@^27.4.6:
|
||||
jest-get-type "^27.4.0"
|
||||
pretty-format "^27.4.6"
|
||||
|
||||
jest-diff@^27.0.1:
|
||||
version "27.5.1"
|
||||
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def"
|
||||
integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==
|
||||
dependencies:
|
||||
chalk "^4.0.0"
|
||||
diff-sequences "^27.5.1"
|
||||
jest-get-type "^27.5.1"
|
||||
pretty-format "^27.5.1"
|
||||
|
||||
jest-docblock@^27.4.0:
|
||||
version "27.4.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.4.0.tgz#06c78035ca93cbbb84faf8fce64deae79a59f69f"
|
||||
@@ -5961,6 +5981,11 @@ jest-get-type@^27.4.0:
|
||||
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.4.0.tgz#7503d2663fffa431638337b3998d39c5e928e9b5"
|
||||
integrity sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ==
|
||||
|
||||
jest-get-type@^27.5.1:
|
||||
version "27.5.1"
|
||||
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1"
|
||||
integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==
|
||||
|
||||
jest-haste-map@^27.4.6:
|
||||
version "27.4.6"
|
||||
resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.4.6.tgz#c60b5233a34ca0520f325b7e2cc0a0140ad0862a"
|
||||
@@ -6012,6 +6037,14 @@ jest-leak-detector@^27.4.6:
|
||||
jest-get-type "^27.4.0"
|
||||
pretty-format "^27.4.6"
|
||||
|
||||
jest-location-mock@^1.0.9:
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/jest-location-mock/-/jest-location-mock-1.0.9.tgz#f4466362423b273e12ca3716467a3d478ce78fa8"
|
||||
integrity sha512-DN/v7Zsa3N4uGgWTCrMrPPxhZORr/4N5gi+u7Tk6sLdORYplrC0//wfFN5FOtx4ZdQzDVfY6rLa4d+wfTKzQHw==
|
||||
dependencies:
|
||||
"@jedmao/location" "^3.0.0"
|
||||
jest-diff "^27.0.1"
|
||||
|
||||
jest-matcher-utils@^27.4.6:
|
||||
version "27.4.6"
|
||||
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.4.6.tgz#53ca7f7b58170638590e946f5363b988775509b8"
|
||||
@@ -7944,6 +7977,15 @@ pretty-format@^27.0.0, pretty-format@^27.4.6:
|
||||
ansi-styles "^5.0.0"
|
||||
react-is "^17.0.1"
|
||||
|
||||
pretty-format@^27.5.1:
|
||||
version "27.5.1"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e"
|
||||
integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
ansi-styles "^5.0.0"
|
||||
react-is "^17.0.1"
|
||||
|
||||
pretty-hrtime@^1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
|
||||
|
||||
@@ -205,6 +205,7 @@ class ConfigSerializer(base.ModelSerializer):
|
||||
"release_doc_base_url": _use_config,
|
||||
"expose_user_email": _use_config,
|
||||
"enable_tool_source_display": _use_config,
|
||||
"enable_celery_tasks": _use_config,
|
||||
"user_library_import_dir_available": lambda item, key, **context: bool(item.get("user_library_import_dir")),
|
||||
"welcome_directory": _use_config,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user