mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge pull request #12031 from guerler/vueify_tool_form
Vueify Tool Form Wrapper
This commit is contained in:
@@ -81,6 +81,7 @@
|
||||
"vue-observe-visibility": "^1.0.0",
|
||||
"vue-router": "^3.3.1",
|
||||
"vue-rx": "^6.2.0",
|
||||
"vue-scrollto": "^2.20.0",
|
||||
"vuedraggable": "2.24.1",
|
||||
"vueisotope": "^3.1.2",
|
||||
"vuex": "^3.4.0",
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<b-button v-if="wait" variant="info" disabled>
|
||||
<font-awesome-icon icon="spinner" class="mr-2" spin />Please wait...
|
||||
</b-button>
|
||||
<b-button v-else variant="primary" v-b-tooltip.hover.bottom :title="tooltip" @click="$emit('onClick')">
|
||||
<font-awesome-icon icon="check" class="mr-2" />{{ title }}
|
||||
</b-button>
|
||||
</template>
|
||||
<script>
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faCheck, faSpinner } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
library.add(faCheck);
|
||||
library.add(faSpinner);
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FontAwesomeIcon,
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
wait: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
tooltip: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div ref="webhook" id="webhook-view" />
|
||||
</template>
|
||||
<script>
|
||||
import Webhooks from "mvc/webhooks";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
toolId: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const el = this.$refs["webhook"];
|
||||
new Webhooks.WebhookView({
|
||||
el,
|
||||
type: this.type,
|
||||
toolId: this.toolId,
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,32 @@
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { getLocalVue } from "jest/helpers";
|
||||
import FormBoolean from "./FormBoolean";
|
||||
import Vue from "vue";
|
||||
|
||||
const localVue = getLocalVue();
|
||||
|
||||
describe("FormBoolean", () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = mount(FormBoolean, {
|
||||
propsData: {
|
||||
value: false,
|
||||
},
|
||||
localVue,
|
||||
});
|
||||
});
|
||||
|
||||
it("check initial value and value change", async () => {
|
||||
const input = wrapper.find("input");
|
||||
expect(wrapper.vm.currentValue).toBe(false);
|
||||
wrapper.setProps({ value: true });
|
||||
await Vue.nextTick();
|
||||
expect(wrapper.vm.currentValue).toBe(true);
|
||||
input.trigger("click");
|
||||
expect(input.element.checked).toBe(false);
|
||||
input.trigger("click");
|
||||
expect(input.element.checked).toBe(true);
|
||||
expect(wrapper.emitted().input[0][0]).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<b-form-checkbox v-model="currentValue" size="lg" switch />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
currentValue: {
|
||||
get() {
|
||||
return this.value;
|
||||
},
|
||||
set(val) {
|
||||
this.$emit("input", val);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
+2
-2
@@ -24,7 +24,7 @@ describe("FormInput", () => {
|
||||
expect(input.element.value).toBe("initial_value");
|
||||
await input.setValue("new_value");
|
||||
expect(input.element.value).toBe("new_value");
|
||||
expect(wrapper.emitted().onChange[0][0]).toBe("new_value");
|
||||
expect(wrapper.emitted().input[0][0]).toBe("new_value");
|
||||
});
|
||||
|
||||
it("check switching to text area", async () => {
|
||||
@@ -33,6 +33,6 @@ describe("FormInput", () => {
|
||||
expect(input.element.value).toBe("initial_value");
|
||||
await input.setValue("new_value");
|
||||
expect(input.element.value).toBe("new_value");
|
||||
expect(wrapper.emitted().onChange[0][0]).toBe("new_value");
|
||||
expect(wrapper.emitted().input[0][0]).toBe("new_value");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<textarea v-if="area" class="ui-input" v-model="currentValue" />
|
||||
<input v-else class="ui-input" v-model="currentValue" />
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
area: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
currentValue: {
|
||||
get() {
|
||||
return this.value;
|
||||
},
|
||||
set(val) {
|
||||
this.$emit("input", val);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,68 +0,0 @@
|
||||
<template>
|
||||
<div ref="form" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Form from "mvc/form/form-view";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
inputs: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
textEnable: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
textDisable: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
initialErrors: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
id() {
|
||||
this.onRender();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.onRender();
|
||||
},
|
||||
methods: {
|
||||
onChange() {
|
||||
this.$emit("onChange", this.form.data.create());
|
||||
},
|
||||
onRender() {
|
||||
this.$nextTick(() => {
|
||||
const el = this.$refs["form"];
|
||||
this.form = new Form({
|
||||
el,
|
||||
inputs: this.inputs,
|
||||
initial_errors: this.initialErrors,
|
||||
text_enable: this.textEnable,
|
||||
text_disable: this.textDisable,
|
||||
}).on("change", this.onChange);
|
||||
this.onChange();
|
||||
});
|
||||
},
|
||||
parseUpdate(data) {
|
||||
this.$nextTick(() => {
|
||||
this.form.update(data, false);
|
||||
});
|
||||
},
|
||||
parseErrors(data) {
|
||||
this.$nextTick(() => {
|
||||
this.form.errors(data);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,29 @@
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { getLocalVue } from "jest/helpers";
|
||||
import FormCard from "./FormCard";
|
||||
|
||||
const localVue = getLocalVue();
|
||||
|
||||
describe("FormCard", () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = mount(FormCard, {
|
||||
propsData: {
|
||||
title: "title",
|
||||
description: "description",
|
||||
icon: "icon-class",
|
||||
},
|
||||
localVue,
|
||||
});
|
||||
});
|
||||
|
||||
it("check props", async () => {
|
||||
const title = wrapper.find(".portlet-title-text > b");
|
||||
expect(title.text()).toBe("title");
|
||||
const description = wrapper.find(".portlet-title-text > span");
|
||||
expect(description.text()).toBe("description");
|
||||
const icon = wrapper.find(".portlet-title-icon");
|
||||
expect(icon.classes()).toContain("icon-class");
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,6 @@
|
||||
<i :class="['portlet-title-icon fa mr-1', icon]" style="display: inline"></i>
|
||||
<span class="portlet-title-text">
|
||||
<b itemprop="name">{{ title }}</b> <span itemprop="description">{{ description }}</span>
|
||||
{{ version }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -26,11 +25,7 @@ export default {
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
version: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<div ref="form" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Form from "mvc/form/form-view";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
inputs: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
textEnable: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
textDisable: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
initialErrors: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
validationScrollTo: {
|
||||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
validationErrors: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
formConfig: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formData: {},
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
id() {
|
||||
this.onRender();
|
||||
},
|
||||
validationScrollTo() {
|
||||
this.onHighlight(this.validationScrollTo);
|
||||
},
|
||||
validation() {
|
||||
this.onHighlight(this.validation, true);
|
||||
this.$emit("onValidation", this.validation);
|
||||
},
|
||||
validationErrors() {
|
||||
const matchedErrors = Object.entries(this.form.data.matchResponse(this.validationErrors));
|
||||
if (matchedErrors.length > 0) {
|
||||
this.onHighlight(matchedErrors[0]);
|
||||
}
|
||||
},
|
||||
formConfig() {
|
||||
this.$nextTick(() => {
|
||||
this.form.update(this.formConfig);
|
||||
if (this.initialErrors) {
|
||||
this.form.errors(this.formConfig);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.onRender();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.form.off();
|
||||
},
|
||||
computed: {
|
||||
validation() {
|
||||
let batch_n = -1;
|
||||
let batch_src = null;
|
||||
for (const job_input_id in this.formData) {
|
||||
const input_value = this.formData[job_input_id];
|
||||
const input_id = this.form.data.match(job_input_id);
|
||||
const input_field = this.form.field_list[input_id];
|
||||
const input_def = this.form.input_list[input_id];
|
||||
if (!input_id || !input_def || !input_field) {
|
||||
continue;
|
||||
}
|
||||
if (!input_def.optional && input_value == null && input_def.type != "hidden") {
|
||||
return [input_id, "Please provide a value for this option."];
|
||||
}
|
||||
if (input_field.validate) {
|
||||
const message = input_field.validate();
|
||||
if (message) {
|
||||
return [input_id, message];
|
||||
}
|
||||
}
|
||||
if (input_value && input_value.batch) {
|
||||
const n = input_value.values.length;
|
||||
const src = n > 0 && input_value.values[0] && input_value.values[0].src;
|
||||
if (src) {
|
||||
if (batch_src === null) {
|
||||
batch_src = src;
|
||||
} else if (batch_src !== src) {
|
||||
return [
|
||||
input_id,
|
||||
"Please select either dataset or dataset list fields for all batch mode fields.",
|
||||
];
|
||||
}
|
||||
}
|
||||
if (batch_n === -1) {
|
||||
batch_n = n;
|
||||
} else if (batch_n !== n) {
|
||||
return [
|
||||
input_id,
|
||||
`Please make sure that you select the same number of inputs for all batch mode fields. This field contains <b>${n}</b> selection(s) while a previous field contains <b>${batch_n}</b>.`,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onChange() {
|
||||
this.formData = this.form.data.create();
|
||||
this.$emit("onChange", this.formData);
|
||||
},
|
||||
onRender() {
|
||||
this.$nextTick(() => {
|
||||
const el = this.$refs["form"];
|
||||
this.form = new Form({
|
||||
el,
|
||||
inputs: this.inputs,
|
||||
initial_errors: this.initialErrors,
|
||||
text_enable: this.textEnable,
|
||||
text_disable: this.textDisable,
|
||||
onchange: () => {
|
||||
this.onChange();
|
||||
},
|
||||
});
|
||||
this.onChange();
|
||||
});
|
||||
},
|
||||
onHighlight(validation, silent = false) {
|
||||
this.form.trigger("reset");
|
||||
if (validation && validation.length == 2) {
|
||||
this.form.highlight(validation[0], validation[1], silent);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -22,7 +22,7 @@ describe("FormElement", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("check help text", async () => {
|
||||
it("check props", async () => {
|
||||
const help = wrapper.find(".ui-form-info");
|
||||
expect(help.text()).toBe("help_text");
|
||||
const error = wrapper.find(".ui-form-error-text");
|
||||
|
||||
@@ -6,16 +6,19 @@
|
||||
</div>
|
||||
<div class="ui-form-title">{{ title }}</div>
|
||||
<div class="ui-form-field">
|
||||
<FormInput :id="id" :value="value" :area="area" @onChange="onChange" />
|
||||
<FormInput v-if="type == 'text'" :id="id" :area="area" v-model="currentValue" />
|
||||
<FormBoolean v-else-if="type == 'boolean'" :id="id" v-model="currentValue" />
|
||||
<span class="ui-form-info form-text text-muted mt-2">{{ help }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import FormInput from "./FormInput";
|
||||
import FormBoolean from "./Elements/FormBoolean";
|
||||
import FormInput from "./Elements/FormInput";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FormBoolean,
|
||||
FormInput,
|
||||
},
|
||||
props: {
|
||||
@@ -24,16 +27,16 @@ export default {
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: "",
|
||||
type: [String, Boolean],
|
||||
default: null,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
default: null,
|
||||
},
|
||||
help: {
|
||||
type: String,
|
||||
default: "",
|
||||
default: null,
|
||||
},
|
||||
area: {
|
||||
type: Boolean,
|
||||
@@ -43,8 +46,20 @@ export default {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: "text",
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
currentValue: {
|
||||
get() {
|
||||
return this.value;
|
||||
},
|
||||
set(val) {
|
||||
this.$emit("input", val);
|
||||
},
|
||||
},
|
||||
hasError() {
|
||||
return !!this.error;
|
||||
},
|
||||
@@ -56,10 +71,5 @@ export default {
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onChange(value) {
|
||||
this.$emit("onChange", value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
<template>
|
||||
<textarea v-if="area" class="ui-input" v-model="currentValue" @input="onChange" @change="onChange" />
|
||||
<input v-else class="ui-input" v-model="currentValue" @input="onChange" @change="onChange" />
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
area: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value(newValue) {
|
||||
this.currentValue = newValue;
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentValue: this.value,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onChange() {
|
||||
this.$emit("onChange", this.currentValue);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -19,7 +19,7 @@
|
||||
@ok="onOk"
|
||||
@cancel="onCancel"
|
||||
>
|
||||
<Form :inputs="formInputs" @onChange="onChange" />
|
||||
<FormDisplay :inputs="formInputs" @onChange="onChange" />
|
||||
</b-modal>
|
||||
</span>
|
||||
</template>
|
||||
@@ -29,7 +29,7 @@ import Vue from "vue";
|
||||
import BootstrapVue from "bootstrap-vue";
|
||||
import MarkdownSelector from "./MarkdownSelector";
|
||||
import DataDialog from "components/DataDialog/DataDialog";
|
||||
import Form from "components/Form/Form";
|
||||
import FormDisplay from "components/Form/FormDisplay";
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
|
||||
@@ -37,7 +37,7 @@ export default {
|
||||
components: {
|
||||
MarkdownSelector,
|
||||
DataDialog,
|
||||
Form,
|
||||
FormDisplay,
|
||||
},
|
||||
props: {
|
||||
argumentName: {
|
||||
|
||||
@@ -28,10 +28,10 @@
|
||||
<div class="toolMenuContainer">
|
||||
<div class="toolMenu">
|
||||
<tool-section
|
||||
v-for="section in sections"
|
||||
v-for="(section, key) in sections"
|
||||
:category="section"
|
||||
:query-filter="queryFilter"
|
||||
:key="section.id"
|
||||
:key="key"
|
||||
@onClick="onOpen"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { getLocalVue } from "jest/helpers";
|
||||
import ToolCard from "./ToolCard";
|
||||
|
||||
const localVue = getLocalVue();
|
||||
|
||||
describe("ToolCard", () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = mount(ToolCard, {
|
||||
propsData: {
|
||||
id: "identifier",
|
||||
user: {
|
||||
id: "user.id",
|
||||
email: "user.email",
|
||||
is_admin: true,
|
||||
preferences: {},
|
||||
},
|
||||
version: "version",
|
||||
title: "title",
|
||||
description: "description",
|
||||
sustainVersion: false,
|
||||
options: {
|
||||
id: "options.id",
|
||||
versions: [],
|
||||
sharable_url: "options.sharable_url",
|
||||
help: "options.help",
|
||||
citations: null,
|
||||
},
|
||||
messageText: "messageText",
|
||||
messageVariant: "warning",
|
||||
},
|
||||
localVue,
|
||||
});
|
||||
});
|
||||
|
||||
it("check props", async () => {
|
||||
const title = wrapper.find(".portlet-title-text > b");
|
||||
expect(title.text()).toBe("title");
|
||||
const description = wrapper.find(".portlet-title-text > span");
|
||||
expect(description.text()).toBe("description");
|
||||
const icon = wrapper.find(".portlet-title-icon");
|
||||
expect(icon.classes()).toContain("fa-wrench");
|
||||
const dropdownHeader = wrapper.find(".tool-dropdown");
|
||||
expect(dropdownHeader.attributes("title")).toBe("Options");
|
||||
const dropdownItems = wrapper.findAll(".dropdown-item");
|
||||
expect(dropdownItems.length).toBe(4);
|
||||
});
|
||||
});
|
||||
+21
-16
@@ -10,26 +10,30 @@
|
||||
title="Options"
|
||||
variant="link"
|
||||
aria-label="View all Options"
|
||||
class="float-right"
|
||||
class="tool-dropdown float-right"
|
||||
size="sm"
|
||||
v-b-tooltip.hover
|
||||
>
|
||||
<template v-slot:button-content>
|
||||
<span class="fa fa-caret-down" />
|
||||
</template>
|
||||
<b-dropdown-item @click="onCopyLink"> <span class="fa fa-chain" /> Copy Link </b-dropdown-item>
|
||||
<b-dropdown-item @click="onCopyId">
|
||||
<span class="fa fa-files-o" /> Copy Tool ID
|
||||
<b-dropdown-item @click="onCopyLink"
|
||||
><span class="fa fa-chain" /><span v-localize>Copy Link</span>
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item v-if="showDownload" @click="onDownload">
|
||||
<span class="fa fa-download" /> Download
|
||||
<b-dropdown-item @click="onCopyId"
|
||||
><span class="fa fa-files-o" /><span v-localize>Copy Tool ID</span>
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item v-if="showLink" @click="onLink">
|
||||
<span class="fa fa-external-link" /> See in Tool Shed
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item v-for="w of webhookDetails" :key="w.title" @click="w.onclick">
|
||||
<span :class="w.icon" /> {{ w.title }}
|
||||
<b-dropdown-item v-if="showDownload" @click="onDownload"
|
||||
><span class="fa fa-download" /><span v-localize>Download</span>
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item v-if="showLink" @click="onLink"
|
||||
><span class="fa fa-external-link" /><span v-localize
|
||||
>See in Tool Shed</span
|
||||
></b-dropdown-item
|
||||
>
|
||||
<b-dropdown-item v-for="w of webhookDetails" :key="w.title" @click="w.onclick"
|
||||
><span :class="w.icon" />{{ w.title | l }}</b-dropdown-item
|
||||
>
|
||||
</b-dropdown>
|
||||
<b-dropdown
|
||||
v-if="showVersions"
|
||||
@@ -47,8 +51,8 @@
|
||||
<span class="fa fa-cubes" />
|
||||
</template>
|
||||
<b-dropdown-item v-for="v of availableVersions" :key="v" @click="$emit('onChangeVersion', v)">
|
||||
<span class="fa fa-cube" /> Switch to {{ v }}
|
||||
</b-dropdown-item>
|
||||
<span class="fa fa-cube" /><span v-localize>Switch to </span>{{ v }}</b-dropdown-item
|
||||
>
|
||||
</b-dropdown>
|
||||
<b-button
|
||||
v-if="showAddFavorite"
|
||||
@@ -79,7 +83,7 @@
|
||||
<i class="portlet-title-icon fa mr-1 fa-wrench" style="display: inline"></i>
|
||||
<span class="portlet-title-text">
|
||||
<b itemprop="name">{{ title }}</b> <span itemprop="description">{{ description }}</span> (Galaxy
|
||||
Verson {{ version }})
|
||||
Version {{ version }})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -89,6 +93,7 @@
|
||||
<slot name="body" />
|
||||
</div>
|
||||
</div>
|
||||
<slot name="buttons" />
|
||||
<div class="m-1">
|
||||
<ToolHelp :content="options.help" />
|
||||
<ToolFooter
|
||||
@@ -105,7 +110,7 @@
|
||||
<script>
|
||||
import ariaAlert from "utils/ariaAlert";
|
||||
import { copyLink, downloadTool, openLink } from "./utilities";
|
||||
import FormMessage from "./FormMessage";
|
||||
import FormMessage from "components/Form/FormMessage";
|
||||
import ToolFooter from "components/Tool/ToolFooter";
|
||||
import ToolHelp from "components/Tool/ToolHelp";
|
||||
import Webhooks from "mvc/webhooks";
|
||||
@@ -205,7 +210,7 @@ export default {
|
||||
this.webhookDetails.push({
|
||||
icon: `fa ${webhook.config.icon}`,
|
||||
title: webhook.config.title,
|
||||
onclick: function () {
|
||||
onclick: () => {
|
||||
const func = new Function("options", webhook.config.function);
|
||||
func(this.options);
|
||||
},
|
||||
@@ -76,9 +76,9 @@ import { faQuestion, faCopy, faAngleDoubleDown, faAngleDoubleUp } from "@fortawe
|
||||
library.add(faQuestion, faCopy, faAngleDoubleDown, faAngleDoubleUp);
|
||||
|
||||
import { getCitations } from "components/Citation/services";
|
||||
import Citation from "components/Citation/Citation.vue";
|
||||
import License from "components/License/License.vue";
|
||||
import Creators from "components/SchemaOrg/Creators.vue";
|
||||
import Citation from "components/Citation/Citation";
|
||||
import License from "components/License/License";
|
||||
import Creators from "components/SchemaOrg/Creators";
|
||||
import { copy } from "utils/clipboard";
|
||||
|
||||
export default {
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
import axios from "axios";
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { getLocalVue } from "jest/helpers";
|
||||
import ToolForm from "./ToolForm";
|
||||
import MockCurrentUser from "../providers/MockCurrentUser";
|
||||
import MockConfigProvider from "../providers/MockConfigProvider";
|
||||
import MockCurrentHistory from "../History/providers/UserHistories/MockCurrentHistory";
|
||||
import Vue from "vue";
|
||||
|
||||
const localVue = getLocalVue();
|
||||
|
||||
describe("ToolForm", () => {
|
||||
let wrapper;
|
||||
let axiosMock;
|
||||
|
||||
beforeEach(() => {
|
||||
axiosMock = new MockAdapter(axios);
|
||||
|
||||
const toolData = {
|
||||
id: "tool_id",
|
||||
name: "tool_name",
|
||||
version: "version",
|
||||
inputs: [],
|
||||
help: "help_text",
|
||||
};
|
||||
axiosMock.onGet(`/api/tools/tool_id/build?tool_version=version`).reply(200, toolData);
|
||||
|
||||
const citations = [];
|
||||
axiosMock.onGet(`/api/tools/tool_id/citations`).reply(200, citations);
|
||||
|
||||
wrapper = mount(ToolForm, {
|
||||
propsData: {
|
||||
id: "tool_id",
|
||||
version: "version",
|
||||
},
|
||||
localVue,
|
||||
stubs: {
|
||||
CurrentUser: MockCurrentUser({ id: "fakeuser" }),
|
||||
UserHistories: MockCurrentHistory({ id: "fakehistory" }),
|
||||
ConfigProvider: MockConfigProvider({ id: "fakeconfig" }),
|
||||
FormDisplay: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
axiosMock.restore();
|
||||
axiosMock.reset();
|
||||
});
|
||||
|
||||
it("check props", async () => {
|
||||
await Vue.nextTick();
|
||||
const button = wrapper.find(".btn-primary");
|
||||
expect(button.attributes("title")).toBe("Execute: tool_name (version)");
|
||||
const dropdown = wrapper.findAll(".dropdown-item");
|
||||
expect(dropdown.length).toBe(2);
|
||||
const help = wrapper.find(".form-help");
|
||||
expect(help.text()).toBe("help_text");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,316 @@
|
||||
<template>
|
||||
<ConfigProvider v-slot="{ config }">
|
||||
<CurrentUser v-slot="{ user }">
|
||||
<UserHistories v-if="user" :user="user" v-slot="{ currentHistory }">
|
||||
<div>
|
||||
<LoadingSpan v-if="showLoading" message="Loading Tool" />
|
||||
<div v-if="showEntryPoints">
|
||||
<ToolEntryPoints v-for="job in entryPoints" :job-id="job.id" :key="job.id" />
|
||||
</div>
|
||||
<ToolSuccess
|
||||
v-if="showSuccess"
|
||||
:job-def="jobDef"
|
||||
:job-response="jobResponse"
|
||||
:tool-name="toolName"
|
||||
/>
|
||||
<Webhook v-if="showSuccess" type="tool" :tool-id="jobDef.tool_id" />
|
||||
<b-alert v-if="showError" show variant="danger">
|
||||
<h4>{{ errorTitle | l }}</h4>
|
||||
<p>
|
||||
The server could not complete the request. Please contact the Galaxy Team if this error
|
||||
persists.
|
||||
</p>
|
||||
<pre>{{ errorContentPretty }}</pre>
|
||||
</b-alert>
|
||||
<ToolRecommendation v-if="showRecommendation" :tool-id="formConfig.id" />
|
||||
<ToolCard
|
||||
v-if="showForm"
|
||||
:id="formConfig.id"
|
||||
:user="user"
|
||||
:version="formConfig.version"
|
||||
:title="formConfig.name"
|
||||
:description="formConfig.description"
|
||||
:options="formConfig"
|
||||
:message-text="messageText"
|
||||
:message-variant="messageVariant"
|
||||
@onChangeVersion="onChangeVersion"
|
||||
@onUpdateFavorites="onUpdateFavorites"
|
||||
itemscope="itemscope"
|
||||
itemtype="https://schema.org/CreativeWork"
|
||||
>
|
||||
<template v-slot:body>
|
||||
<FormDisplay
|
||||
:id="formConfig.id"
|
||||
:inputs="inputs"
|
||||
:validation-errors="validationErrors"
|
||||
:validation-scroll-to="validationScrollTo"
|
||||
:form-config="formConfig"
|
||||
@onChange="onChange"
|
||||
@onValidation="onValidation"
|
||||
/>
|
||||
<FormElement
|
||||
v-if="emailAllowed(config, user)"
|
||||
id="send_email_notification"
|
||||
title="Email notification"
|
||||
help="Send an email notification when the job completes."
|
||||
type="boolean"
|
||||
v-model="email"
|
||||
/>
|
||||
<FormElement
|
||||
v-if="remapAllowed"
|
||||
id="rerun_remap_job_id"
|
||||
:title="remapTitle"
|
||||
:help="remapHelp"
|
||||
type="boolean"
|
||||
v-model="remap"
|
||||
/>
|
||||
<FormElement
|
||||
v-if="reuseAllowed(user)"
|
||||
id="use_cached_job"
|
||||
title="Attempt to re-use jobs with identical parameters?"
|
||||
help="This may skip executing jobs that you have already run."
|
||||
type="boolean"
|
||||
v-model="reuse"
|
||||
/>
|
||||
</template>
|
||||
<template v-slot:buttons>
|
||||
<ButtonSpinner
|
||||
id="execute"
|
||||
title="Execute"
|
||||
@onClick="onExecute(config, currentHistory.id)"
|
||||
:wait="showExecuting"
|
||||
:tooltip="tooltip"
|
||||
/>
|
||||
</template>
|
||||
</ToolCard>
|
||||
</div>
|
||||
</UserHistories>
|
||||
</CurrentUser>
|
||||
</ConfigProvider>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Scroller from "vue-scrollto";
|
||||
import { getGalaxyInstance } from "app";
|
||||
import { getToolFormData, updateToolFormData, submitJob } from "./services";
|
||||
import ToolCard from "./ToolCard";
|
||||
import ButtonSpinner from "components/Common/ButtonSpinner";
|
||||
import CurrentUser from "components/providers/CurrentUser";
|
||||
import ConfigProvider from "components/providers/ConfigProvider";
|
||||
import LoadingSpan from "components/LoadingSpan";
|
||||
import FormDisplay from "components/Form/FormDisplay";
|
||||
import FormElement from "components/Form/FormElement";
|
||||
import ToolSuccess from "./ToolSuccess";
|
||||
import UserHistories from "components/History/providers/UserHistories";
|
||||
import Webhook from "components/Common/Webhook";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ButtonSpinner,
|
||||
CurrentUser,
|
||||
ConfigProvider,
|
||||
LoadingSpan,
|
||||
FormDisplay,
|
||||
ToolCard,
|
||||
FormElement,
|
||||
ToolSuccess,
|
||||
UserHistories,
|
||||
Webhook,
|
||||
},
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
version: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
job_id: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
history_id: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showLoading: true,
|
||||
showForm: false,
|
||||
showEntryPoints: false,
|
||||
showRecommendation: false,
|
||||
showSuccess: false,
|
||||
showError: false,
|
||||
showExecuting: false,
|
||||
error: null,
|
||||
formConfig: {},
|
||||
formData: {},
|
||||
errorTitle: null,
|
||||
errorContent: null,
|
||||
messageVariant: "",
|
||||
messageText: "",
|
||||
reuse: false,
|
||||
email: false,
|
||||
remap: false,
|
||||
entryPoints: [],
|
||||
jobDef: {},
|
||||
jobResponse: {},
|
||||
validationInternal: null,
|
||||
validationScrollTo: null,
|
||||
validationErrors: null,
|
||||
currentVersion: this.version,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.requestTool();
|
||||
const Galaxy = getGalaxyInstance();
|
||||
if (Galaxy && Galaxy.currHistoryPanel) {
|
||||
Galaxy.currHistoryPanel.collection.on("change", () => {
|
||||
this.onUpdate();
|
||||
});
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
toolName() {
|
||||
return this.formConfig.name;
|
||||
},
|
||||
tooltip() {
|
||||
return `Execute: ${this.formConfig.name} (${this.formConfig.version})`;
|
||||
},
|
||||
errorContentPretty() {
|
||||
return JSON.stringify(this.errorContent, null, 4);
|
||||
},
|
||||
inputs() {
|
||||
return this.formConfig.inputs;
|
||||
},
|
||||
remapAllowed() {
|
||||
return this.job_id && this.formConfig.job_remap;
|
||||
},
|
||||
remapTitle() {
|
||||
if (this.formConfig.job_remap === "job_produced_collection_elements") {
|
||||
return "Replace elements in collection ?";
|
||||
} else {
|
||||
return "Resume dependencies from this job ?";
|
||||
}
|
||||
},
|
||||
remapHelp() {
|
||||
if (this.formConfig.job_remap === "job_produced_collection_elements") {
|
||||
return "The previous run of this tool failed. Use this option to replace the failed element(s) in the dataset collection that were produced during the previous tool run.";
|
||||
} else {
|
||||
return "The previous run of this tool failed and other tools were waiting for it to finish successfully. Use this option to resume those tools using the new output(s) of this tool run.";
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
emailAllowed(config, user) {
|
||||
return config.server_mail_configured && !user.isAnonymous;
|
||||
},
|
||||
reuseAllowed(user) {
|
||||
if (user.preferences && "extra_user_preferences" in user.preferences) {
|
||||
const extra_user_preferences = JSON.parse(user.preferences.extra_user_preferences);
|
||||
const keyCached = "use_cached_job|use_cached_job_checkbox";
|
||||
const hasCachedJobs = keyCached in extra_user_preferences;
|
||||
return hasCachedJobs ? extra_user_preferences[keyCached] : false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
onValidation(validationInternal) {
|
||||
this.validationInternal = validationInternal;
|
||||
},
|
||||
onChange(newData) {
|
||||
this.formData = newData;
|
||||
this.onUpdate();
|
||||
},
|
||||
onUpdate() {
|
||||
updateToolFormData(this.formConfig.id, this.currentVersion, this.formData).then((data) => {
|
||||
this.formConfig = data;
|
||||
});
|
||||
},
|
||||
onChangeVersion(newVersion) {
|
||||
this.requestTool(newVersion);
|
||||
},
|
||||
onUpdateFavorites(user, newFavorites) {
|
||||
user.preferences["favorites"] = newFavorites;
|
||||
},
|
||||
requestTool(newVersion) {
|
||||
this.currentVersion = newVersion || this.currentVersion;
|
||||
getToolFormData(this.id, this.currentVersion, this.job_id, this.history_id).then((data) => {
|
||||
this.formConfig = data;
|
||||
this.showLoading = false;
|
||||
this.showForm = true;
|
||||
if (newVersion) {
|
||||
this.messageVariant = "success";
|
||||
this.messageText = `Now you are using '${data.name}' version ${data.version}, id '${data.id}'.`;
|
||||
}
|
||||
});
|
||||
},
|
||||
onExecute(config, historyId) {
|
||||
if (this.validationInternal) {
|
||||
this.validationScrollTo = this.validationInternal.slice();
|
||||
return;
|
||||
}
|
||||
this.showExecuting = true;
|
||||
const Galaxy = getGalaxyInstance();
|
||||
const jobDef = {
|
||||
history_id: historyId,
|
||||
tool_id: this.formConfig.id,
|
||||
tool_version: this.formConfig.version,
|
||||
inputs: {
|
||||
...this.formData,
|
||||
},
|
||||
};
|
||||
if (this.email) {
|
||||
jobDef.inputs["send_email_notification"] = true;
|
||||
}
|
||||
if (this.remap) {
|
||||
jobDef.inputs["rerun_remap_job_id"] = this.job_id;
|
||||
}
|
||||
if (this.reuse) {
|
||||
jobDef.inputs["use_cached_job"] = true;
|
||||
}
|
||||
console.debug("toolForm::onExecute()", jobDef);
|
||||
submitJob(jobDef).then(
|
||||
(jobResponse) => {
|
||||
if (Galaxy.currHistoryPanel) {
|
||||
Galaxy.currHistoryPanel.refreshContents();
|
||||
}
|
||||
this.showForm = false;
|
||||
if (jobResponse.produces_entry_points) {
|
||||
this.showEntryPoints = true;
|
||||
this.entryPoints = jobResponse.jobs;
|
||||
}
|
||||
const nJobs = jobResponse && jobResponse.jobs ? jobResponse.jobs.length : 0;
|
||||
if (nJobs > 0) {
|
||||
this.showSuccess = true;
|
||||
this.jobDef = jobDef;
|
||||
this.jobResponse = jobResponse;
|
||||
} else {
|
||||
this.showError = true;
|
||||
this.errorTitle = "Invalid success response. No jobs found.";
|
||||
this.errorContent = jobResponse;
|
||||
}
|
||||
if ([true, "true"].includes(config.enable_tool_recommendations)) {
|
||||
this.showRecommendation = true;
|
||||
}
|
||||
Scroller.scrollTo("body");
|
||||
},
|
||||
(e) => {
|
||||
this.showExecuting = false;
|
||||
const errorData = e && e.response && e.response.data && e.response.data.err_data;
|
||||
if (errorData) {
|
||||
this.validationErrors = errorData;
|
||||
} else {
|
||||
this.showError = true;
|
||||
this.showForm = false;
|
||||
this.errorTitle = "Job submission failed";
|
||||
this.errorContent = this.jobDef;
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div class="donemessagelarge">
|
||||
<p>
|
||||
Executed <b>{{ toolName }}</b> and successfully added {{ nJobsText }} to the queue.
|
||||
</p>
|
||||
<p>The tool uses {{ nInputsText }}:</p>
|
||||
<ul>
|
||||
<li v-for="item of inputs" :key="item.hid">
|
||||
<b>{{ item.hid }}: {{ item.name }}</b>
|
||||
</li>
|
||||
</ul>
|
||||
<p>It produces {{ nOutputsText }}:</p>
|
||||
<ul>
|
||||
<li v-for="item of jobResponse.outputs" :key="item.hid">
|
||||
<b>{{ item.hid }}: {{ item.name }}</b>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
You can check the status of queued jobs and view the resulting data by refreshing the History panel. When
|
||||
the job has been run the status will change from 'running' to 'finished' if completed successfully or
|
||||
'error' if problems were encountered.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
jobDef: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
jobResponse: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
toolName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
inputs() {
|
||||
const inputs = [];
|
||||
const index = {};
|
||||
for (const i in this.jobDef.inputs) {
|
||||
const input = this.jobDef.inputs[i];
|
||||
if (input && Array.isArray(input.values)) {
|
||||
for (const j of input.values) {
|
||||
if (j.src && !index[j.id]) {
|
||||
inputs.push(j);
|
||||
index[j.id] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return inputs;
|
||||
},
|
||||
nInputs() {
|
||||
return this.inputs.length;
|
||||
},
|
||||
nOutputs() {
|
||||
return this.jobResponse && this.jobResponse.outputs ? this.jobResponse.outputs.length : 0;
|
||||
},
|
||||
nJobs() {
|
||||
return this.jobResponse && this.jobResponse.jobs ? this.jobResponse.jobs.length : 0;
|
||||
},
|
||||
nJobsText() {
|
||||
return this.nJobs > 1 ? `${this.njobs} jobs` : `1 job`;
|
||||
},
|
||||
nInputsText() {
|
||||
return this.nInputs > 1 ? `${this.nInputs} inputs` : `this input`;
|
||||
},
|
||||
nOutputsText() {
|
||||
return this.nOutputs > 1 ? `${this.nOutputs} outputs` : `this output`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -2,6 +2,63 @@ import axios from "axios";
|
||||
import { rethrowSimple } from "utils/simple-error";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
|
||||
export async function updateToolFormData(tool_id, tool_version, inputs) {
|
||||
const current_state = {
|
||||
tool_id: tool_id,
|
||||
tool_version: tool_version,
|
||||
inputs: inputs,
|
||||
};
|
||||
const url = `${getAppRoot()}api/tools/${tool_id}/build`;
|
||||
try {
|
||||
const { data } = await axios.post(url, current_state);
|
||||
return data;
|
||||
} catch (e) {
|
||||
rethrowSimple(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Tools data request helper **/
|
||||
export async function getToolFormData(tool_id, tool_version, job_id, history_id) {
|
||||
let url = "";
|
||||
const data = {};
|
||||
|
||||
// build request url and collect request data
|
||||
if (job_id) {
|
||||
url = `${getAppRoot()}api/jobs/${job_id}/build_for_rerun`;
|
||||
} else {
|
||||
url = `${getAppRoot()}api/tools/${tool_id}/build`;
|
||||
const queryString = window.location.search;
|
||||
const params = new URLSearchParams(queryString);
|
||||
for (const [key, value] of params.entries()) {
|
||||
if (key != "tool_id") {
|
||||
data[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
history_id && (data["history_id"] = history_id);
|
||||
tool_version && (data["tool_version"] = tool_version);
|
||||
|
||||
// attach data to request url
|
||||
if (Object.entries(data).length != 0) {
|
||||
const params = new URLSearchParams(data);
|
||||
url = `${url}?${params.toString()}`;
|
||||
}
|
||||
|
||||
// request tool data
|
||||
try {
|
||||
const { data } = await axios.get(url);
|
||||
return data;
|
||||
} catch (e) {
|
||||
rethrowSimple(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function submitJob(jobDetails) {
|
||||
const url = `${getAppRoot()}api/tools`;
|
||||
const { data } = await axios.post(url, jobDetails);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function addFavorite(userId, toolId) {
|
||||
const url = `${getAppRoot()}api/users/${userId}/favorites/tools`;
|
||||
try {
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
:value="node.label"
|
||||
title="Label"
|
||||
help="Add a step label."
|
||||
@onChange="onLabel"
|
||||
@input="onLabel"
|
||||
:error="errorLabel"
|
||||
/>
|
||||
<FormElement
|
||||
@@ -42,15 +42,15 @@
|
||||
title="Step Annotation"
|
||||
:area="true"
|
||||
help="Add an annotation or notes to this step. Annotations are available when a workflow is viewed."
|
||||
@onChange="onAnnotation"
|
||||
@input="onAnnotation"
|
||||
/>
|
||||
<Form :id="id" :inputs="inputs" @onChange="onChange" />
|
||||
<FormDisplay :id="id" :inputs="inputs" @onChange="onChange" />
|
||||
</template>
|
||||
</FormCard>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Form from "components/Form/Form";
|
||||
import FormDisplay from "components/Form/FormDisplay";
|
||||
import FormCard from "components/Form/FormCard";
|
||||
import FormElement from "components/Form/FormElement";
|
||||
import FormMessage from "components/Form/FormMessage";
|
||||
@@ -60,7 +60,7 @@ import WorkflowIcons from "components/Workflow/icons";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Form,
|
||||
FormDisplay,
|
||||
FormCard,
|
||||
FormElement,
|
||||
FormMessage,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<Form :id="id" :inputs="inputs" ref="form" @onChange="onChange" />
|
||||
<FormDisplay :id="id" :inputs="inputs" ref="form" @onChange="onChange" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Form from "components/Form/Form";
|
||||
import FormDisplay from "components/Form/FormDisplay";
|
||||
import makeSection from "./makeSection";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Form,
|
||||
FormDisplay,
|
||||
},
|
||||
props: {
|
||||
id: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<CurrentUser v-slot="{ user }">
|
||||
<FormCardTool
|
||||
<ToolCard
|
||||
:id="node.config_form.id"
|
||||
:user="user"
|
||||
:version="node.config_form.version"
|
||||
@@ -18,7 +18,7 @@
|
||||
:value="node.label"
|
||||
title="Label"
|
||||
help="Add a step label."
|
||||
@onChange="onLabel"
|
||||
@input="onLabel"
|
||||
:error="errorLabel"
|
||||
/>
|
||||
<FormElement
|
||||
@@ -27,16 +27,16 @@
|
||||
title="Step Annotation"
|
||||
:area="true"
|
||||
help="Add an annotation or notes to this step. Annotations are available when a workflow is viewed."
|
||||
@onChange="onAnnotation"
|
||||
@input="onAnnotation"
|
||||
/>
|
||||
<Form
|
||||
<FormDisplay
|
||||
:id="id"
|
||||
:inputs="inputs"
|
||||
:initial-errors="true"
|
||||
:form-config="formConfig"
|
||||
text-enable="Set in Advance"
|
||||
text-disable="Set at Runtime"
|
||||
@onChange="onChange"
|
||||
ref="form"
|
||||
/>
|
||||
<FormSection
|
||||
:id="id"
|
||||
@@ -46,14 +46,14 @@
|
||||
@onChangeOutputDatatype="onChangeOutputDatatype"
|
||||
/>
|
||||
</template>
|
||||
</FormCardTool>
|
||||
</ToolCard>
|
||||
</CurrentUser>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CurrentUser from "components/providers/CurrentUser";
|
||||
import Form from "components/Form/Form";
|
||||
import FormCardTool from "components/Form/FormCardTool";
|
||||
import FormDisplay from "components/Form/FormDisplay";
|
||||
import ToolCard from "components/Tool/ToolCard";
|
||||
import FormSection from "./FormSection";
|
||||
import FormElement from "components/Form/FormElement";
|
||||
import { getModule } from "components/Workflow/Editor/modules/services";
|
||||
@@ -63,8 +63,8 @@ import Utils from "utils/utils";
|
||||
export default {
|
||||
components: {
|
||||
CurrentUser,
|
||||
Form,
|
||||
FormCardTool,
|
||||
FormDisplay,
|
||||
ToolCard,
|
||||
FormElement,
|
||||
FormSection,
|
||||
},
|
||||
@@ -88,6 +88,7 @@ export default {
|
||||
sectionValues: {},
|
||||
messageVariant: "",
|
||||
messageText: "",
|
||||
formConfig: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -161,9 +162,7 @@ export default {
|
||||
inputs: Object.assign({}, this.mainValues, this.sectionValues),
|
||||
}).then((data) => {
|
||||
this.$emit("onSetData", this.node.id, data);
|
||||
const form = this.$refs["form"];
|
||||
form.parseUpdate(data.config_form);
|
||||
form.parseErrors(data.config_form);
|
||||
this.formConfig = data;
|
||||
if (newVersion) {
|
||||
const options = data.config_form;
|
||||
this.messageVariant = "success";
|
||||
|
||||
@@ -52,19 +52,19 @@
|
||||
<span class="fa fa-cog" />
|
||||
</template>
|
||||
<b-dropdown-item href="#" @click="$emit('onSaveAs')"
|
||||
><span class="fa fa-floppy-o mr-1" />Save As...</b-dropdown-item
|
||||
><span class="fa fa-floppy-o" />Save As...</b-dropdown-item
|
||||
>
|
||||
<b-dropdown-item href="#" @click="$emit('onLayout')"
|
||||
><span class="fa fa-align-left mr-1" />Auto Layout</b-dropdown-item
|
||||
><span class="fa fa-align-left" />Auto Layout</b-dropdown-item
|
||||
>
|
||||
<b-dropdown-item href="#" @click="$emit('onLint')"
|
||||
><span class="fa fa-magic mr-1" />Best Practices</b-dropdown-item
|
||||
><span class="fa fa-magic" />Best Practices</b-dropdown-item
|
||||
>
|
||||
<b-dropdown-item href="#" @click="$emit('onUpgrade')"
|
||||
><span class="fa fa-recycle mr-1" />Upgrade Workflow</b-dropdown-item
|
||||
><span class="fa fa-recycle" />Upgrade Workflow</b-dropdown-item
|
||||
>
|
||||
<b-dropdown-item href="#" @click="$emit('onDownload')"
|
||||
><span class="fa fa-download mr-1" />Download</b-dropdown-item
|
||||
><span class="fa fa-download" />Download</b-dropdown-item
|
||||
>
|
||||
</b-dropdown>
|
||||
<b-button
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
const MockConfigProvider = (fakeConfig) => ({
|
||||
render() {
|
||||
return this.$scopedSlots.default({
|
||||
config: fakeConfig,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default MockConfigProvider;
|
||||
@@ -15,7 +15,7 @@ import { getGalaxyInstance } from "app";
|
||||
import { getAppRoot } from "onload";
|
||||
import decodeUriComponent from "decode-uri-component";
|
||||
import Router from "layout/router";
|
||||
import ToolForm from "mvc/tool/tool-form";
|
||||
import ToolForm from "components/Tool/ToolForm";
|
||||
import FormWrapper from "mvc/form/form-wrapper";
|
||||
import Sharing from "components/Sharing.vue";
|
||||
import UserPreferences from "components/User/UserPreferences.vue";
|
||||
@@ -427,7 +427,7 @@ export const getAnalysisRouter = (Galaxy) => {
|
||||
if (params.version) {
|
||||
params.version = decodeUriComponent(params.version);
|
||||
}
|
||||
this.page.display(new ToolForm.View(params));
|
||||
this._display_vue_helper(ToolForm, params);
|
||||
},
|
||||
|
||||
/** load the center panel iframe using the given url */
|
||||
|
||||
@@ -121,7 +121,7 @@ export default Backbone.View.extend({
|
||||
// render error messages
|
||||
var error_text = this.model.get("error_text");
|
||||
this.$error[error_text ? "show" : "hide"]();
|
||||
this.$el[error_text ? "addClass" : "removeClass"]("alert alert-danger");
|
||||
this.$el[error_text ? "addClass" : "removeClass"]("alert alert-info");
|
||||
this.$error_text.html(error_text);
|
||||
// render backdrop
|
||||
this.$backdrop[this.model.get("backdrop") ? "show" : "hide"]();
|
||||
@@ -186,7 +186,7 @@ export default Backbone.View.extend({
|
||||
.append(
|
||||
$("<div/>")
|
||||
.addClass("ui-form-error")
|
||||
.append($("<span/>").addClass("fa fa-arrow-down mr-1"))
|
||||
.append($("<span/>").addClass("fa fa-exclamation mr-1"))
|
||||
.append($("<span/>").addClass("ui-form-error-text"))
|
||||
)
|
||||
.append(
|
||||
|
||||
@@ -8,7 +8,6 @@ import Portlet from "mvc/ui/ui-portlet";
|
||||
import Ui from "mvc/ui/ui-misc";
|
||||
import FormSection from "mvc/form/form-section";
|
||||
import FormData from "mvc/form/form-data";
|
||||
import { getGalaxyInstance } from "app";
|
||||
|
||||
export default Backbone.View.extend({
|
||||
initialize: function (options) {
|
||||
@@ -26,15 +25,14 @@ export default Backbone.View.extend({
|
||||
},
|
||||
|
||||
/** Update available options */
|
||||
update: function (new_model, triggerChange = true) {
|
||||
update: function (new_model) {
|
||||
var self = this;
|
||||
this.data.matchModel(new_model, (node, input_id) => {
|
||||
var field = self.field_list[input_id];
|
||||
if (field.update) {
|
||||
field.update(node);
|
||||
triggerChange && field.trigger("change");
|
||||
const Galaxy = getGalaxyInstance();
|
||||
Galaxy.emit.debug("form-view::update()", `Updating input: ${input_id}`);
|
||||
field.trigger("change");
|
||||
console.debug("form-view::update()", `Updating input: ${input_id}`);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -54,7 +52,7 @@ export default Backbone.View.extend({
|
||||
highlight: function (input_id, message, silent) {
|
||||
var input_element = this.element_list[input_id];
|
||||
if (input_element) {
|
||||
input_element.error(message || "Please verify this parameter.");
|
||||
input_element.error(message || "Please verify this option.");
|
||||
this.portlet.expand();
|
||||
this.trigger("expand", input_id);
|
||||
if (!silent) {
|
||||
@@ -157,7 +155,6 @@ export default Backbone.View.extend({
|
||||
message: options.message,
|
||||
});
|
||||
}
|
||||
const Galaxy = getGalaxyInstance();
|
||||
Galaxy.emit.debug("form-view::initialize()", "Completed");
|
||||
console.debug("form-view::initialize()", "Completed");
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,472 +0,0 @@
|
||||
/* This is the regular tool form */
|
||||
|
||||
import _ from "underscore";
|
||||
import $ from "jquery";
|
||||
import Backbone from "backbone";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { getGalaxyInstance } from "app";
|
||||
import _l from "utils/localization";
|
||||
import Utils from "utils/utils";
|
||||
import Ui from "mvc/ui/ui-misc";
|
||||
import Modal from "mvc/ui/ui-modal";
|
||||
import ToolFormBase from "mvc/tool/tool-form-base";
|
||||
import Webhooks from "mvc/webhooks";
|
||||
import Vue from "vue";
|
||||
import ToolEntryPoints from "components/ToolEntryPoints/ToolEntryPoints";
|
||||
import ToolRecommendation from "components/ToolRecommendation";
|
||||
|
||||
const View = Backbone.View.extend({
|
||||
initialize: function (options) {
|
||||
const Galaxy = getGalaxyInstance();
|
||||
this.modal = Galaxy.modal || new Modal.View();
|
||||
this.form = new ToolFormBase(
|
||||
Utils.merge(
|
||||
{
|
||||
setupToolMicrodata: true,
|
||||
listen_to_history: true,
|
||||
always_refresh: false,
|
||||
buildmodel: (process, form) => {
|
||||
const options = form.model.attributes;
|
||||
|
||||
// build request url
|
||||
let build_url = "";
|
||||
let build_data = {};
|
||||
const job_id = options.job_id;
|
||||
if (job_id) {
|
||||
build_url = `${getAppRoot()}api/jobs/${job_id}/build_for_rerun`;
|
||||
} else {
|
||||
build_url = `${getAppRoot()}api/tools/${options.id}/build`;
|
||||
build_data = $.extend({}, Galaxy.params);
|
||||
build_data["tool_id"] && delete build_data["tool_id"];
|
||||
}
|
||||
options.version && (build_data["tool_version"] = options.version);
|
||||
|
||||
// get initial model
|
||||
Utils.get({
|
||||
url: build_url,
|
||||
data: build_data,
|
||||
success: (data) => {
|
||||
if (!data.display) {
|
||||
window.location = getAppRoot();
|
||||
return;
|
||||
}
|
||||
|
||||
// we can rely on tool_id, only if it's installed from toolshed
|
||||
// if no version provided and tool_id is not in toolshed style, don't show the warning
|
||||
const client_version =
|
||||
options.version ||
|
||||
(options.tool_id && options.tool_id.match(/^(toolshed)\./)
|
||||
? options.tool_id
|
||||
: false);
|
||||
|
||||
if (
|
||||
data.versions &&
|
||||
client_version &&
|
||||
!data.versions.includes(client_version.replace(/.*\//, ""))
|
||||
) {
|
||||
data.message = `Requested version unavailable.`;
|
||||
}
|
||||
|
||||
form.model.set(data);
|
||||
this._customize(form);
|
||||
Galaxy.emit.debug("tool-form-base::_buildModel()", "Initial tool model ready.", data);
|
||||
process.resolve();
|
||||
},
|
||||
error: (response, status) => {
|
||||
const error_message = (response && response.err_msg) || "Uncaught error.";
|
||||
if (status == 401) {
|
||||
window.location = `${getAppRoot()}user/login?${$.param({
|
||||
redirect: `${getAppRoot()}?tool_id=${options.id}`,
|
||||
})}`;
|
||||
} else if (form.$el.is(":empty")) {
|
||||
form.$el.prepend(
|
||||
new Ui.Message({
|
||||
message: error_message,
|
||||
status: "danger",
|
||||
persistent: true,
|
||||
}).$el
|
||||
);
|
||||
} else {
|
||||
Galaxy.modal &&
|
||||
Galaxy.modal.show({
|
||||
title: _l("Tool request failed"),
|
||||
body: error_message,
|
||||
buttons: {
|
||||
Close: () => {
|
||||
Galaxy.modal.hide();
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
Galaxy.emit.debug(
|
||||
"tool-form-base::_buildModel()",
|
||||
"Initial tool model request failed.",
|
||||
response
|
||||
);
|
||||
process.reject();
|
||||
},
|
||||
});
|
||||
},
|
||||
postchange: (process, form) => {
|
||||
const current_state = {
|
||||
tool_id: form.model.get("id"),
|
||||
tool_version: form.model.get("version"),
|
||||
inputs: $.extend(true, {}, form.data.create()),
|
||||
};
|
||||
form.wait(true);
|
||||
Galaxy.emit.debug("tool-form::postchange()", "Sending current state.", current_state);
|
||||
Utils.request({
|
||||
type: "POST",
|
||||
url: `${getAppRoot()}api/tools/${form.model.get("id")}/build`,
|
||||
data: current_state,
|
||||
success: (data) => {
|
||||
form.update(data);
|
||||
form.wait(false);
|
||||
Galaxy.emit.debug("tool-form::postchange()", "Received new model.", data);
|
||||
process.resolve();
|
||||
},
|
||||
error: (response) => {
|
||||
Galaxy.emit.debug("tool-form::postchange()", "Refresh request failed.", response);
|
||||
process.reject();
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
options
|
||||
)
|
||||
);
|
||||
this.deferred = this.form.deferred;
|
||||
this.setElement("<div/>");
|
||||
this.$el.append(this.form.$el);
|
||||
},
|
||||
|
||||
_customize: function (form) {
|
||||
const Galaxy = getGalaxyInstance();
|
||||
const options = form.model.attributes;
|
||||
var inputs = options.inputs;
|
||||
/* only append notification interface if
|
||||
Galaxy has the server mail configured
|
||||
*/
|
||||
if (Galaxy.config.server_mail_configured && !Galaxy.user.isAnonymous()) {
|
||||
inputs.push({
|
||||
name: `send_email_notification`,
|
||||
label: _l("Email notification"),
|
||||
type: "boolean",
|
||||
value: "false",
|
||||
ignore: "false",
|
||||
help: _l("Send an email notification when the job completes."),
|
||||
});
|
||||
}
|
||||
|
||||
// build execute button
|
||||
const execute_button = new Ui.Button({
|
||||
icon: "fa-check",
|
||||
tooltip: `Execute: ${options.name} (${options.version})`,
|
||||
title: _l("Execute"),
|
||||
cls: "btn btn-primary",
|
||||
wait_cls: "btn btn-info",
|
||||
onclick: () => {
|
||||
execute_button.wait();
|
||||
form.portlet.disable();
|
||||
this.submit(options, () => {
|
||||
execute_button.unwait();
|
||||
form.portlet.enable();
|
||||
});
|
||||
},
|
||||
});
|
||||
options.buttons = { execute: execute_button };
|
||||
|
||||
// remap feature
|
||||
if (options.job_id && options.job_remap) {
|
||||
let label;
|
||||
let help;
|
||||
if (options.job_remap === "job_produced_collection_elements") {
|
||||
label = "Replace elements in collection ?";
|
||||
help =
|
||||
"The previous run of this tool failed. Use this option to replace the failed element(s) in the dataset collection that were produced during the previous tool run.";
|
||||
} else {
|
||||
label = "Resume dependencies from this job ?";
|
||||
help =
|
||||
"The previous run of this tool failed and other tools were waiting for it to finish successfully. Use this option to resume those tools using the new output(s) of this tool run.";
|
||||
}
|
||||
options.inputs.push({
|
||||
label: label,
|
||||
name: "rerun_remap_job_id",
|
||||
type: "select",
|
||||
display: "radio",
|
||||
ignore: "__ignore__",
|
||||
value: "__ignore__",
|
||||
options: [
|
||||
["Yes", options.job_id],
|
||||
["No", "__ignore__"],
|
||||
],
|
||||
help: help,
|
||||
});
|
||||
}
|
||||
|
||||
// Job Re-use Options
|
||||
let extra_user_preferences = {};
|
||||
if (Galaxy.user.attributes.preferences && "extra_user_preferences" in Galaxy.user.attributes.preferences) {
|
||||
extra_user_preferences = JSON.parse(Galaxy.user.attributes.preferences.extra_user_preferences);
|
||||
}
|
||||
const use_cached_job =
|
||||
"use_cached_job|use_cached_job_checkbox" in extra_user_preferences
|
||||
? extra_user_preferences["use_cached_job|use_cached_job_checkbox"]
|
||||
: false;
|
||||
if (use_cached_job === "true") {
|
||||
options.inputs.push({
|
||||
label: "BETA: Attempt to re-use jobs with identical parameters ?",
|
||||
help: "This may skip executing jobs that you have already run",
|
||||
name: "use_cached_job",
|
||||
type: "select",
|
||||
display: "radio",
|
||||
ignore: "__ignore__",
|
||||
value: "__ignore__",
|
||||
options: [
|
||||
["No", false],
|
||||
["Yes", true],
|
||||
],
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/** Submit a regular job.
|
||||
* @param{dict} options - Specifies tool id and version
|
||||
* @param{function} callback - Called when request has completed
|
||||
*/
|
||||
submit: function (options, callback) {
|
||||
const Galaxy = getGalaxyInstance();
|
||||
const history_id = Galaxy.currHistoryPanel && Galaxy.currHistoryPanel.model.id;
|
||||
const job_def = {
|
||||
history_id: history_id,
|
||||
tool_id: options.id,
|
||||
tool_version: options.version,
|
||||
inputs: this.form.data.create(),
|
||||
};
|
||||
this.form.trigger("reset");
|
||||
if (!this.validate(job_def)) {
|
||||
Galaxy.emit.debug("tool-form::submit()", "Submission canceled. Validation failed.");
|
||||
callback && callback();
|
||||
return;
|
||||
}
|
||||
if (options.action !== `${getAppRoot()}tool_runner/index`) {
|
||||
const $f = $("<form/>").attr({
|
||||
action: options.action,
|
||||
method: options.method,
|
||||
enctype: options.enctype,
|
||||
});
|
||||
_.each(job_def.inputs, (value, key) => {
|
||||
$f.append($("<input/>").attr({ name: key, value: value }));
|
||||
});
|
||||
$f.hide().appendTo("body").submit().remove();
|
||||
callback && callback();
|
||||
return;
|
||||
}
|
||||
Galaxy.emit.debug("tool-form::submit()", "Validation complete.", job_def);
|
||||
Utils.request({
|
||||
type: "POST",
|
||||
url: `${getAppRoot()}api/tools`,
|
||||
data: job_def,
|
||||
success: (response) => {
|
||||
callback && callback();
|
||||
this.$el.children().hide();
|
||||
if (response.produces_entry_points) {
|
||||
for (const job of response.jobs) {
|
||||
const toolEntryPointsInstance = Vue.extend(ToolEntryPoints);
|
||||
const vm = document.createElement("div");
|
||||
this.$el.append(vm);
|
||||
const instance = new toolEntryPointsInstance({
|
||||
propsData: {
|
||||
jobId: job.id,
|
||||
},
|
||||
});
|
||||
instance.$mount(vm);
|
||||
}
|
||||
}
|
||||
this.$el.append(this._templateSuccess(response, job_def));
|
||||
const enable_tool_recommendations = Galaxy.config.enable_tool_recommendations;
|
||||
if (enable_tool_recommendations === true || enable_tool_recommendations === "true") {
|
||||
// show tool recommendations
|
||||
const ToolRecommendationInstance = Vue.extend(ToolRecommendation);
|
||||
const vm = document.createElement("div");
|
||||
this.$el.append(vm);
|
||||
const instance = new ToolRecommendationInstance({
|
||||
propsData: {
|
||||
toolId: job_def.tool_id,
|
||||
},
|
||||
});
|
||||
instance.$mount(vm);
|
||||
}
|
||||
this.$el.parent().scrollTop(0);
|
||||
// Show Webhook if job is running
|
||||
if (response.jobs && response.jobs.length > 0) {
|
||||
this.$el.append($("<div/>", { id: "webhook-view" }));
|
||||
new Webhooks.WebhookView({
|
||||
type: "tool",
|
||||
toolId: job_def.tool_id,
|
||||
});
|
||||
}
|
||||
if (Galaxy.currHistoryPanel) {
|
||||
this.form.stopListening(Galaxy.currHistoryPanel.collection);
|
||||
Galaxy.currHistoryPanel.refreshContents();
|
||||
}
|
||||
},
|
||||
error: (response) => {
|
||||
callback && callback();
|
||||
Galaxy.emit.debug("tool-form::submit", "Submission failed.", response);
|
||||
let input_found = false;
|
||||
if (response && response.err_data) {
|
||||
const error_messages = this.form.data.matchResponse(response.err_data);
|
||||
for (const input_id in error_messages) {
|
||||
this.form.highlight(input_id, error_messages[input_id]);
|
||||
input_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!input_found) {
|
||||
this.modal.show({
|
||||
title: _l("Job submission failed"),
|
||||
body: this._templateError(job_def, response && response.err_msg),
|
||||
buttons: {
|
||||
Close: () => {
|
||||
this.modal.hide();
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
/** Validate job dictionary.
|
||||
* @param{dict} job_def - Job execution dictionary
|
||||
*/
|
||||
validate: function (job_def) {
|
||||
const Galaxy = getGalaxyInstance();
|
||||
const job_inputs = job_def.inputs;
|
||||
let batch_n = -1;
|
||||
let batch_src = null;
|
||||
for (const job_input_id in job_inputs) {
|
||||
const input_value = job_inputs[job_input_id];
|
||||
const input_id = this.form.data.match(job_input_id);
|
||||
const input_field = this.form.field_list[input_id];
|
||||
const input_def = this.form.input_list[input_id];
|
||||
if (!input_id || !input_def || !input_field) {
|
||||
Galaxy.emit.debug("tool-form::validate()", "Retrieving input objects failed.");
|
||||
continue;
|
||||
}
|
||||
if (!input_def.optional && input_value == null && input_def.type != "hidden") {
|
||||
this.form.highlight(input_id, "This input requires a value.");
|
||||
return false;
|
||||
}
|
||||
if (input_field.validate) {
|
||||
const message = input_field.validate();
|
||||
if (message) {
|
||||
this.form.highlight(input_id, message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (input_value && input_value.batch) {
|
||||
const n = input_value.values.length;
|
||||
const src = n > 0 && input_value.values[0] && input_value.values[0].src;
|
||||
if (src) {
|
||||
if (batch_src === null) {
|
||||
batch_src = src;
|
||||
} else if (batch_src !== src) {
|
||||
this.form.highlight(
|
||||
input_id,
|
||||
"Please select either dataset or dataset list fields for all batch mode fields."
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (batch_n === -1) {
|
||||
batch_n = n;
|
||||
} else if (batch_n !== n) {
|
||||
this.form.highlight(
|
||||
input_id,
|
||||
`Please make sure that you select the same number of inputs for all batch mode fields. This field contains <b>${n}</b> selection(s) while a previous field contains <b>${batch_n}</b>.`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
_getInputs: function (job_def) {
|
||||
const inputs = [];
|
||||
const index = {};
|
||||
for (const i in job_def.inputs) {
|
||||
const input = job_def.inputs[i];
|
||||
if (input && Array.isArray(input.values)) {
|
||||
for (const j of input.values) {
|
||||
if (j.src && !index[j.id]) {
|
||||
inputs.push(j);
|
||||
index[j.id] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return inputs;
|
||||
},
|
||||
|
||||
_templateRow: function (list, title) {
|
||||
let blurb = "";
|
||||
if (list.length > 0) {
|
||||
blurb += `<p>${title}:</p>`;
|
||||
list.sort((a, b) => {
|
||||
b.hid - a.hid;
|
||||
});
|
||||
blurb += `<ul>`;
|
||||
for (const item of list) {
|
||||
const rowString = `${item.hid}: ${_.escape(item.name)}`;
|
||||
blurb += `<li><b>${rowString}</b></li>`;
|
||||
}
|
||||
blurb += `</ul>`;
|
||||
}
|
||||
return blurb;
|
||||
},
|
||||
|
||||
_templateSuccess: function (response, job_def) {
|
||||
const njobs = response && response.jobs ? response.jobs.length : 0;
|
||||
if (njobs > 0) {
|
||||
const inputs = this._getInputs(job_def);
|
||||
const ninputs = inputs.length;
|
||||
const noutputs = response.outputs.length;
|
||||
const njobsText = njobs > 1 ? `${njobs} jobs` : `1 job`;
|
||||
const ninputsText = ninputs > 1 ? `${ninputs} inputs` : `this input`;
|
||||
const noutputsText = noutputs > 1 ? `${noutputs} outputs` : `this output`;
|
||||
const tool_name = this.form.model.get("name");
|
||||
return `<div class="donemessagelarge">
|
||||
<p>
|
||||
Executed <b>${tool_name}</b> and successfully added ${njobsText} to the queue.
|
||||
</p>
|
||||
${this._templateRow(inputs, `The tool uses ${ninputsText}`)}
|
||||
${this._templateRow(response.outputs, `It produces ${noutputsText}`)}
|
||||
<p>
|
||||
You can check the status of queued jobs and view the resulting data by refreshing the History panel. When the job has been run the status will change from 'running' to 'finished' if completed successfully or 'error' if problems were encountered.
|
||||
</p>
|
||||
</div>`;
|
||||
} else {
|
||||
return this._templateError(response, "Invalid success response. No jobs found.");
|
||||
}
|
||||
},
|
||||
|
||||
_templateError: function (response, err_msg) {
|
||||
return $("<div/>")
|
||||
.addClass("errormessagelarge")
|
||||
.append(
|
||||
$("<p/>").text(
|
||||
`The server could not complete the request. Please contact the Galaxy Team if this error persists. ${
|
||||
err_msg || ""
|
||||
}`
|
||||
)
|
||||
)
|
||||
.append($("<pre/>").text(JSON.stringify(response, null, 4)));
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
View: View,
|
||||
};
|
||||
@@ -364,8 +364,8 @@ body {
|
||||
.nav-note {
|
||||
@extend .font-weight-bold;
|
||||
@extend .position-absolute;
|
||||
left: 2.2rem;
|
||||
top: 2.2rem;
|
||||
left: 1.9rem;
|
||||
top: 1.9rem;
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,10 @@ pre {
|
||||
.dropdown-menu {
|
||||
.dropdown-item {
|
||||
text-decoration: none;
|
||||
.fa {
|
||||
@extend .fa-fw;
|
||||
@extend .mr-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,10 @@ import testApp from "../test-app";
|
||||
import InputElement from "mvc/form/form-input";
|
||||
import Ui from "mvc/ui/ui-misc";
|
||||
import FormData from "mvc/form/form-data";
|
||||
import ToolForm from "mvc/tool/tool-form";
|
||||
import Form from "mvc/form/form-view";
|
||||
import Utils from "utils/utils";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import toolsTestBuild from "../test-data/json/tools.test.build";
|
||||
|
||||
QUnit.module("Form test", {
|
||||
beforeEach: function () {
|
||||
@@ -32,16 +33,9 @@ QUnit.module("Form test", {
|
||||
},
|
||||
});
|
||||
|
||||
QUnit.test("tool-form", function (assert) {
|
||||
var toolform = new ToolForm.View({ id: "test" });
|
||||
$("body").prepend(toolform.$el);
|
||||
window.fakeserver.respond();
|
||||
|
||||
var form = toolform.form;
|
||||
assert.ok(
|
||||
form.$(".portlet-title-text").html() == `<b itemprop="name">_name</b> <span itemprop="description">_description</span> (Galaxy Version _version)`,
|
||||
"Title correct"
|
||||
);
|
||||
QUnit.test("form", function (assert) {
|
||||
var form = new Form(toolsTestBuild);
|
||||
$("body").prepend(form.$el);
|
||||
var tour_ids = [];
|
||||
$("[tour_id]").each(function () {
|
||||
tour_ids.push($(this).attr("tour_id"));
|
||||
@@ -90,7 +84,7 @@ QUnit.test("data", function (assert) {
|
||||
Utils.get({
|
||||
url: getAppRoot() + "api/tools/test/build",
|
||||
success: function (response) {
|
||||
FormData.visitInputs(response.inputs, function (node, name, context) {
|
||||
FormData.visitInputs(response.inputs, function (node, name) {
|
||||
visits.push({ name: name, node: node });
|
||||
});
|
||||
},
|
||||
|
||||
@@ -3248,6 +3248,11 @@ better-assert@~1.0.0:
|
||||
dependencies:
|
||||
callsite "1.0.0"
|
||||
|
||||
bezier-easing@2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/bezier-easing/-/bezier-easing-2.1.0.tgz#c04dfe8b926d6ecaca1813d69ff179b7c2025d86"
|
||||
integrity sha1-wE3+i5JtbsrKGBPWn/F5t8ICXYY=
|
||||
|
||||
big.js@^3.1.3:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
|
||||
@@ -15169,6 +15174,13 @@ vue-rx@^6.2.0:
|
||||
resolved "https://registry.yarnpkg.com/vue-rx/-/vue-rx-6.2.0.tgz#c3b86462b252626edd16417385bb9054a3a23acb"
|
||||
integrity sha512-tpKUcqS5IUYS+HsdbR5TlE5LL9PK4zwlplEtmMeydnbqaUTa9ciLMplJXAtFSiQw1vuURoyEJmCXqMxaVEIloQ==
|
||||
|
||||
vue-scrollto@^2.20.0:
|
||||
version "2.20.0"
|
||||
resolved "https://registry.yarnpkg.com/vue-scrollto/-/vue-scrollto-2.20.0.tgz#3ba52239a62710c97003d12d4393f1f3393cd5cc"
|
||||
integrity sha512-7i+AGKJTThZnMEkhIPgrZjyAX+fXV7/rGdg+CV283uZZwCxwiMXaBLTmIc5RTA4uwGnT+E6eJle3mXQfM2OD3A==
|
||||
dependencies:
|
||||
bezier-easing "2.1.0"
|
||||
|
||||
vue-style-loader@^4.1.0:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.2.tgz#dedf349806f25ceb4e64f3ad7c0a44fba735fcf8"
|
||||
|
||||
@@ -361,7 +361,7 @@ registration:
|
||||
|
||||
tool_form:
|
||||
selectors:
|
||||
options: '#options [data-toggle="dropdown"]'
|
||||
options: '.tool-dropdown'
|
||||
execute: 'button#execute'
|
||||
parameter_div: 'div.ui-form-element[tour_id="${parameter}"]'
|
||||
parameter_checkbox: 'div.ui-form-element[tour_id="${parameter}"] .ui-switch div'
|
||||
|
||||
Reference in New Issue
Block a user