mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
@@ -127,8 +127,19 @@ export default {
|
||||
},
|
||||
created() {
|
||||
this.onCloneInputs();
|
||||
// build flat formData that is ready to be submitted
|
||||
this.formData = this.buildFormData();
|
||||
// emit back to parent, so that parent has submittable data
|
||||
this.$emit("onChange", this.formData);
|
||||
},
|
||||
methods: {
|
||||
buildFormData() {
|
||||
const params = {};
|
||||
Object.entries(this.formIndex).forEach(([key, input]) => {
|
||||
params[key] = input.value;
|
||||
});
|
||||
return params;
|
||||
},
|
||||
onReplaceParams() {
|
||||
let refreshOnChange = false;
|
||||
Object.entries(this.replaceParams).forEach(([key, value]) => {
|
||||
@@ -159,10 +170,7 @@ export default {
|
||||
},
|
||||
onChange(refreshOnChange) {
|
||||
this.onCreateIndex();
|
||||
const params = {};
|
||||
Object.entries(this.formIndex).forEach(([key, input]) => {
|
||||
params[key] = input.value;
|
||||
});
|
||||
const params = this.buildFormData();
|
||||
if (JSON.stringify(params) != JSON.stringify(this.formData)) {
|
||||
this.formData = params;
|
||||
this.resetError();
|
||||
|
||||
@@ -49,11 +49,10 @@ describe("FormElement", () => {
|
||||
});
|
||||
expect(wrapper.find(".ui-form-title-text").text()).toEqual("title_text");
|
||||
expect(wrapper.findAll("button[title='Disable']").length).toEqual(1);
|
||||
expect(wrapper.emitted().input[0][0]).toEqual("initial_value");
|
||||
|
||||
await wrapper.find(".ui-form-collapsible-icon").trigger("click");
|
||||
expect(wrapper.emitted().input[1][0]).toEqual("collapsible_value");
|
||||
expect(wrapper.emitted().input[1][1]).toEqual("input");
|
||||
expect(wrapper.emitted().input[0][0]).toEqual("collapsible_value");
|
||||
expect(wrapper.emitted().input[0][1]).toEqual("input");
|
||||
|
||||
await wrapper.setProps({
|
||||
collapsedEnableText: "Enable Collapsible",
|
||||
@@ -63,7 +62,7 @@ describe("FormElement", () => {
|
||||
expect(wrapper.findAll("button[title='Disable Collapsible']").length).toEqual(0);
|
||||
|
||||
await wrapper.find(".ui-form-collapsible-icon").trigger("click");
|
||||
expect(wrapper.emitted().input[2][0]).toEqual("default_value");
|
||||
expect(wrapper.emitted().input[1][0]).toEqual("default_value");
|
||||
expect(wrapper.findAll("button[title='Disable Collapsible']").length).toEqual(1);
|
||||
expect(wrapper.findAll("button[title='Enable Collapsible']").length).toEqual(0);
|
||||
});
|
||||
|
||||
@@ -82,9 +82,8 @@ const collapsed = ref(false);
|
||||
const collapsible = computed(() => !props.disabled && collapsibleValue.value !== undefined);
|
||||
const connectable = computed(() => collapsible.value && Boolean(attrs.value["connectable"]));
|
||||
|
||||
// Determines to wether expand or collapse the input
|
||||
// Determines whether to expand or collapse the input
|
||||
{
|
||||
setValue(props.value);
|
||||
const valueJson = JSON.stringify(props.value);
|
||||
connected.value = valueJson === JSON.stringify(connectedValue);
|
||||
collapsed.value =
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<CollectionElementsProvider
|
||||
v-if="dsc"
|
||||
:id="dsc.id"
|
||||
ref="provider"
|
||||
:key="dsc.id"
|
||||
v-slot="{ loading, result: payload }"
|
||||
:contents-url="contentsUrl"
|
||||
@@ -76,6 +77,9 @@ export default {
|
||||
const arr = this.selectedCollections;
|
||||
return arr[arr.length - 1];
|
||||
},
|
||||
jobState() {
|
||||
return this.dsc["job_state_summary"];
|
||||
},
|
||||
isRoot() {
|
||||
return this.dsc == this.rootCollection;
|
||||
},
|
||||
@@ -93,6 +97,12 @@ export default {
|
||||
this.$emit("update:selected-collections", []);
|
||||
}
|
||||
},
|
||||
jobState: {
|
||||
handler() {
|
||||
this.$refs.provider.load();
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateDsc(collection, fields) {
|
||||
|
||||
@@ -5,8 +5,7 @@ export function loadWebhookMenuItems(items) {
|
||||
Webhooks.load({
|
||||
type: "masthead",
|
||||
callback: function (webhooks) {
|
||||
webhooks.each((model) => {
|
||||
const webhook = model.toJSON();
|
||||
webhooks.forEach((webhook) => {
|
||||
if (webhook.activate) {
|
||||
const obj = {
|
||||
id: webhook.id,
|
||||
|
||||
@@ -6,7 +6,7 @@ import Webhooks from "utils/webhooks";
|
||||
import ToolSourceMenuItem from "components/Tool/ToolSourceMenuItem";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
|
||||
const { currentUser: user } = useCurrentUser();
|
||||
const { currentUser: user } = useCurrentUser(false, true);
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
@@ -28,9 +28,7 @@ const webhookDetails = ref([]);
|
||||
Webhooks.load({
|
||||
type: "tool-menu",
|
||||
callback: (webhooks) => {
|
||||
webhooks.each((model) => {
|
||||
const webhook = model.toJSON();
|
||||
|
||||
webhooks.forEach((webhook) => {
|
||||
if (webhook.activate && webhook.config.function) {
|
||||
webhookDetails.value.push({
|
||||
icon: `fa ${webhook.config.icon}`,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { getLocalVue, mockModule } from "tests/jest/helpers";
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
import axios from "axios";
|
||||
import ToolCard from "./ToolCard";
|
||||
import Vuex from "vuex";
|
||||
import { userStore } from "store/userStore";
|
||||
@@ -18,8 +20,12 @@ const createStore = (currentUser) => {
|
||||
|
||||
describe("ToolCard", () => {
|
||||
let wrapper;
|
||||
let axiosMock;
|
||||
|
||||
beforeEach(() => {
|
||||
axiosMock = new MockAdapter(axios);
|
||||
axiosMock.onGet(`/api/webhooks`).reply(200, []);
|
||||
|
||||
const store = createStore({
|
||||
id: "user.id",
|
||||
email: "user.email",
|
||||
|
||||
@@ -66,7 +66,7 @@ function onSetError(e) {
|
||||
errorText.value = e;
|
||||
}
|
||||
|
||||
const { currentUser: user } = useCurrentUser();
|
||||
const { currentUser: user } = useCurrentUser(false, true);
|
||||
const hasUser = computed(() => !user.value.isAnonymous);
|
||||
|
||||
const versions = computed(() => props.options.versions);
|
||||
|
||||
@@ -30,6 +30,7 @@ describe("ToolForm", () => {
|
||||
help: "help_text",
|
||||
};
|
||||
axiosMock.onGet(`/api/tools/tool_id/build?tool_version=version`).reply(200, toolData);
|
||||
axiosMock.onGet(`/api/webhooks`).reply(200, []);
|
||||
|
||||
const citations = [];
|
||||
axiosMock.onGet(`/api/tools/tool_id/citations`).reply(200, citations);
|
||||
|
||||
@@ -4,8 +4,8 @@ import { useConfig } from "composables/config";
|
||||
import { useCurrentUser } from "composables/user";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
|
||||
const { config } = useConfig();
|
||||
const { currentUser } = useCurrentUser();
|
||||
const { config } = useConfig(true);
|
||||
const { currentUser } = useCurrentUser(false, true);
|
||||
|
||||
const props = defineProps({
|
||||
toolId: {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { getLocalVue, mockModule } from "tests/jest/helpers";
|
||||
import axios from "axios";
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
import FormTool from "./FormTool";
|
||||
import MockCurrentUser from "components/providers/MockCurrentUser";
|
||||
import MockConfigProvider from "components/providers/MockConfigProvider";
|
||||
@@ -11,6 +13,9 @@ import { createTestingPinia } from "@pinia/testing";
|
||||
const localVue = getLocalVue();
|
||||
|
||||
describe("FormTool", () => {
|
||||
const axiosMock = new MockAdapter(axios);
|
||||
axiosMock.onGet(`/api/webhooks`).reply(200, []);
|
||||
|
||||
function mountTarget() {
|
||||
const store = new Vuex.Store({
|
||||
modules: {
|
||||
|
||||
@@ -1,62 +1,58 @@
|
||||
<template>
|
||||
<CurrentUser v-slot="{ user }">
|
||||
<ToolCard
|
||||
v-if="hasData"
|
||||
:id="configForm.id"
|
||||
:user="user"
|
||||
:version="configForm.version"
|
||||
:title="configForm.name"
|
||||
:description="configForm.description"
|
||||
:options="configForm"
|
||||
:message-text="messageText"
|
||||
:message-variant="messageVariant"
|
||||
@onChangeVersion="onChangeVersion"
|
||||
@onUpdateFavorites="onUpdateFavorites">
|
||||
<template v-slot:body>
|
||||
<FormElement
|
||||
id="__label"
|
||||
:value="label"
|
||||
title="Label"
|
||||
help="Add a step label."
|
||||
:error="uniqueErrorLabel"
|
||||
@input="onLabel" />
|
||||
<FormElement
|
||||
id="__annotation"
|
||||
:value="annotation"
|
||||
title="Step Annotation"
|
||||
:area="true"
|
||||
help="Add an annotation or notes to this step. Annotations are available when a workflow is viewed."
|
||||
@input="onAnnotation" />
|
||||
<FormConditional :step="step" v-on="$listeners" />
|
||||
<div class="mt-2 mb-4">
|
||||
<Heading h2 separator bold size="sm"> Tool Parameters </Heading>
|
||||
<FormDisplay
|
||||
:id="id"
|
||||
:inputs="inputs"
|
||||
:errors="errors"
|
||||
text-enable="Set in Advance"
|
||||
text-disable="Set at Runtime"
|
||||
:workflow-building-mode="true"
|
||||
@onChange="onChange" />
|
||||
</div>
|
||||
<div class="mt-2 mb-4">
|
||||
<Heading h2 separator bold size="sm"> Additional Options </Heading>
|
||||
<FormSection
|
||||
:id="stepId"
|
||||
:node-inputs="stepInputs"
|
||||
:node-outputs="stepOutputs"
|
||||
:step="step"
|
||||
:datatypes="datatypes"
|
||||
:post-job-actions="postJobActions"
|
||||
@onChange="onChangePostJobActions" />
|
||||
</div>
|
||||
</template>
|
||||
</ToolCard>
|
||||
</CurrentUser>
|
||||
<ToolCard
|
||||
v-if="hasData"
|
||||
:id="configForm.id"
|
||||
:version="configForm.version"
|
||||
:title="configForm.name"
|
||||
:description="configForm.description"
|
||||
:options="configForm"
|
||||
:message-text="messageText"
|
||||
:message-variant="messageVariant"
|
||||
@onChangeVersion="onChangeVersion"
|
||||
@onUpdateFavorites="onUpdateFavorites">
|
||||
<template v-slot:body>
|
||||
<FormElement
|
||||
id="__label"
|
||||
:value="label"
|
||||
title="Label"
|
||||
help="Add a step label."
|
||||
:error="uniqueErrorLabel"
|
||||
@input="onLabel" />
|
||||
<FormElement
|
||||
id="__annotation"
|
||||
:value="annotation"
|
||||
title="Step Annotation"
|
||||
:area="true"
|
||||
help="Add an annotation or notes to this step. Annotations are available when a workflow is viewed."
|
||||
@input="onAnnotation" />
|
||||
<FormConditional :step="step" v-on="$listeners" />
|
||||
<div class="mt-2 mb-4">
|
||||
<Heading h2 separator bold size="sm"> Tool Parameters </Heading>
|
||||
<FormDisplay
|
||||
:id="id"
|
||||
:inputs="inputs"
|
||||
:errors="errors"
|
||||
text-enable="Set in Advance"
|
||||
text-disable="Set at Runtime"
|
||||
:workflow-building-mode="true"
|
||||
@onChange="onChange" />
|
||||
</div>
|
||||
<div class="mt-2 mb-4">
|
||||
<Heading h2 separator bold size="sm"> Additional Options </Heading>
|
||||
<FormSection
|
||||
:id="stepId"
|
||||
:node-inputs="stepInputs"
|
||||
:node-outputs="stepOutputs"
|
||||
:step="step"
|
||||
:datatypes="datatypes"
|
||||
:post-job-actions="postJobActions"
|
||||
@onChange="onChangePostJobActions" />
|
||||
</div>
|
||||
</template>
|
||||
</ToolCard>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CurrentUser from "@/components/providers/CurrentUser";
|
||||
import FormDisplay from "@/components/Form/FormDisplay.vue";
|
||||
import ToolCard from "@/components/Tool/ToolCard.vue";
|
||||
import FormSection from "./FormSection.vue";
|
||||
@@ -71,7 +67,6 @@ import { toRef } from "vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CurrentUser,
|
||||
FormDisplay,
|
||||
ToolCard,
|
||||
FormElement,
|
||||
@@ -111,7 +106,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mainValues: {},
|
||||
mainValues: null,
|
||||
messageText: "",
|
||||
messageVariant: "success",
|
||||
};
|
||||
@@ -169,7 +164,7 @@ export default {
|
||||
* @param { Object } values contains flat key-value pairs `prefixed-name=value`
|
||||
*/
|
||||
onChange(values) {
|
||||
const initialRequest = Object.keys(this.mainValues).length === 0;
|
||||
const initialRequest = this.mainValues === null;
|
||||
this.mainValues = values;
|
||||
if (!initialRequest) {
|
||||
this.postChanges();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { computed, onMounted, inject } from "vue";
|
||||
|
||||
/* composable config wrapper */
|
||||
export function useConfig() {
|
||||
export function useConfig(fetchOnce = false) {
|
||||
const store = inject("store");
|
||||
|
||||
const config = computed(() => store.getters["config/config"]);
|
||||
@@ -9,7 +9,9 @@ export function useConfig() {
|
||||
|
||||
// Anytime we mount this (for now), make sure to load.
|
||||
onMounted(() => {
|
||||
store.dispatch("config/loadConfigs");
|
||||
if (!(fetchOnce && isLoaded)) {
|
||||
store.dispatch("config/loadConfigs");
|
||||
}
|
||||
});
|
||||
|
||||
return { config, isLoaded };
|
||||
|
||||
@@ -6,15 +6,16 @@ import type { Store } from "vuex";
|
||||
/**
|
||||
* composable user store wrapper
|
||||
* @param noFetch when true, the user will not be fetched from the server
|
||||
* @param fetchOnce when true, the user will only be fetched from the server if it is not already in the store
|
||||
* @returns currentUser computed
|
||||
*/
|
||||
export function useCurrentUser(noFetch: boolean | Ref<boolean> = false) {
|
||||
export function useCurrentUser(noFetch: boolean | Ref<boolean> = false, fetchOnce: boolean | Ref<boolean> = false) {
|
||||
// TODO: add store typing
|
||||
const store = inject("store") as Store<unknown>;
|
||||
const currentUser = computed(() => store.getters["user/currentUser"]);
|
||||
const currentFavorites = computed(() => store.getters["user/currentFavorites"]);
|
||||
onMounted(() => {
|
||||
if (!unref(noFetch)) {
|
||||
if (!unref(noFetch) && !(Object.keys(currentUser).length > 0) && unref(fetchOnce)) {
|
||||
store.dispatch("user/loadUser");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,14 +2,11 @@ import Webhooks from "utils/webhooks";
|
||||
import Utils from "utils/utils";
|
||||
|
||||
export function onloadWebhooks(Galaxy) {
|
||||
console.log("onloadWebhooks");
|
||||
|
||||
if (Galaxy.config.enable_webhooks) {
|
||||
Webhooks.load({
|
||||
type: "onload",
|
||||
callback: function (webhooks) {
|
||||
webhooks.each((model) => {
|
||||
var webhook = model.toJSON();
|
||||
webhooks.forEach((webhook) => {
|
||||
if (webhook.activate && webhook.script) {
|
||||
Utils.appendScriptStyle(webhook);
|
||||
}
|
||||
|
||||
@@ -490,6 +490,7 @@ tool_form:
|
||||
parameter_checkbox: 'div.ui-form-element[id="form-element-${parameter}"] .ui-switch'
|
||||
parameter_input: 'div.ui-form-element[id="form-element-${parameter}"] .ui-input'
|
||||
parameter_textarea: 'div.ui-form-element[id="form-element-${parameter}"] textarea'
|
||||
repeat_insert: '[data-description="repeat insert"]'
|
||||
reference: '.formatted-reference'
|
||||
about: '.tool-footer'
|
||||
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
import axios from "axios";
|
||||
import Backbone from "backbone";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { rethrowSimple } from "@/utils/simple-error";
|
||||
import Utils from "utils/utils";
|
||||
|
||||
const Webhooks = Backbone.Collection.extend({
|
||||
url: function () {
|
||||
return `${getAppRoot()}api/webhooks`;
|
||||
},
|
||||
});
|
||||
let webhookData = undefined;
|
||||
|
||||
async function getWebHookData() {
|
||||
if (webhookData === undefined) {
|
||||
try {
|
||||
const { data } = await axios.get(`${getAppRoot()}api/webhooks`);
|
||||
webhookData = data;
|
||||
} catch (e) {
|
||||
rethrowSimple(e);
|
||||
}
|
||||
}
|
||||
return webhookData;
|
||||
}
|
||||
|
||||
const WebhookView = Backbone.View.extend({
|
||||
el: "#webhook-view",
|
||||
@@ -18,16 +28,13 @@ const WebhookView = Backbone.View.extend({
|
||||
this.$el.attr("tool_id", toolId);
|
||||
this.$el.attr("tool_version", toolVersion);
|
||||
|
||||
const webhooks = new Webhooks();
|
||||
webhooks.fetch({
|
||||
success: (data) => {
|
||||
if (options.type) {
|
||||
data.reset(filterType(data, options.type));
|
||||
}
|
||||
if (data.length > 0) {
|
||||
this.render(weightedRandomPick(data));
|
||||
}
|
||||
},
|
||||
getWebHookData().then((data) => {
|
||||
if (options.type) {
|
||||
data.reset(filterType(data, options.type));
|
||||
}
|
||||
if (data.length > 0) {
|
||||
this.render(weightedRandomPick(data));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -40,21 +47,18 @@ const WebhookView = Backbone.View.extend({
|
||||
});
|
||||
|
||||
const load = (options) => {
|
||||
const webhooks = new Webhooks();
|
||||
webhooks.fetch({
|
||||
async: options.async !== undefined ? options.async : true,
|
||||
success: (data) => {
|
||||
if (options.type) {
|
||||
data.reset(filterType(data, options.type));
|
||||
}
|
||||
options.callback(data);
|
||||
},
|
||||
getWebHookData().then((data) => {
|
||||
let filteredData = data;
|
||||
if (options.type) {
|
||||
filteredData = filterType(data, options.type);
|
||||
}
|
||||
options.callback(filteredData);
|
||||
});
|
||||
};
|
||||
|
||||
function filterType(data, type) {
|
||||
return data.models.filter((item) => {
|
||||
const itype = item.get("type");
|
||||
return data.filter((item) => {
|
||||
const itype = item.type;
|
||||
if (itype) {
|
||||
return itype.indexOf(type) !== -1;
|
||||
} else {
|
||||
|
||||
@@ -539,7 +539,7 @@ class Data(metaclass=DataMeta):
|
||||
trans.fill_template_mako(
|
||||
"/dataset/large_file.mako",
|
||||
truncated_data=open(dataset.file_name, "rb").read(max_peek_size),
|
||||
dataset=dataset,
|
||||
data=dataset,
|
||||
),
|
||||
headers,
|
||||
)
|
||||
|
||||
@@ -59,6 +59,11 @@ List of behavior changes associated with profile versions:
|
||||
|
||||
- Do not strip leading and trailing whitespaces in `from_work_dir` attribute.
|
||||
|
||||
### 23.0
|
||||
|
||||
- Text parameters that are inferred to be optional (i.e the `optional` tag is not set, but the tool parameter accepts an empty string)
|
||||
are set to `None` for templating in Cheetah. Older tools receive the empty string `""` as the templated value.
|
||||
|
||||
### Examples
|
||||
|
||||
A normal tool:
|
||||
|
||||
@@ -356,7 +356,9 @@ class ToolEvaluator:
|
||||
input, value, other_values=param_dict, compute_environment=self.compute_environment
|
||||
)
|
||||
else:
|
||||
input_values[input.name] = InputValueWrapper(input, value, param_dict)
|
||||
input_values[input.name] = InputValueWrapper(
|
||||
input, value, param_dict, profile=self.tool and self.tool.profile
|
||||
)
|
||||
|
||||
# HACK: only wrap if check_values is not false, this deals with external
|
||||
# tools where the inputs don't even get passed through. These
|
||||
|
||||
@@ -164,6 +164,7 @@ class ToolParameter(Dictifiable):
|
||||
self.hidden = input_source.get_bool("hidden", False)
|
||||
self.refresh_on_change = input_source.get_bool("refresh_on_change", False)
|
||||
self.optional = input_source.parse_optional()
|
||||
self.optionality_inferred = False
|
||||
self.is_dynamic = False
|
||||
self.label = input_source.parse_label()
|
||||
self.help = input_source.parse_help()
|
||||
@@ -326,7 +327,24 @@ class SimpleTextToolParameter(ToolParameter):
|
||||
def __init__(self, tool, input_source):
|
||||
input_source = ensure_input_source(input_source)
|
||||
super().__init__(tool, input_source)
|
||||
self.optional = input_source.get_bool("optional", False)
|
||||
optional = input_source.get("optional", None)
|
||||
if optional is not None:
|
||||
optional = string_as_bool(optional)
|
||||
else:
|
||||
# Optionality not explicitly defined, default to False
|
||||
optional = False
|
||||
if self.type == "text":
|
||||
# A text parameter that doesn't raise a validation error on empty string
|
||||
# is considered to be optional
|
||||
try:
|
||||
for validator in self.validators:
|
||||
validator.validate("")
|
||||
optional = True
|
||||
self.optionality_inferred = True
|
||||
except ValueError:
|
||||
pass
|
||||
self.optional = optional
|
||||
|
||||
if self.optional:
|
||||
self.value = None
|
||||
else:
|
||||
@@ -353,7 +371,8 @@ class TextToolParameter(SimpleTextToolParameter):
|
||||
>>> p = TextToolParameter(None, XML('<param name="_name" type="text" value="default" />'))
|
||||
>>> print(p.name)
|
||||
_name
|
||||
>>> assert sorted(p.to_dict(trans).items()) == [('area', False), ('argument', None), ('datalist', []), ('help', ''), ('hidden', False), ('is_dynamic', False), ('label', ''), ('model_class', 'TextToolParameter'), ('name', '_name'), ('optional', False), ('refresh_on_change', False), ('type', 'text'), ('value', u'default')]
|
||||
>>> sorted(p.to_dict(trans).items())
|
||||
[('area', False), ('argument', None), ('datalist', []), ('help', ''), ('hidden', False), ('is_dynamic', False), ('label', ''), ('model_class', 'TextToolParameter'), ('name', '_name'), ('optional', True), ('refresh_on_change', False), ('type', 'text'), ('value', 'default')]
|
||||
"""
|
||||
|
||||
def __init__(self, tool, input_source):
|
||||
|
||||
@@ -98,7 +98,7 @@ class WrappedParameters:
|
||||
name=input.name,
|
||||
)
|
||||
else:
|
||||
input_values[input.name] = InputValueWrapper(input, value, incoming)
|
||||
input_values[input.name] = InputValueWrapper(input, value, incoming, tool.profile)
|
||||
|
||||
|
||||
def make_dict_copy(from_dict):
|
||||
|
||||
@@ -66,7 +66,7 @@ class ToolParameterValueWrapper:
|
||||
Base class for object that Wraps a Tool Parameter and Value.
|
||||
"""
|
||||
|
||||
value: Union[str, List[str]]
|
||||
value: Optional[Union[str, List[str]]]
|
||||
input: "ToolParameter"
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
@@ -120,10 +120,20 @@ class InputValueWrapper(ToolParameterValueWrapper):
|
||||
def __init__(
|
||||
self,
|
||||
input: "ToolParameter",
|
||||
value: str,
|
||||
value: Optional[str],
|
||||
other_values: Optional[Dict[str, str]] = None,
|
||||
profile: Optional[float] = None,
|
||||
) -> None:
|
||||
self.input = input
|
||||
if (
|
||||
value is None
|
||||
and input.type == "text"
|
||||
and input.optional
|
||||
and input.optionality_inferred
|
||||
and (profile is None or profile < 23.0)
|
||||
):
|
||||
# Tools with old profile versions may treat an optional text parameter as `""`
|
||||
value = ""
|
||||
self.value = value
|
||||
self._other_values: Dict[str, str] = other_values or {}
|
||||
|
||||
@@ -236,7 +246,7 @@ class SelectToolParameterWrapper(ToolParameterValueWrapper):
|
||||
compute_environment: Optional["ComputeEnvironment"] = None,
|
||||
):
|
||||
self.input = input
|
||||
self.value = value
|
||||
self.value: Union[str, List[str]] = value
|
||||
self.input.value_label = input.value_to_display_text(value)
|
||||
self._other_values = other_values or {}
|
||||
self.compute_environment = compute_environment
|
||||
|
||||
@@ -918,6 +918,26 @@ steps:
|
||||
output_connector.send_keys(Keys.SPACE)
|
||||
assert self.driver.switch_to.active_element.text == "No compatible input found in workflow"
|
||||
|
||||
@selenium_test
|
||||
def test_insert_input_handling(self):
|
||||
self.open_in_workflow_editor(
|
||||
"""class: GalaxyWorkflow
|
||||
inputs: []
|
||||
steps:
|
||||
build_list:
|
||||
tool_id: __BUILD_LIST__
|
||||
"""
|
||||
)
|
||||
editor = self.components.workflow_editor
|
||||
node = editor.node._(label="build_list")
|
||||
node.wait_for_and_click()
|
||||
assert not node.has_class("input-terminal")
|
||||
self.components.tool_form.repeat_insert.wait_for_and_click()
|
||||
node.input_terminal(name="datasets_0|input").wait_for_present()
|
||||
self.components.tool_form.repeat_insert.wait_for_and_click()
|
||||
node.input_terminal(name="datasets_1|input").wait_for_present()
|
||||
self.assert_workflow_has_changes_and_save()
|
||||
|
||||
@selenium_test
|
||||
def test_workflow_output_handling(self):
|
||||
self.open_in_workflow_editor(
|
||||
|
||||
@@ -3,13 +3,16 @@
|
||||
#set $int_val_inc = int($inttest) + 1
|
||||
#set $int_flot_val_inc = int($floattest) + 1
|
||||
#set $float_val_inc = float($floattest) + 1
|
||||
echo $int_val_inc >> '$out_file1';
|
||||
#set $text_val = $texttest.strip()
|
||||
echo $int_val_inc >> '$out_file1';
|
||||
echo $int_flot_val_inc >> '$out_file1';
|
||||
echo $float_val_inc >> '$out_file1';
|
||||
echo $text_val >> '#out_file1'
|
||||
</command>
|
||||
<inputs>
|
||||
<param name="inttest" value="1" type="integer" />
|
||||
<param name="floattest" value="1.0" type="float" />
|
||||
<param name="texttest" type="text" />
|
||||
</inputs>
|
||||
<outputs>
|
||||
<data name="out_file1" format="txt" />
|
||||
@@ -27,4 +30,4 @@
|
||||
</output>
|
||||
</test>
|
||||
</tests>
|
||||
</tool>
|
||||
</tool>
|
||||
@@ -91,7 +91,9 @@ class TestParameterParsing(BaseParameterTestCase):
|
||||
def test_parse_optional(self):
|
||||
param = self._parameter_for(
|
||||
xml="""
|
||||
<param type="text" name="texti" value="mydefault" />
|
||||
<param type="text" name="texti" value="mydefault">
|
||||
<validator type="empty_field" />
|
||||
</param>
|
||||
"""
|
||||
)
|
||||
assert param.optional is False
|
||||
|
||||
Reference in New Issue
Block a user