diff --git a/client/package.json b/client/package.json
index 8f5b4ad7b4b..bf6b816241c 100644
--- a/client/package.json
+++ b/client/package.json
@@ -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",
diff --git a/client/src/components/Common/ButtonSpinner.vue b/client/src/components/Common/ButtonSpinner.vue
new file mode 100644
index 00000000000..56531098ef6
--- /dev/null
+++ b/client/src/components/Common/ButtonSpinner.vue
@@ -0,0 +1,36 @@
+
+
+ Please wait...
+
+
+ {{ title }}
+
+
+
diff --git a/client/src/components/Common/Webhook.vue b/client/src/components/Common/Webhook.vue
new file mode 100644
index 00000000000..a3885849690
--- /dev/null
+++ b/client/src/components/Common/Webhook.vue
@@ -0,0 +1,27 @@
+
+
+
+
diff --git a/client/src/components/Form/Elements/FormBoolean.test.js b/client/src/components/Form/Elements/FormBoolean.test.js
new file mode 100644
index 00000000000..90149c2442c
--- /dev/null
+++ b/client/src/components/Form/Elements/FormBoolean.test.js
@@ -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);
+ });
+});
diff --git a/client/src/components/Form/Elements/FormBoolean.vue b/client/src/components/Form/Elements/FormBoolean.vue
new file mode 100644
index 00000000000..b926c2c3738
--- /dev/null
+++ b/client/src/components/Form/Elements/FormBoolean.vue
@@ -0,0 +1,24 @@
+
+
+
+
+
diff --git a/client/src/components/Form/FormInput.test.js b/client/src/components/Form/Elements/FormInput.test.js
similarity index 88%
rename from client/src/components/Form/FormInput.test.js
rename to client/src/components/Form/Elements/FormInput.test.js
index ebe24aa5960..c9b7eb9247b 100644
--- a/client/src/components/Form/FormInput.test.js
+++ b/client/src/components/Form/Elements/FormInput.test.js
@@ -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");
});
});
diff --git a/client/src/components/Form/Elements/FormInput.vue b/client/src/components/Form/Elements/FormInput.vue
new file mode 100644
index 00000000000..05758a19b28
--- /dev/null
+++ b/client/src/components/Form/Elements/FormInput.vue
@@ -0,0 +1,32 @@
+
+
+
+
+
diff --git a/client/src/components/Form/Form.vue b/client/src/components/Form/Form.vue
deleted file mode 100644
index 078671ffa80..00000000000
--- a/client/src/components/Form/Form.vue
+++ /dev/null
@@ -1,68 +0,0 @@
-
-
-
-
-
diff --git a/client/src/components/Form/FormCard.test.js b/client/src/components/Form/FormCard.test.js
new file mode 100644
index 00000000000..303e4f7c5cd
--- /dev/null
+++ b/client/src/components/Form/FormCard.test.js
@@ -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");
+ });
+});
diff --git a/client/src/components/Form/FormCard.vue b/client/src/components/Form/FormCard.vue
index 05443ec7630..1562b2e8161 100644
--- a/client/src/components/Form/FormCard.vue
+++ b/client/src/components/Form/FormCard.vue
@@ -8,7 +8,6 @@
{{ title }} {{ description }}
- {{ version }}
@@ -26,11 +25,7 @@ export default {
},
description: {
type: String,
- required: false,
- },
- version: {
- type: String,
- required: false,
+ default: null,
},
icon: {
type: String,
diff --git a/client/src/components/Form/FormDisplay.vue b/client/src/components/Form/FormDisplay.vue
new file mode 100644
index 00000000000..09d42bd6f16
--- /dev/null
+++ b/client/src/components/Form/FormDisplay.vue
@@ -0,0 +1,156 @@
+
+
+
+
+
diff --git a/client/src/components/Form/FormElement.test.js b/client/src/components/Form/FormElement.test.js
index 34f8c604732..90c4e295f01 100644
--- a/client/src/components/Form/FormElement.test.js
+++ b/client/src/components/Form/FormElement.test.js
@@ -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");
diff --git a/client/src/components/Form/FormElement.vue b/client/src/components/Form/FormElement.vue
index 9235cbe2c38..89abca8ca1e 100644
--- a/client/src/components/Form/FormElement.vue
+++ b/client/src/components/Form/FormElement.vue
@@ -6,16 +6,19 @@
{{ title }}
-
+
+
{{ help }}
diff --git a/client/src/components/Form/FormInput.vue b/client/src/components/Form/FormInput.vue
deleted file mode 100644
index ed25eb4917b..00000000000
--- a/client/src/components/Form/FormInput.vue
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
diff --git a/client/src/components/Markdown/MarkdownVisualization.vue b/client/src/components/Markdown/MarkdownVisualization.vue
index b9c5bcce32d..358508fa1bd 100644
--- a/client/src/components/Markdown/MarkdownVisualization.vue
+++ b/client/src/components/Markdown/MarkdownVisualization.vue
@@ -19,7 +19,7 @@
@ok="onOk"
@cancel="onCancel"
>
-
+
@@ -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: {
diff --git a/client/src/components/Panels/ToolBox.vue b/client/src/components/Panels/ToolBox.vue
index 73167474b32..fca9bd812ce 100644
--- a/client/src/components/Panels/ToolBox.vue
+++ b/client/src/components/Panels/ToolBox.vue
@@ -28,10 +28,10 @@
@@ -89,6 +93,7 @@
+
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);
},
diff --git a/client/src/components/Tool/ToolFooter.vue b/client/src/components/Tool/ToolFooter.vue
index 22d097f2704..991b9788be3 100644
--- a/client/src/components/Tool/ToolFooter.vue
+++ b/client/src/components/Tool/ToolFooter.vue
@@ -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 {
diff --git a/client/src/components/Tool/ToolForm.test.js b/client/src/components/Tool/ToolForm.test.js
new file mode 100644
index 00000000000..15cf8df15f7
--- /dev/null
+++ b/client/src/components/Tool/ToolForm.test.js
@@ -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");
+ });
+});
diff --git a/client/src/components/Tool/ToolForm.vue b/client/src/components/Tool/ToolForm.vue
new file mode 100644
index 00000000000..29f5de66e28
--- /dev/null
+++ b/client/src/components/Tool/ToolForm.vue
@@ -0,0 +1,316 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ errorTitle | l }}
+
+ The server could not complete the request. Please contact the Galaxy Team if this error
+ persists.
+
+ {{ errorContentPretty }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/Tool/ToolSuccess.vue b/client/src/components/Tool/ToolSuccess.vue
new file mode 100644
index 00000000000..eeb9e8e3ed6
--- /dev/null
+++ b/client/src/components/Tool/ToolSuccess.vue
@@ -0,0 +1,79 @@
+
+
+
+ Executed {{ toolName }} and successfully added {{ nJobsText }} to the queue.
+
+
The tool uses {{ nInputsText }}:
+
+
+ {{ item.hid }}: {{ item.name }}
+
+
+
It produces {{ nOutputsText }}:
+
+
+ {{ item.hid }}: {{ item.name }}
+
+
+
+ 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.
+
+
+
+
+
diff --git a/client/src/components/Tool/services.js b/client/src/components/Tool/services.js
index 7fc0c2f230e..f8df815e275 100644
--- a/client/src/components/Tool/services.js
+++ b/client/src/components/Tool/services.js
@@ -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 {
diff --git a/client/src/components/Form/utilities.js b/client/src/components/Tool/utilities.js
similarity index 100%
rename from client/src/components/Form/utilities.js
rename to client/src/components/Tool/utilities.js
diff --git a/client/src/components/Workflow/Editor/Forms/FormDefault.vue b/client/src/components/Workflow/Editor/Forms/FormDefault.vue
index fa9d6be0061..226b2a11a82 100644
--- a/client/src/components/Workflow/Editor/Forms/FormDefault.vue
+++ b/client/src/components/Workflow/Editor/Forms/FormDefault.vue
@@ -33,7 +33,7 @@
:value="node.label"
title="Label"
help="Add a step label."
- @onChange="onLabel"
+ @input="onLabel"
:error="errorLabel"
/>
-
+