diff --git a/client/galaxy/scripts/components/DataDialog/DataDialog.vue b/client/galaxy/scripts/components/DataDialog/DataDialog.vue
index f7dd42fa8fa..aff6fe39a65 100644
--- a/client/galaxy/scripts/components/DataDialog/DataDialog.vue
+++ b/client/galaxy/scripts/components/DataDialog/DataDialog.vue
@@ -23,6 +23,10 @@
Back
+
+
+ Upload
+
{
axiosMock.onGet(`/api/datatypes/types_and_mapping`).reply(200, typesAndMappingResponse);
await getDatatypesMapper().then((mapper) => {
expect(mapper.isSubType("txt", "data")).to.equals(true);
+ expect(mapper.isSubType("txt", "txt")).to.equals(true);
expect(mapper.isSubType("data", "txt")).to.equals(false);
+ expect(mapper.isSubTypeOfAny("data", ["txt", "data"])).to.equals(true);
});
});
});
diff --git a/client/galaxy/scripts/components/Datatypes/model.js b/client/galaxy/scripts/components/Datatypes/model.js
index 1039dd96d20..a6812070821 100644
--- a/client/galaxy/scripts/components/Datatypes/model.js
+++ b/client/galaxy/scripts/components/Datatypes/model.js
@@ -10,4 +10,8 @@ export class DatatypesMapperModel {
parent = mapping.ext_to_class_name[parent];
return mapping.class_to_classes[child] && parent in mapping.class_to_classes[child];
}
+
+ isSubTypeOfAny(child, parents) {
+ return parents.some((parent) => this.isSubType(child, parent));
+ }
}
diff --git a/client/galaxy/scripts/components/Upload/Collection.vue b/client/galaxy/scripts/components/Upload/Collection.vue
index 0a116e7f0c2..64b0947d72f 100644
--- a/client/galaxy/scripts/components/Upload/Collection.vue
+++ b/client/galaxy/scripts/components/Upload/Collection.vue
@@ -42,7 +42,7 @@
-
+
{{ btnCloseTitle }}
-
+
{{ btnCloseTitle }}
-
+
{{ btnCloseTitle }}
{{ btnStopTitle }}
+
+ {{ btnSelectTitle }}
+
{
this._eventAnnounce(index, file);
},
@@ -200,6 +220,17 @@ export default {
},
},
methods: {
+ _eventSelect: function () {
+ const models = this.getUploadedModels();
+ const asDict = models.map((model) => {
+ return {
+ id: model.attributes.id, // model.id has datatype prefix
+ src: model.src,
+ };
+ });
+ this.callback(asDict);
+ this.app.cancel();
+ },
_newUploadModelProps: function (index, file) {
return {
id: index,
@@ -213,8 +244,9 @@ export default {
/** Success */
_eventSuccess: function (index, message) {
+ var hids = _.pluck(message["outputs"], "hid");
var it = this.collection.get(index);
- it.set({ percentage: 100, status: "success" });
+ it.set({ percentage: 100, status: "success", hids: hids });
this._updateStateForSuccess(it);
},
diff --git a/client/galaxy/scripts/components/Upload/RulesInput.vue b/client/galaxy/scripts/components/Upload/RulesInput.vue
index 567ec96b57a..085005e90a9 100644
--- a/client/galaxy/scripts/components/Upload/RulesInput.vue
+++ b/client/galaxy/scripts/components/Upload/RulesInput.vue
@@ -46,7 +46,7 @@
>
-
+
{{ btnCloseTitle }}
0;
+ const counterNonRunning = this.counterAnnounce + this.counterSuccess + this.counterError;
+ this.enableReset = this.counterRunning == 0 && counterNonRunning > 0;
this.enableStart = this.counterRunning == 0 && this.counterAnnounce > 0;
this.enableBuild =
this.counterRunning == 0 &&
this.counterAnnounce == 0 &&
this.counterSuccess > 0 &&
this.counterError == 0;
- this.enableSources = this.counterRunning == 0;
+ this.enableSources = this.counterRunning == 0 && (this.multiple || counterNonRunning == 0);
var show_table = this.counterAnnounce + this.counterSuccess + this.counterError > 0;
this.showHelper = !show_table;
},
@@ -259,7 +259,7 @@ export default {
return $(this.$refs.uploadTable);
},
extensionDetails(extension) {
- return findExtension(this.listExtensions, extension);
+ return findExtension(this.effectiveExtensions, extension);
},
initExtensionInfo() {
$(this.$refs.footerExtensionInfo)
@@ -283,7 +283,7 @@ export default {
this.collection = new UploadModel.Collection();
},
initAppProperties() {
- this.listExtensions = this.app.listExtensions;
+ this.listExtensions = this.app.effectiveExtensions;
this.listGenomes = this.app.listGenomes;
this.ftpUploadSite = this.app.currentFtp();
this.fileSourcesConfigured = this.app.fileSourcesConfigured;
diff --git a/client/galaxy/scripts/components/Upload/UploadModal.vue b/client/galaxy/scripts/components/Upload/UploadModal.vue
index 6e441f36b98..fc8e30dd9be 100644
--- a/client/galaxy/scripts/components/Upload/UploadModal.vue
+++ b/client/galaxy/scripts/components/Upload/UploadModal.vue
@@ -6,23 +6,25 @@
modal-class="ui-modal"
dialog-class="upload-dialog"
body-class="upload-dialog-body"
+ ref="modal"
no-enforce-focus
hide-footer
+ :id="id"
>
{{ title }}
-
-
+
+
-
+
-
+
-
+
@@ -40,6 +42,7 @@ import Vue from "vue";
import BootstrapVue from "bootstrap-vue";
import { getGalaxyInstance } from "app";
import UploadUtils from "mvc/upload/upload-utils";
+import { getDatatypesMapper } from "components/Datatypes";
import Composite from "./Composite";
import Collection from "./Collection";
import Default from "./Default";
@@ -93,6 +96,20 @@ export default {
type: Boolean,
default: false,
},
+ formats: {
+ type: Array,
+ default: null,
+ },
+ multiple: {
+ // Restrict the forms to a single dataset upload if false
+ type: Boolean,
+ default: true,
+ },
+ callback: {
+ // Return uploads when done if supplied.
+ type: Function,
+ default: null,
+ },
auto: {
type: Object,
default: function () {
@@ -102,6 +119,7 @@ export default {
},
data: function () {
return {
+ id: "",
title: _l("Download from web or upload from disk"),
historyAvailable: false,
currentUser: null,
@@ -109,6 +127,8 @@ export default {
listExtensions: [],
genomesSet: false,
extensionsSet: false,
+ datatypesMapper: null,
+ datatypesMapperReady: true,
};
},
created: function () {
@@ -138,11 +158,67 @@ export default {
}, this.defaultGenome);
this.initStateWhenHistoryReady();
+ if (this.formats !== null) {
+ this.datatypesMapperReady = false;
+ getDatatypesMapper().then((datatypesMapper) => {
+ this.datatypesMapper = datatypesMapper;
+ this.datatypesMapperReady = true;
+ });
+ } else {
+ this.datatypesMapperReady = true;
+ }
},
computed: {
ready() {
- return this.genomesSet && this.extensionsSet && this.historyAvailable;
+ return this.genomesSet && this.extensionsSet && this.historyAvailable && this.datatypesMapperReady;
},
+ unrestricted() {
+ return this.formats === null && this.multiple;
+ },
+ effectiveExtensions() {
+ if (this.formats === null || !this.datatypesMapperReady) {
+ return this.listExtensions;
+ }
+ const effectiveExtensions = [];
+ this.listExtensions.forEach((extension) => {
+ if (extension && extension.id == "auto") {
+ effectiveExtensions.push(extension);
+ } else if (this.datatypesMapper.isSubTypeOfAny(extension.id, this.formats)) {
+ effectiveExtensions.push(extension);
+ }
+ });
+ return effectiveExtensions;
+ },
+ formatRestricted() {
+ return this.formats !== null;
+ },
+ showComposite() {
+ if (!this.formatRestricted) {
+ return true;
+ }
+ return this.effectiveExtensions.some((extension) => !!extension.composite_files);
+ },
+ showRegular() {
+ if (!this.formatRestricted) {
+ return true;
+ }
+ return this.effectiveExtensions.some((extension) => !extension.composite_files);
+ },
+ showCollection() {
+ if (this.unrestricted) {
+ return true;
+ }
+ return false;
+ },
+ showRules() {
+ if (this.unrestricted) {
+ return true;
+ }
+ return this.multiple;
+ },
+ },
+ mounted() {
+ this.id = String(this._uid);
},
methods: {
show() {
@@ -152,6 +228,21 @@ export default {
hide() {
this.modalShow = false;
},
+ cancel() {
+ this.hide();
+ this.$nextTick(() => {
+ this.$bvModal.hide(this.id, "cancel");
+ this.$destroy();
+ });
+ },
+ dismiss() {
+ // hide or cancel based on whether this is a singleton
+ if (this.callback) {
+ this.cancel();
+ } else {
+ this.hide();
+ }
+ },
initStateWhenHistoryReady() {
const Galaxy = getGalaxyInstance();
if (!Galaxy.currHistoryPanel || !Galaxy.currHistoryPanel.model) {
@@ -245,12 +336,4 @@ export default {
};
-
+
diff --git a/client/galaxy/scripts/components/Upload/index.js b/client/galaxy/scripts/components/Upload/index.js
index 2551d630223..a3db736fc69 100644
--- a/client/galaxy/scripts/components/Upload/index.js
+++ b/client/galaxy/scripts/components/Upload/index.js
@@ -4,3 +4,5 @@
export { default as UploadModal } from "./UploadModal";
export { initializeUploadDefaults } from "./config";
+
+export { mount as mountUploadModal } from "./mount";
diff --git a/client/galaxy/scripts/components/Upload/mount.js b/client/galaxy/scripts/components/Upload/mount.js
new file mode 100644
index 00000000000..b59db4150bc
--- /dev/null
+++ b/client/galaxy/scripts/components/Upload/mount.js
@@ -0,0 +1,13 @@
+import Vue from "vue";
+import UploadModal from "./UploadModal";
+import { initializeUploadDefaults } from "./config";
+
+export function mount(propsData = {}) {
+ propsData = initializeUploadDefaults(propsData);
+ const instance = Vue.extend(UploadModal);
+ const vm = document.createElement("div");
+ document.getElementsByTagName("body")[0].appendChild(vm);
+ new instance({
+ propsData: propsData,
+ }).$mount(vm);
+}
diff --git a/client/galaxy/scripts/components/Upload/test_helpers.js b/client/galaxy/scripts/components/Upload/test_helpers.js
index 776530bd548..4912c63c01d 100644
--- a/client/galaxy/scripts/components/Upload/test_helpers.js
+++ b/client/galaxy/scripts/components/Upload/test_helpers.js
@@ -10,7 +10,7 @@ export function mountWithApp(component, options = {}, propsData_ = {}) {
return "ftp://localhost";
},
model: new Backbone.Model(),
- listExtensions: [
+ effectiveExtensions: [
{ id: "ab1", text: "ab1", description: "A binary sequence file in 'ab1' format with a '.ab1'" },
{
id: "affybatch",
diff --git a/client/galaxy/scripts/mvc/ui/ui-select-content.js b/client/galaxy/scripts/mvc/ui/ui-select-content.js
index 0037ed539d7..ac3bddc5a27 100644
--- a/client/galaxy/scripts/mvc/ui/ui-select-content.js
+++ b/client/galaxy/scripts/mvc/ui/ui-select-content.js
@@ -272,8 +272,10 @@ const View = Backbone.View.extend({
});
if (cnf.src == "hda") {
this.button_dialog.show();
+ this.upload_dialog.show();
} else {
this.button_dialog.hide();
+ this.upload_dialog.hide();
}
this.button_type.value(i);
} else {
@@ -306,7 +308,8 @@ const View = Backbone.View.extend({
// prepare extension component of error message
const data = self.model.get("data");
- const extensions = Utils.textify(this.model.get("extensions"));
+ const formats = this.model.get("extensions");
+ const extensions = Utils.textify(formats);
const src_labels = this.model.get("src_labels");
// build radio button for data selectors
@@ -362,14 +365,38 @@ const View = Backbone.View.extend({
},
});
+ // build data dialog button
+ this.upload_dialog = new Ui.Button({
+ icon: "fa-upload",
+ tooltip: "Upload Dataset",
+ onclick: () => {
+ const current = this.model.get("current");
+ const cnf = this.config[current];
+ // model doesn't have format yet unfortunately, need to get through to here.
+ galaxy.data.dialog(
+ (response) => {
+ this._handleDropValues(response, false);
+ },
+ {
+ multiple: cnf.multiple,
+ formats: formats,
+ new: true,
+ }
+ );
+ },
+ });
+
// append views
const $fields = $("").addClass("overflow-auto w-100 py-2 py-lg-0 pr-lg-2 ");
this.$el
.empty()
.addClass("d-flex flex-row flex-wrap flex-lg-nowrap")
.append($("").append(this.button_type.$el))
- .append($fields)
- .append($("").append(this.button_dialog.$el));
+ .append($fields);
+ if (galaxy.config.enable_upload_from_form_button) {
+ this.$el.append($("").append(this.upload_dialog.$el));
+ }
+ this.$el.append($("").append(this.button_dialog.$el));
_.each(this.fields, (field) => {
$fields.append(field.$el);
});
diff --git a/client/galaxy/scripts/utils/data.js b/client/galaxy/scripts/utils/data.js
index 9c22eb20f1e..62acba5d56e 100644
--- a/client/galaxy/scripts/utils/data.js
+++ b/client/galaxy/scripts/utils/data.js
@@ -5,6 +5,7 @@ import DataDialog from "components/DataDialog/DataDialog.vue";
import { FilesDialog } from "components/FilesDialog";
import WorkflowDialog from "components/SelectionDialog/WorkflowDialog.vue";
import DatasetCollectionDialog from "components/SelectionDialog/DatasetCollectionDialog.vue";
+import { mountUploadModal } from "components/Upload";
import { getGalaxyInstance } from "app";
import { getAppRoot } from "onload/loadConfig";
@@ -44,7 +45,12 @@ export function dialog(callback, options = {}) {
root: galaxy.root,
host: host,
});
- _mountSelectionDialog(DataDialog, options);
+ if (options.new) {
+ options.modalShow = true;
+ mountUploadModal(options);
+ } else {
+ _mountSelectionDialog(DataDialog, options);
+ }
});
}
diff --git a/client/galaxy/scripts/utils/uploadbox.js b/client/galaxy/scripts/utils/uploadbox.js
index ce377b595c6..755f3719c67 100644
--- a/client/galaxy/scripts/utils/uploadbox.js
+++ b/client/galaxy/scripts/utils/uploadbox.js
@@ -300,6 +300,7 @@ import { getAppRoot } from "onload/loadConfig";
alert(m);
},
complete: () => {},
+ multiple: true,
},
options
);
@@ -320,7 +321,7 @@ import { getAppRoot } from "onload/loadConfig";
// element
var uploadinput = $(this).uploadinput({
- multiple: true,
+ multiple: opts.multiple,
onchange: (files) => {
_.each(files, (file) => {
file.chunk_mode = true;
diff --git a/client/galaxy/style/scss/base.scss b/client/galaxy/style/scss/base.scss
index a14fbf59619..761a10c09ea 100644
--- a/client/galaxy/style/scss/base.scss
+++ b/client/galaxy/style/scss/base.scss
@@ -316,6 +316,15 @@ body {
}
}
+.upload-dialog {
+ width: 900px;
+}
+
+.upload-dialog-body {
+ height: 500px;
+ overflow: hidden;
+}
+
// ==== Masthead ====
#masthead {
@extend .p-0;
diff --git a/lib/galaxy/managers/configuration.py b/lib/galaxy/managers/configuration.py
index 48aaf0120ab..8755e73ac68 100644
--- a/lib/galaxy/managers/configuration.py
+++ b/lib/galaxy/managers/configuration.py
@@ -105,6 +105,7 @@ class ConfigSerializer(base.ModelSerializer):
'python' : _defaults_to((sys.version_info.major, sys.version_info.minor)),
'select_type_workflow_threshold' : _use_config,
'file_sources_configured' : lambda config, key, **context: self.app.file_sources.custom_sources_configured,
+ 'enable_upload_from_form_button' : _use_config,
}
diff --git a/lib/galaxy/webapps/galaxy/config_schema.yml b/lib/galaxy/webapps/galaxy/config_schema.yml
index 8ee9e3d0496..96bfea9a282 100644
--- a/lib/galaxy/webapps/galaxy/config_schema.yml
+++ b/lib/galaxy/webapps/galaxy/config_schema.yml
@@ -2158,6 +2158,15 @@ mapping:
When using LDAP for authentication, allow administrators to pre-populate users
using an additional form on 'Create new user'
+ enable_upload_from_form_button:
+ type: bool
+ default: false
+ required: false
+ desc: |
+ If enabled, add another button to tool form data inputs that allow uploading
+ data from the tool form in fewer clicks (at the expense of making the form more
+ complicated).
+
allow_user_dataset_purge:
type: bool
default: true