mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge remote-tracking branch 'upstream/release_23.0' into dev
This commit is contained in:
@@ -20,12 +20,16 @@
|
||||
@click="submit('attribute', 'attributes')">
|
||||
<font-awesome-icon icon="save" class="mr-1" />{{ "Save" | l }}
|
||||
</b-button>
|
||||
<b-button @click="submit('attribute', 'autodetect')">
|
||||
<b-button v-if="!result['metadata_disable']" @click="submit('attribute', 'autodetect')">
|
||||
<font-awesome-icon icon="redo" class="mr-1" />{{ "Auto-detect" | l }}
|
||||
</b-button>
|
||||
</div>
|
||||
</b-tab>
|
||||
<b-tab v-if="!result['conversion_disable'] || !result['datatype_disable']">
|
||||
<b-tab
|
||||
v-if="
|
||||
(!result['conversion_disable'] || !result['datatype_disable']) &&
|
||||
!result['metadata_disable']
|
||||
">
|
||||
<template v-slot:title>
|
||||
<span v-if="!result['conversion_disable']">
|
||||
<font-awesome-icon icon="cog" class="mr-1" />{{ "Convert" | l }}
|
||||
|
||||
@@ -56,6 +56,11 @@ describe("History Navigation", () => {
|
||||
provide: { store },
|
||||
});
|
||||
|
||||
const createButton = wrapper.find("*[data-description='create new history']");
|
||||
expect(createButton.attributes().disabled).toBeFalsy();
|
||||
const switchButton = wrapper.find("*[data-description='switch to another history']");
|
||||
expect(switchButton.attributes().disabled).toBeFalsy();
|
||||
|
||||
const dropDown = wrapper.find("*[data-description='history options']");
|
||||
const optionElements = dropDown.findAll("b-dropdown-item-stub");
|
||||
const optionTexts = optionElements.wrappers.map((el) => el.text());
|
||||
@@ -76,8 +81,12 @@ describe("History Navigation", () => {
|
||||
provide: { store },
|
||||
});
|
||||
|
||||
const dropDown = wrapper.find("*[data-description='history options']");
|
||||
const createButton = wrapper.find("*[data-description='create new history']");
|
||||
expect(createButton.attributes().disabled).toBeTruthy();
|
||||
const switchButton = wrapper.find("*[data-description='switch to another history']");
|
||||
expect(switchButton.attributes().disabled).toBeTruthy();
|
||||
|
||||
const dropDown = wrapper.find("*[data-description='history options']");
|
||||
const enabledOptionElements = dropDown.findAll("b-dropdown-item-stub:not([disabled])");
|
||||
const enabledOptionTexts = enabledOptionElements.wrappers.map((el) => el.text());
|
||||
expect(enabledOptionTexts).toStrictEqual(anonymousOptions);
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
data-description="create new history"
|
||||
size="sm"
|
||||
variant="link"
|
||||
title="Create new history"
|
||||
:disabled="currentUser.isAnonymous"
|
||||
:title="userTitle('Create new history')"
|
||||
@click="$emit('createNewHistory')">
|
||||
<Icon fixed-width icon="plus" />
|
||||
</b-button>
|
||||
@@ -23,7 +24,8 @@
|
||||
data-description="switch to another history"
|
||||
size="sm"
|
||||
variant="link"
|
||||
title="Switch to history">
|
||||
:disabled="currentUser.isAnonymous"
|
||||
:title="userTitle('Switch to history')">
|
||||
<Icon fixed-width icon="exchange-alt" />
|
||||
</b-button>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import SelectorModal from "./SelectorModal";
|
||||
|
||||
const localVue = getLocalVue();
|
||||
|
||||
const SELECTED_HISTORY_ID = "COOL_ID";
|
||||
const CURRENT_HISTORY_ID = "COOL_ID";
|
||||
const getFakeHistorySummaries = (num, selectedIndex = 0) => {
|
||||
const result = Array.from({ length: num }, (_, index) => ({
|
||||
id: `ID-${index}`,
|
||||
@@ -13,11 +13,11 @@ const getFakeHistorySummaries = (num, selectedIndex = 0) => {
|
||||
tags: [],
|
||||
update_time: new Date().toISOString(),
|
||||
}));
|
||||
result[selectedIndex].id = SELECTED_HISTORY_ID;
|
||||
result[selectedIndex].id = CURRENT_HISTORY_ID;
|
||||
return result;
|
||||
};
|
||||
const PROPS_WITH_10_HISTORIES = {
|
||||
currentHistoryId: SELECTED_HISTORY_ID,
|
||||
currentHistoryId: CURRENT_HISTORY_ID,
|
||||
histories: getFakeHistorySummaries(10),
|
||||
perPage: 3,
|
||||
static: true, // Force the modal visible for testing
|
||||
@@ -27,6 +27,8 @@ const PROPS_WITH_10_HISTORY_MULTIPLE_SELECT = {
|
||||
multiple: true,
|
||||
};
|
||||
|
||||
const CURRENT_HISTORY_INDICATION_TEXT = "(Current)";
|
||||
|
||||
describe("History SelectorModal.vue", () => {
|
||||
let wrapper;
|
||||
|
||||
@@ -38,12 +40,11 @@ describe("History SelectorModal.vue", () => {
|
||||
await flushPromises();
|
||||
}
|
||||
|
||||
it("should highlight the currently selected history", async () => {
|
||||
it("should indicate the currently selected history", async () => {
|
||||
await mountWith(PROPS_WITH_10_HISTORIES);
|
||||
|
||||
const selectedRows = wrapper.findAll(".table-info");
|
||||
expect(selectedRows.length).toBe(1);
|
||||
expect(selectedRows.at(0).attributes("data-pk")).toBe(SELECTED_HISTORY_ID);
|
||||
const currentHistoryRow = wrapper.find(`[data-pk="${CURRENT_HISTORY_ID}"]`);
|
||||
expect(currentHistoryRow.html()).toContain(CURRENT_HISTORY_INDICATION_TEXT);
|
||||
});
|
||||
|
||||
it("paginates the histories", async () => {
|
||||
@@ -67,25 +68,29 @@ describe("History SelectorModal.vue", () => {
|
||||
expect(wrapper.emitted()["selectHistory"][0][0].id).toBe(targetHistoryId);
|
||||
});
|
||||
|
||||
it("select multiple histories", async () => {
|
||||
await mountWith(PROPS_WITH_10_HISTORY_MULTIPLE_SELECT);
|
||||
describe("Multi-selection Mode", () => {
|
||||
it("should select multiple histories", async () => {
|
||||
await mountWith(PROPS_WITH_10_HISTORY_MULTIPLE_SELECT);
|
||||
|
||||
expect(wrapper.emitted()["selectHistories"]).toBeUndefined();
|
||||
expect(wrapper.emitted()["selectHistories"]).toBeUndefined();
|
||||
|
||||
const targetHistoryId1 = "ID-1";
|
||||
const targetRow1 = wrapper.find(`[data-pk="${targetHistoryId1}"]`);
|
||||
await targetRow1.trigger("click");
|
||||
const targetHistoryId1 = "ID-1";
|
||||
const targetRow1 = wrapper.find(`[data-pk="${targetHistoryId1}"]`);
|
||||
await targetRow1.trigger("click");
|
||||
|
||||
const targetHistoryId2 = "ID-2";
|
||||
const targetRow2 = wrapper.find(`[data-pk="${targetHistoryId2}"]`);
|
||||
await targetRow2.trigger("click");
|
||||
const targetHistoryId2 = "ID-2";
|
||||
const targetRow2 = wrapper.find(`[data-pk="${targetHistoryId2}"]`);
|
||||
await targetRow2.trigger("click");
|
||||
|
||||
expect(wrapper.vm.selectedHistories.length).toBe(2);
|
||||
expect(wrapper.vm.selectedHistories.length).toBe(2);
|
||||
|
||||
const button = wrapper.find(".btn-primary");
|
||||
const button = wrapper.find(".btn-primary");
|
||||
|
||||
await button.trigger("click");
|
||||
await button.trigger("click");
|
||||
|
||||
expect(wrapper.emitted()["selectHistories"][0][0][0].id).toBe(targetHistoryId1);
|
||||
expect(wrapper.emitted()["selectHistories"][0][0][0].id).toBe(targetHistoryId1);
|
||||
|
||||
console.debug(wrapper.html());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
primary-key="id"
|
||||
:fields="fields"
|
||||
:filter="filter"
|
||||
:items="formattedItems"
|
||||
:items="histories"
|
||||
:per-page="perPage"
|
||||
:current-page="currentPage"
|
||||
:selectable="true"
|
||||
@@ -22,6 +22,9 @@
|
||||
selected-variant="success"
|
||||
@row-selected="rowSelected"
|
||||
@filtered="onFiltered">
|
||||
<template v-slot:cell(name)="row">
|
||||
{{ row.item.name }} <i v-if="row.item.id === currentHistoryId"><b>(Current)</b></i>
|
||||
</template>
|
||||
<template v-slot:cell(tags)="row">
|
||||
<stateless-tags :value="row.item.tags" :disabled="true" />
|
||||
</template>
|
||||
@@ -31,7 +34,9 @@
|
||||
</b-table>
|
||||
<template v-slot:modal-footer>
|
||||
<b-pagination v-model="currentPage" :total-rows="totalRows" :per-page="perPage" />
|
||||
<b-button v-if="multiple" variant="primary" @click="addSelected">Add Selected</b-button>
|
||||
<b-button v-if="multiple" :disabled="isEmptySelection" variant="primary" @click="addSelected">
|
||||
Add Selected
|
||||
</b-button>
|
||||
</template>
|
||||
</b-modal>
|
||||
</template>
|
||||
@@ -69,13 +74,8 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
formattedItems() {
|
||||
return this.histories.map((item) => {
|
||||
if (item.id === this.currentHistoryId) {
|
||||
item._rowVariant = "info";
|
||||
}
|
||||
return item;
|
||||
});
|
||||
isEmptySelection() {
|
||||
return this.selectedHistories.length === 0;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<span>
|
||||
<span :class="spinnerClasses"></span>
|
||||
<span class="loading-message">{{ message }}.<span class="blinking">..</span></span>
|
||||
<span :class="spinnerClasses" title="loading"></span>
|
||||
<span v-if="!spinnerOnly" class="loading-message">{{ message }}.<span class="blinking">..</span></span>
|
||||
</span>
|
||||
</template>
|
||||
<script>
|
||||
@@ -16,6 +16,11 @@ export default {
|
||||
type: String,
|
||||
default: "Loading",
|
||||
},
|
||||
spinnerOnly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
spinnerClasses() {
|
||||
|
||||
@@ -59,9 +59,9 @@ export default {
|
||||
computed: {
|
||||
targetClass() {
|
||||
if (this.toolKey) {
|
||||
return `tool-menu-item-${this.tool[this.toolKey]} title-link`;
|
||||
return `tool-menu-item-${this.tool[this.toolKey]} title-link cursor-pointer`;
|
||||
} else {
|
||||
return `title-link`;
|
||||
return `title-link cursor-pointer`;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -109,7 +109,7 @@ export default {
|
||||
this.$emit("onResults", this.favoritesResults);
|
||||
} else {
|
||||
// keys with sorting order
|
||||
const keys = { exact: 2, name: 1, description: 0 };
|
||||
const keys = { exact: 3, name: 2, description: 1, combined: 0 };
|
||||
this.$emit("onResults", searchToolsByKeys(this.toolsList, keys, q));
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<tool-search
|
||||
:current-panel-view="currentPanelView"
|
||||
placeholder="search tools"
|
||||
:toolbox="toolbox"
|
||||
:toolbox="workflowTools"
|
||||
:query="query"
|
||||
@onQuery="onQuery"
|
||||
@onResults="onResults" />
|
||||
@@ -74,7 +74,7 @@
|
||||
import _l from "utils/localization";
|
||||
import ToolSection from "./Common/ToolSection";
|
||||
import ToolSearch from "./Common/ToolSearch";
|
||||
import { filterToolSections } from "./utilities";
|
||||
import { filterToolSections, removeDisabledTools } from "./utilities";
|
||||
import PanelViewButton from "./Buttons/PanelViewButton";
|
||||
|
||||
export default {
|
||||
@@ -140,12 +140,13 @@ export default {
|
||||
};
|
||||
},
|
||||
sections() {
|
||||
return filterToolSections(this.toolsLayout, this.results);
|
||||
return filterToolSections(this.workflowTools, this.results);
|
||||
},
|
||||
toolsLayout() {
|
||||
return this.toolbox.map((section) => {
|
||||
return {
|
||||
...section,
|
||||
disabled: !section.elems && !section.is_workflow_compatible,
|
||||
elems:
|
||||
section.elems &&
|
||||
section.elems.map((el) => {
|
||||
@@ -155,6 +156,9 @@ export default {
|
||||
};
|
||||
});
|
||||
},
|
||||
workflowTools() {
|
||||
return removeDisabledTools(this.toolsLayout);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onQuery(query) {
|
||||
|
||||
@@ -89,7 +89,12 @@ export function searchToolsByKeys(tools, keys, query) {
|
||||
const returnedTools = [];
|
||||
for (const tool of tools) {
|
||||
for (const key of Object.keys(keys)) {
|
||||
const actualValue = tool[key] ? tool[key].toLowerCase() : "";
|
||||
let actualValue = "";
|
||||
if (key === "combined") {
|
||||
actualValue = tool.name.toLowerCase() + " " + tool.description.toLowerCase();
|
||||
} else {
|
||||
actualValue = tool[key] ? tool[key].toLowerCase() : "";
|
||||
}
|
||||
const queryLowerCase = query.toLowerCase();
|
||||
if (actualValue.match(queryLowerCase)) {
|
||||
// do we care for exact matches && is it an exact match ?
|
||||
@@ -109,6 +114,22 @@ export function normalizeTools(tools) {
|
||||
return tools;
|
||||
}
|
||||
|
||||
export function removeDisabledTools(tools) {
|
||||
return tools.filter((section) => {
|
||||
if (section.model_class === "ToolSectionLabel") {
|
||||
return true;
|
||||
} else if (!section.elems && section.disabled) {
|
||||
return false;
|
||||
} else if (section.elems) {
|
||||
section.elems = section.elems.filter((el) => !el.disabled);
|
||||
if (!section.elems.length) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function flattenToolsSection(section) {
|
||||
const flattenTools = [];
|
||||
if (section.elems) {
|
||||
|
||||
@@ -17,7 +17,7 @@ describe("test helpers in tool searching utilities", () => {
|
||||
});
|
||||
|
||||
it("test tool search helper that searches for tools given keys", async () => {
|
||||
const q = "collection";
|
||||
let q = "collection";
|
||||
let expectedResults = [
|
||||
"__FILTER_FAILED_DATASETS__",
|
||||
"__FILTER_EMPTY_DATASETS__",
|
||||
@@ -37,6 +37,12 @@ describe("test helpers in tool searching utilities", () => {
|
||||
keys = { description: 0, name: 1 };
|
||||
results = searchToolsByKeys(normalizeTools(toolsList), keys, q);
|
||||
expect(results).toEqual(expectedResults);
|
||||
|
||||
q = "filter empty datasets";
|
||||
expectedResults = ["__FILTER_EMPTY_DATASETS__"];
|
||||
keys = { description: 1, name: 2, combined: 0 };
|
||||
results = searchToolsByKeys(normalizeTools(toolsList), keys, q);
|
||||
expect(results).toEqual(expectedResults);
|
||||
});
|
||||
|
||||
it("test tool filtering helpers on toolsList given list of ids", async () => {
|
||||
|
||||
@@ -11,9 +11,7 @@ import { createTestingPinia } from "@pinia/testing";
|
||||
const localVue = getLocalVue();
|
||||
|
||||
describe("FormTool", () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
function mountTarget() {
|
||||
const store = new Vuex.Store({
|
||||
modules: {
|
||||
user: mockModule(userStore),
|
||||
@@ -21,7 +19,7 @@ describe("FormTool", () => {
|
||||
},
|
||||
});
|
||||
|
||||
wrapper = mount(FormTool, {
|
||||
return mount(FormTool, {
|
||||
propsData: {
|
||||
id: "input",
|
||||
datatypes: [],
|
||||
@@ -32,7 +30,7 @@ describe("FormTool", () => {
|
||||
name: "tool_name",
|
||||
version: "1.0",
|
||||
description: "description",
|
||||
inputs: [],
|
||||
inputs: [{ name: "input", label: "input", type: "text", value: "value" }],
|
||||
help: "help_text",
|
||||
versions: ["1.0", "2.0", "3.0"],
|
||||
citations: false,
|
||||
@@ -46,15 +44,16 @@ describe("FormTool", () => {
|
||||
stubs: {
|
||||
CurrentUser: MockCurrentUser({ id: "fakeuser" }),
|
||||
ConfigProvider: MockConfigProvider({ id: "fakeconfig" }),
|
||||
FormElement: { template: "<div>form-element</div>" },
|
||||
ToolFooter: { template: "<div>tool-footer</div>" },
|
||||
},
|
||||
pinia: createTestingPinia(),
|
||||
provide: { store },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it("changes between different versions", async () => {
|
||||
const wrapper = mountTarget();
|
||||
|
||||
const dropdowns = wrapper.findAll(".tool-versions .dropdown-item");
|
||||
let version = dropdowns.at(1);
|
||||
expect(version.text()).toBe("Switch to 2.0");
|
||||
|
||||
@@ -163,9 +163,16 @@ export default {
|
||||
onLabel(newLabel) {
|
||||
this.$emit("onLabel", this.stepId, newLabel);
|
||||
},
|
||||
/**
|
||||
* Change event is triggered on component creation and input changes.
|
||||
* @param { Object } values contains flat key-value pairs `prefixed-name=value`
|
||||
*/
|
||||
onChange(values) {
|
||||
const initialRequest = Object.keys(this.mainValues).length === 0;
|
||||
this.mainValues = values;
|
||||
this.postChanges();
|
||||
if (!initialRequest) {
|
||||
this.postChanges();
|
||||
}
|
||||
},
|
||||
onChangePostJobActions(postJobActions) {
|
||||
this.$emit("onChangePostJobActions", this.stepId, postJobActions);
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
<div>
|
||||
<FormTool
|
||||
v-if="hasActiveNodeTool"
|
||||
:key="activeStep.id"
|
||||
:step="activeStep"
|
||||
:datatypes="datatypes"
|
||||
@onChangePostJobActions="onChangePostJobActions"
|
||||
@@ -253,6 +254,10 @@ export default {
|
||||
const hasChanges = ref(false);
|
||||
const hasInvalidConnections = computed(() => Object.keys(connectionsStore.invalidConnections).length > 0);
|
||||
|
||||
stepStore.$subscribe((mutation, state) => {
|
||||
hasChanges.value = true;
|
||||
});
|
||||
|
||||
function resetStores() {
|
||||
connectionsStore.$reset();
|
||||
stepStore.$reset();
|
||||
@@ -341,9 +346,6 @@ export default {
|
||||
this.hasChanges = true;
|
||||
}
|
||||
},
|
||||
steps(newSteps, oldSteps) {
|
||||
this.hasChanges = true;
|
||||
},
|
||||
hasChanges() {
|
||||
this.$emit("update:confirmation", this.hasChanges);
|
||||
},
|
||||
@@ -356,7 +358,6 @@ export default {
|
||||
methods: {
|
||||
onUpdateStep(step) {
|
||||
this.stepStore.updateStep(step);
|
||||
this.hasChanges = true;
|
||||
},
|
||||
onUpdateStepPosition(stepId, position) {
|
||||
const step = { ...this.steps[stepId], position };
|
||||
@@ -408,6 +409,7 @@ export default {
|
||||
hide_modal(); // hide other modals created in utilities also...
|
||||
},
|
||||
async onRefactor(response) {
|
||||
this.resetStores();
|
||||
await fromSimple(response.workflow);
|
||||
this._loadEditorData(response.workflow);
|
||||
},
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
@move="onMoveTo"
|
||||
@pan-by="onPanBy">
|
||||
<div class="node-header unselectable clearfix" @click="makeActive" @keyup.enter="makeActive">
|
||||
<loading-span v-if="isLoading" message="Loading details" />
|
||||
<b-button-group class="float-right">
|
||||
<loading-span v-if="isLoading" spinner-only />
|
||||
<b-button
|
||||
v-if="canClone"
|
||||
v-b-tooltip.hover
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ref } from "vue";
|
||||
|
||||
export function useUniqueLabelError(
|
||||
workflowStateStore: ReturnType<typeof useWorkflowStepStore>,
|
||||
label: string | undefined
|
||||
label: string | null | undefined
|
||||
) {
|
||||
const error = ref("");
|
||||
if (label && workflowStateStore.workflowOutputs[label]) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { setActivePinia, createPinia } from "pinia";
|
||||
|
||||
import { useWorkflowStepStore } from "@/stores/workflowStepStore";
|
||||
import { useWorkflowStepStore, type TerminalSource } from "@/stores/workflowStepStore";
|
||||
import {
|
||||
terminalFactory,
|
||||
InputCollectionTerminal,
|
||||
@@ -15,7 +15,11 @@ import {
|
||||
import { testDatatypesMapper } from "@/components/Datatypes/test_fixtures";
|
||||
import { useConnectionStore } from "@/stores/workflowConnectionStore";
|
||||
import type { DataOutput, Steps } from "@/stores/workflowStepStore";
|
||||
import { NULL_COLLECTION_TYPE_DESCRIPTION } from "./collectionTypeDescription";
|
||||
import {
|
||||
ANY_COLLECTION_TYPE_DESCRIPTION,
|
||||
CollectionTypeDescription,
|
||||
NULL_COLLECTION_TYPE_DESCRIPTION,
|
||||
} from "./collectionTypeDescription";
|
||||
import { simpleSteps, advancedSteps } from "../test_fixtures";
|
||||
|
||||
function setupAdvanced() {
|
||||
@@ -35,6 +39,17 @@ function setupAdvanced() {
|
||||
return terminals;
|
||||
}
|
||||
|
||||
function rebuildTerminal<T extends ReturnType<typeof terminalFactory>>(terminal: T): T {
|
||||
let terminalSource: TerminalSource;
|
||||
const step = terminal.stepStore.getStep(terminal.stepId);
|
||||
if (terminal.terminalType === "input") {
|
||||
terminalSource = step.inputs.find((input) => input.name == terminal.name)!;
|
||||
} else {
|
||||
terminalSource = step.outputs.find((output) => output.name == terminal.name)!;
|
||||
}
|
||||
return terminalFactory(terminal.stepId, terminalSource, testDatatypesMapper) as T;
|
||||
}
|
||||
|
||||
describe("terminalFactory", () => {
|
||||
let terminals: { [index: string]: { [index: string]: ReturnType<typeof terminalFactory> } } = {};
|
||||
beforeEach(() => {
|
||||
@@ -64,6 +79,8 @@ describe("terminalFactory", () => {
|
||||
expect(terminals["any collection"]["output"]).toBeInstanceOf(OutputCollectionTerminal);
|
||||
expect(terminals["multi data"]["advanced|advanced_threshold"]).toBeInstanceOf(InputParameterTerminal);
|
||||
expect(terminals["list collection input"]["input1"]).toBeInstanceOf(InputCollectionTerminal);
|
||||
expect(terminals["filter_failed"]["input"]).toBeInstanceOf(InputCollectionTerminal);
|
||||
expect(terminals["filter_failed"]["output"]).toBeInstanceOf(OutputCollectionTerminal);
|
||||
});
|
||||
it("throws error on invalid terminalSource", () => {
|
||||
const invalidFactory = () => terminalFactory(1, {} as any, testDatatypesMapper);
|
||||
@@ -257,7 +274,7 @@ describe("canAccept", () => {
|
||||
"Can't map over this input with output collection type - this step has outputs defined constraining the mapping of this tool. Disconnect outputs and retry."
|
||||
);
|
||||
});
|
||||
it("rejects attaching non-collection outputs to mapper over inputs", () => {
|
||||
it("rejects attaching non-collection outputs to mapped-over inputs", () => {
|
||||
const collectionOut = terminals["list input"]["output"] as OutputCollectionTerminal;
|
||||
const simpleDataOut = terminals["data input"]["output"] as OutputTerminal;
|
||||
const dataIn = terminals["simple data"]["input"] as InputTerminal;
|
||||
@@ -281,11 +298,7 @@ describe("canAccept", () => {
|
||||
dataInTwo.disconnect(dataOut);
|
||||
// terminal isn't a reactive class in any way (worth a thought doing, but difficult!), the following happens when a new terminal is built
|
||||
// in useTerminal.ts
|
||||
const rebuiltDataIn = terminalFactory(
|
||||
dataIn.stepId,
|
||||
advancedSteps[dataIn.stepId].inputs[0],
|
||||
testDatatypesMapper
|
||||
);
|
||||
const rebuiltDataIn = rebuildTerminal(dataIn);
|
||||
expect(rebuiltDataIn.localMapOver.isCollection).toBe(false);
|
||||
expect(rebuiltDataIn.canAccept(simpleDataOut).canAccept).toBe(true);
|
||||
});
|
||||
@@ -389,7 +402,9 @@ describe("canAccept", () => {
|
||||
const collectionOut = terminals["list:list input"]["output"] as OutputCollectionTerminal;
|
||||
const collectionIn = terminals["list collection input"]["input1"] as InputCollectionTerminal;
|
||||
expect(collectionIn.canAccept(collectionOut).canAccept).toBe(true);
|
||||
expect(collectionIn.isMappedOver()).toBe(false);
|
||||
collectionIn.connect(collectionOut);
|
||||
expect(collectionIn.isMappedOver()).toBe(true);
|
||||
expect(collectionIn.mapOver).toEqual({ collectionType: "list", isCollection: true, rank: 1 });
|
||||
const intermediateOut = terminals["list collection input"]["out_file1"] as OutputCollectionTerminal;
|
||||
const otherListIn = terminals["list collection input 2"]["input1"] as InputCollectionTerminal;
|
||||
@@ -459,6 +474,20 @@ describe("canAccept", () => {
|
||||
"Effective output data type(s) [tabular] do not appear to match input type(s) [ab1]."
|
||||
);
|
||||
});
|
||||
it("resolves collection type source", () => {
|
||||
const filterFailedInput = terminals["filter_failed"]["input"] as InputCollectionTerminal;
|
||||
const filterFailedOutput = terminals["filter_failed"]["output"] as OutputCollectionTerminal;
|
||||
expect(filterFailedOutput.collectionType).toBe(ANY_COLLECTION_TYPE_DESCRIPTION);
|
||||
const collectionOut = terminals["list input"]["output"] as OutputCollectionTerminal;
|
||||
filterFailedInput.connect(collectionOut);
|
||||
expect(rebuildTerminal(filterFailedOutput).collectionType).toStrictEqual(new CollectionTypeDescription("list"));
|
||||
filterFailedInput.disconnect(collectionOut);
|
||||
const listPairedOutput = terminals["list:paired input"]["output"] as OutputCollectionTerminal;
|
||||
filterFailedInput.connect(listPairedOutput);
|
||||
expect(rebuildTerminal(filterFailedOutput).collectionType).toStrictEqual(
|
||||
new CollectionTypeDescription("list:paired")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Input terminal", () => {
|
||||
|
||||
@@ -97,6 +97,7 @@ class Terminal extends EventEmitter {
|
||||
this.resetMappingIfNeeded(connection);
|
||||
}
|
||||
setMapOver(val: CollectionTypeDescriptor) {
|
||||
// we use this method to determine the map over status based on all terminals connected to this step
|
||||
let outputVal = val;
|
||||
if (this.multiple) {
|
||||
// emulate list input
|
||||
@@ -112,7 +113,11 @@ class Terminal extends EventEmitter {
|
||||
this.stepStore.changeStepInputMapOver(this.stepId, this.name, effectiveMapOver);
|
||||
this.localMapOver = effectiveMapOver;
|
||||
}
|
||||
if (!this.mapOver.equal(effectiveMapOver)) {
|
||||
if (
|
||||
!this.mapOver.equal(effectiveMapOver) &&
|
||||
(effectiveMapOver.isCollection ||
|
||||
!Object.values(this.stepStore.stepInputMapOver[this.stepId]).find((mapOver) => mapOver.isCollection))
|
||||
) {
|
||||
this.stepStore.changeStepMapOver(this.stepId, effectiveMapOver);
|
||||
}
|
||||
}
|
||||
@@ -242,6 +247,11 @@ class BaseInputTerminal extends Terminal {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
isMappedOver(): boolean {
|
||||
return Boolean(this.localMapOver.isCollection);
|
||||
}
|
||||
|
||||
connected() {
|
||||
return Boolean(this._getFirstOutputTerminal());
|
||||
}
|
||||
@@ -400,10 +410,6 @@ export class InputTerminal extends BaseInputTerminal {
|
||||
this.getStepMapOver();
|
||||
}
|
||||
|
||||
isMappedOver(): boolean {
|
||||
return Boolean(this.localMapOver.isCollection);
|
||||
}
|
||||
|
||||
attachable(other: BaseOutputTerminal) {
|
||||
const otherCollectionType = this._otherCollectionType(other);
|
||||
const mapOver = this.mapOver;
|
||||
@@ -490,10 +496,10 @@ export class InputParameterTerminal extends BaseInputTerminal {
|
||||
const effectiveThisType = this.effectiveType(this.type);
|
||||
const otherType = ("type" in other && other.type) || "data";
|
||||
const effectiveOtherType = this.effectiveType(otherType);
|
||||
const canAccept = effectiveThisType === effectiveOtherType
|
||||
const canAccept = effectiveThisType === effectiveOtherType;
|
||||
return new ConnectionAcceptable(
|
||||
canAccept,
|
||||
canAccept ? null: `Cannot attach a ${effectiveOtherType} parameter to a ${effectiveThisType} input`
|
||||
canAccept ? null : `Cannot attach a ${effectiveOtherType} parameter to a ${effectiveThisType} input`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -672,19 +678,56 @@ interface OutputCollectionTerminalArgs extends BaseOutputTerminalArgs {
|
||||
}
|
||||
|
||||
export class OutputCollectionTerminal extends BaseOutputTerminal {
|
||||
collectionTypeSource: string | null;
|
||||
|
||||
constructor(attr: OutputCollectionTerminalArgs) {
|
||||
super(attr);
|
||||
this.collectionTypeSource = null;
|
||||
if (attr.collection_type) {
|
||||
this.collectionType = new CollectionTypeDescription(attr.collection_type);
|
||||
} else {
|
||||
const collectionTypeSource = attr.collection_type_source;
|
||||
if (!collectionTypeSource) {
|
||||
this.collectionTypeSource = attr.collection_type_source;
|
||||
if (!this.collectionTypeSource) {
|
||||
console.log("Warning: No collection type or collection type source defined.");
|
||||
}
|
||||
this.collectionType = ANY_COLLECTION_TYPE_DESCRIPTION;
|
||||
this.collectionType = this.getCollectionTypeFromInput() || ANY_COLLECTION_TYPE_DESCRIPTION;
|
||||
}
|
||||
this.isCollection = true;
|
||||
}
|
||||
|
||||
getCollectionTypeFromInput() {
|
||||
const connection = this.connectionStore.connections.find(
|
||||
(connection) =>
|
||||
connection.input.name === this.collectionTypeSource && connection.input.stepId === this.stepId
|
||||
);
|
||||
if (connection) {
|
||||
const outputStep = this.stepStore.getStep(connection.output.stepId);
|
||||
const inputStep = this.stepStore.getStep(this.stepId);
|
||||
if (outputStep) {
|
||||
const stepOutput = outputStep.outputs.find((output) => output.name == connection.output.name);
|
||||
const stepInput = inputStep.inputs.find((input) => input.name === this.collectionTypeSource);
|
||||
if (stepInput && stepOutput) {
|
||||
const outputTerminal = terminalFactory(connection.output.stepId, stepOutput, this.datatypesMapper);
|
||||
const inputTerminal = terminalFactory(connection.output.stepId, stepInput, this.datatypesMapper);
|
||||
// otherCollectionType is the mapped over output collection as it would appear at the input terminal
|
||||
const otherCollectionType = inputTerminal._otherCollectionType(outputTerminal);
|
||||
// we need to find which of the possible input collection types is connected
|
||||
if ("collectionTypes" in inputTerminal) {
|
||||
// collection_type_source must point at input collection terminal
|
||||
const connectedCollectionType = inputTerminal.collectionTypes.find(
|
||||
(collectionType) =>
|
||||
otherCollectionType.canMatch(collectionType) ||
|
||||
otherCollectionType.canMapOver(collectionType)
|
||||
);
|
||||
if (connectedCollectionType) {
|
||||
return connectedCollectionType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ANY_COLLECTION_TYPE_DESCRIPTION;
|
||||
}
|
||||
}
|
||||
|
||||
interface OutputParameterTerminalArgs extends Omit<BaseOutputTerminalArgs, "datatypes"> {
|
||||
|
||||
@@ -812,5 +812,99 @@
|
||||
"left": 70.45245361328125,
|
||||
"top": 316.3525695800781
|
||||
}
|
||||
},
|
||||
"17": {
|
||||
"id": 17,
|
||||
"type": "tool",
|
||||
"label": "filter_failed",
|
||||
"content_id": "__FILTER_FAILED_DATASETS__",
|
||||
"name": "Filter failed datasets",
|
||||
"tool_state": {
|
||||
"input": "{\"__class__\": \"RuntimeValue\"}",
|
||||
"__page__": null,
|
||||
"__rerun_remap_job_id__": null
|
||||
},
|
||||
"errors": null,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "input",
|
||||
"label": "Input Collection",
|
||||
"multiple": false,
|
||||
"input_type": "dataset_collection",
|
||||
"collection_types": [
|
||||
"list",
|
||||
"list:paired"
|
||||
],
|
||||
"optional": false,
|
||||
"extensions": [
|
||||
"data"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "output",
|
||||
"extensions": [
|
||||
"input"
|
||||
],
|
||||
"type": "collection",
|
||||
"optional": false,
|
||||
"collection": true,
|
||||
"collection_type": null,
|
||||
"collection_type_source": "input",
|
||||
"label": "input dataset(s) (filtered failed datasets)"
|
||||
}
|
||||
],
|
||||
"annotation": "",
|
||||
"post_job_actions": {},
|
||||
"uuid": "f5de18b9-b08e-4160-b71b-54768a4e5208",
|
||||
"when": null,
|
||||
"workflow_outputs": [
|
||||
{
|
||||
"output_name": "output",
|
||||
"uuid": "3774e554-b7d1-41ba-9acd-ee82ba9fbf54",
|
||||
"label": null
|
||||
}
|
||||
],
|
||||
"input_connections": {},
|
||||
"position": {
|
||||
"left": 0,
|
||||
"top": 0
|
||||
}
|
||||
},
|
||||
"18": {
|
||||
"id": 18,
|
||||
"type": "data_collection_input",
|
||||
"label": "list:paired input",
|
||||
"content_id": null,
|
||||
"name": "Input dataset collection",
|
||||
"tool_state": {
|
||||
"collection_type": "\"list:paired\"",
|
||||
"optional": "\"false\"",
|
||||
"tag": "\"\"",
|
||||
"__page__": null,
|
||||
"__rerun_remap_job_id__": null
|
||||
},
|
||||
"errors": null,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "output",
|
||||
"extensions": [
|
||||
"input"
|
||||
],
|
||||
"collection": true,
|
||||
"collection_type": "list:paired",
|
||||
"optional": false
|
||||
}
|
||||
],
|
||||
"annotation": "",
|
||||
"post_job_actions": {},
|
||||
"workflow_outputs": [],
|
||||
"input_connections": {},
|
||||
"position": {
|
||||
"left": 12.8729248046875,
|
||||
"top": 501.7839660644531
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ export interface BaseTerminal {
|
||||
|
||||
export interface InputTerminal extends BaseTerminal {
|
||||
connectorType: "input";
|
||||
input_subworkflow_step_id?: number;
|
||||
}
|
||||
|
||||
export interface OutputTerminal extends BaseTerminal {
|
||||
|
||||
@@ -63,6 +63,7 @@ interface BaseStepInput {
|
||||
extensions: string[];
|
||||
optional: boolean;
|
||||
input_type: string;
|
||||
input_subworkflow_step_id?: number;
|
||||
}
|
||||
|
||||
export interface DataStepInput extends BaseStepInput {
|
||||
@@ -85,8 +86,8 @@ export type TerminalSource = InputTerminalSource | OutputTerminalSource;
|
||||
|
||||
interface WorkflowOutput {
|
||||
output_name: string;
|
||||
label?: string;
|
||||
uuid?: string;
|
||||
label?: string | null;
|
||||
uuid?: string | null;
|
||||
}
|
||||
|
||||
export interface NewStep {
|
||||
@@ -97,7 +98,7 @@ export interface NewStep {
|
||||
errors?: string[] | null;
|
||||
input_connections: StepInputConnection;
|
||||
inputs: Array<InputTerminalSource>;
|
||||
label?: string;
|
||||
label?: string | null;
|
||||
name: string;
|
||||
outputs: Array<OutputTerminalSource>;
|
||||
position?: StepPosition;
|
||||
@@ -106,7 +107,7 @@ export interface NewStep {
|
||||
tooltip?: string;
|
||||
type: "tool" | "data_input" | "data_collection_input" | "subworkflow" | "parameter_input" | "pause";
|
||||
uuid?: string;
|
||||
when?: string;
|
||||
when?: string | null;
|
||||
workflow_outputs?: WorkflowOutput[];
|
||||
}
|
||||
|
||||
@@ -125,6 +126,7 @@ export interface StepInputConnection {
|
||||
export interface ConnectionOutputLink {
|
||||
output_name: string;
|
||||
id: number;
|
||||
input_subworkflow_step_id?: number;
|
||||
}
|
||||
|
||||
interface WorkflowOutputs {
|
||||
@@ -231,11 +233,19 @@ export const useWorkflowStepStore = defineStore("workflowStepStore", {
|
||||
},
|
||||
addConnection(connection: Connection) {
|
||||
const inputStep = this.getStep(connection.input.stepId);
|
||||
const input = inputStep.inputs.find((input) => input.name === connection.input.name);
|
||||
const connectionLink: ConnectionOutputLink = {
|
||||
output_name: connection.output.name,
|
||||
id: connection.output.stepId,
|
||||
};
|
||||
if (input && "input_subworkflow_step_id" in input && input.input_subworkflow_step_id !== undefined) {
|
||||
connectionLink["input_subworkflow_step_id"] = input.input_subworkflow_step_id;
|
||||
}
|
||||
const updatedStep = {
|
||||
...inputStep,
|
||||
input_connections: {
|
||||
...inputStep.input_connections,
|
||||
[connection.input.name]: { output_name: connection.output.name, id: connection.output.stepId },
|
||||
[connection.input.name]: connectionLink,
|
||||
},
|
||||
};
|
||||
this.updateStep(updatedStep);
|
||||
@@ -258,7 +268,7 @@ export const useWorkflowStepStore = defineStore("workflowStepStore", {
|
||||
export function stepToConnections(step: Step): Connection[] {
|
||||
const connections: Connection[] = [];
|
||||
if (step.input_connections) {
|
||||
Object.entries(step?.input_connections).forEach(([input_name, outputArray]) => {
|
||||
Object.entries(step?.input_connections).forEach(([inputName, outputArray]) => {
|
||||
if (outputArray === undefined) {
|
||||
return;
|
||||
}
|
||||
@@ -269,7 +279,7 @@ export function stepToConnections(step: Step): Connection[] {
|
||||
const connection = new Connection(
|
||||
{
|
||||
stepId: step.id,
|
||||
name: input_name,
|
||||
name: inputName,
|
||||
connectorType: "input",
|
||||
},
|
||||
{
|
||||
@@ -278,6 +288,10 @@ export function stepToConnections(step: Step): Connection[] {
|
||||
connectorType: "output",
|
||||
}
|
||||
);
|
||||
const connectionInput = step.inputs.find((input) => input.name == inputName);
|
||||
if (connectionInput && "input_subworkflow_step_id" in connectionInput) {
|
||||
connection.input.input_subworkflow_step_id = connectionInput.input_subworkflow_step_id;
|
||||
}
|
||||
connections.push(connection);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,15 +6,14 @@ tags:
|
||||
- "UI"
|
||||
- "visualization"
|
||||
|
||||
requirements:
|
||||
- new_history
|
||||
|
||||
steps:
|
||||
- content: "This short tour will walk you through <b>Galaxy's Window Manager</b> feature"
|
||||
|
||||
- element: "#current-history-panel .create-hist-btn"
|
||||
intro: "Before using the Window Manager, we will create a new history for this tour."
|
||||
postclick: true
|
||||
|
||||
- element: "#tool-panel-upload-button"
|
||||
intro: "Now, we will upload some tabular data into our new history."
|
||||
intro: "First, we will upload some tabular data into our new history."
|
||||
postclick: true
|
||||
|
||||
- element: "#btn-new"
|
||||
@@ -78,7 +77,7 @@ steps:
|
||||
|
||||
- element: "#right"
|
||||
intro: "This is your history. It contains our two tables being uploaded."
|
||||
|
||||
|
||||
- component: history_panel.item(hid=1,state=ok).display_button
|
||||
intro: "Clicking the eye-icon usually displays a dataset in the center panel."
|
||||
postclick: true
|
||||
|
||||
@@ -7163,8 +7163,20 @@ class WorkflowStep(Base, RepresentById):
|
||||
conn.output_name = output_name
|
||||
add_object_to_object_session(conn, output_step)
|
||||
conn.output_step = output_step
|
||||
if input_subworkflow_step_index is not None:
|
||||
input_subworkflow_step = self.subworkflow.step_by_index(input_subworkflow_step_index)
|
||||
if self.subworkflow:
|
||||
if input_subworkflow_step_index is not None:
|
||||
input_subworkflow_step = self.subworkflow.step_by_index(input_subworkflow_step_index)
|
||||
else:
|
||||
input_subworkflow_steps = [step for step in self.subworkflow.input_steps if step.label == input_name]
|
||||
if not input_subworkflow_steps:
|
||||
inferred_order_index = input_name.split(":", 1)[0]
|
||||
if inferred_order_index.isdigit():
|
||||
input_subworkflow_steps = [self.subworkflow.step_by_index(int(inferred_order_index))]
|
||||
if len(input_subworkflow_steps) != 1:
|
||||
raise galaxy.exceptions.MessageException(
|
||||
f"Invalid subworkflow connection at step index {self.order_index + 1}"
|
||||
)
|
||||
input_subworkflow_step = input_subworkflow_steps[0]
|
||||
conn.input_subworkflow_step = input_subworkflow_step
|
||||
return conn
|
||||
|
||||
|
||||
@@ -4,12 +4,10 @@ adapt cwltool to Galaxy features and abstract the library away from the rest
|
||||
of the framework.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import copy
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pickle
|
||||
from abc import (
|
||||
ABCMeta,
|
||||
abstractmethod,
|
||||
@@ -31,7 +29,6 @@ from galaxy.exceptions import MessageException
|
||||
from galaxy.util import (
|
||||
listify,
|
||||
safe_makedirs,
|
||||
unicodify,
|
||||
)
|
||||
from galaxy.util.bunch import Bunch
|
||||
from .cwltool_deps import (
|
||||
@@ -86,8 +83,6 @@ SUPPORTED_TOOL_REQUIREMENTS = [
|
||||
|
||||
SUPPORTED_WORKFLOW_REQUIREMENTS = SUPPORTED_TOOL_REQUIREMENTS + []
|
||||
|
||||
PERSISTED_REPRESENTATION = "cwl_tool_object"
|
||||
|
||||
ToolStateType = Dict[str, Union[None, str, bool, Dict[str, str]]]
|
||||
|
||||
|
||||
@@ -173,29 +168,29 @@ class ToolProxy(metaclass=ABCMeta):
|
||||
def to_persistent_representation(self):
|
||||
"""Return a JSON representation of this tool. Not for serialization
|
||||
over the wire, but serialization in a database."""
|
||||
# TODO: Replace this with some more readable serialization,
|
||||
# I really don't like using pickle here.
|
||||
if PERSISTED_REPRESENTATION == "cwl_tool_object":
|
||||
persisted_obj = remove_pickle_problems(self._tool)
|
||||
else:
|
||||
persisted_obj = self._raw_process_reference
|
||||
persisted_obj = self._tool.tool
|
||||
persisted_obj["requirements"] = self.requirements
|
||||
if not persisted_obj.get("cwlVersion"):
|
||||
# This happens for any inline process, but getting it from metadata is correct for inline processes at least
|
||||
persisted_obj["cwlVersion"] = self._tool.metadata["cwlVersion"]
|
||||
return {
|
||||
"class": self._class,
|
||||
"pickle": unicodify(base64.b64encode(pickle.dumps(persisted_obj, pickle.HIGHEST_PROTOCOL))),
|
||||
# Should maybe be yaml instead
|
||||
"raw_process_reference": persisted_obj,
|
||||
"uuid": self.uuid,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def from_persistent_representation(as_object) -> "ToolProxy":
|
||||
def from_persistent_representation(as_object, strict_cwl_validation=True, tool_directory=None) -> "ToolProxy":
|
||||
"""Recover an object serialized with to_persistent_representation."""
|
||||
if "class" not in as_object:
|
||||
raise Exception("Failed to deserialize tool proxy from JSON object - no class found.")
|
||||
if "pickle" not in as_object:
|
||||
raise Exception("Failed to deserialize tool proxy from JSON object - no pickle representation found.")
|
||||
if "uuid" not in as_object:
|
||||
raise Exception("Failed to deserialize tool proxy from JSON object - no uuid found.")
|
||||
to_unpickle = base64.b64decode(as_object["pickle"])
|
||||
loaded_object = pickle.loads(to_unpickle)
|
||||
loaded_object = tool_proxy(
|
||||
tool_object=as_object["raw_process_reference"],
|
||||
strict_cwl_validation=strict_cwl_validation,
|
||||
tool_directory=tool_directory,
|
||||
uuid=as_object.get("uuid"),
|
||||
)
|
||||
return loaded_object
|
||||
|
||||
@property
|
||||
@@ -743,14 +738,8 @@ def tool_proxy_from_persistent_representation(
|
||||
) -> ToolProxy:
|
||||
"""Load a ToolProxy from a previously persisted representation."""
|
||||
ensure_cwltool_available()
|
||||
if PERSISTED_REPRESENTATION == "cwl_tool_object":
|
||||
kwds = {"cwl_tool_object": ToolProxy.from_persistent_representation(persisted_tool)}
|
||||
else:
|
||||
raw_process_reference = persisted_tool # ???
|
||||
kwds = {"raw_process_reference": ToolProxy.from_persistent_representation(raw_process_reference)}
|
||||
uuid = persisted_tool["uuid"]
|
||||
return _to_cwl_tool_object(
|
||||
uuid=uuid, strict_cwl_validation=strict_cwl_validation, tool_directory=tool_directory, **kwds
|
||||
return ToolProxy.from_persistent_representation(
|
||||
persisted_tool, strict_cwl_validation=strict_cwl_validation, tool_directory=tool_directory
|
||||
)
|
||||
|
||||
|
||||
@@ -765,15 +754,10 @@ def load_job_proxy(job_directory, strict_cwl_validation=True) -> JobProxy:
|
||||
job_objects = json.load(open(job_objects_path))
|
||||
job_inputs = job_objects["job_inputs"]
|
||||
output_dict = job_objects["output_dict"]
|
||||
# Any reason to retain older tool_path variant of this? Probably not?
|
||||
if "tool_path" in job_objects:
|
||||
tool_path = job_objects["tool_path"]
|
||||
cwl_tool = tool_proxy(tool_path, strict_cwl_validation=strict_cwl_validation)
|
||||
else:
|
||||
persisted_tool = job_objects["tool_representation"]
|
||||
cwl_tool = tool_proxy_from_persistent_representation(
|
||||
persisted_tool=persisted_tool, strict_cwl_validation=strict_cwl_validation
|
||||
)
|
||||
persisted_tool = job_objects["tool_representation"]
|
||||
cwl_tool = tool_proxy_from_persistent_representation(
|
||||
persisted_tool=persisted_tool, strict_cwl_validation=strict_cwl_validation
|
||||
)
|
||||
return cwl_tool.job_proxy(job_inputs, output_dict, job_directory=job_directory)
|
||||
|
||||
|
||||
@@ -1107,17 +1091,6 @@ class SubworkflowStepProxy(BaseStepProxy):
|
||||
return self._subworkflow_proxy
|
||||
|
||||
|
||||
def remove_pickle_problems(obj):
|
||||
"""doc_loader does not pickle correctly"""
|
||||
if hasattr(obj, "doc_loader"):
|
||||
obj.doc_loader = None
|
||||
if hasattr(obj, "embedded_tool"):
|
||||
obj.embedded_tool = remove_pickle_problems(obj.embedded_tool)
|
||||
if hasattr(obj, "steps"):
|
||||
obj.steps = [remove_pickle_problems(s) for s in obj.steps]
|
||||
return obj
|
||||
|
||||
|
||||
def _outer_field_to_input_instance(field):
|
||||
field_type = field_to_field_type(field) # Must be a list if in here?
|
||||
if not isinstance(field_type, list):
|
||||
|
||||
@@ -291,6 +291,10 @@ class DatasetInterface(BaseUIController, UsesAnnotations, UsesItemRatings, UsesE
|
||||
if data.missing_meta():
|
||||
message = 'Required metadata values are missing. Some of these values may not be editable by the user. Selecting "Auto-detect" will attempt to fix these values.'
|
||||
status = "warning"
|
||||
metadata_disable = data.state not in [
|
||||
trans.model.Dataset.states.OK,
|
||||
trans.model.Dataset.states.FAILED_METADATA,
|
||||
]
|
||||
# datatype conversion
|
||||
conversion_options = [
|
||||
(f"{convert_id} (using '{convert_name}')", convert_id)
|
||||
@@ -369,6 +373,7 @@ class DatasetInterface(BaseUIController, UsesAnnotations, UsesItemRatings, UsesE
|
||||
"message": message,
|
||||
"status": status,
|
||||
"dataset_id": dataset_id,
|
||||
"metadata_disable": metadata_disable,
|
||||
"attribute_inputs": attribute_inputs,
|
||||
"conversion_inputs": conversion_inputs,
|
||||
"conversion_disable": conversion_disable,
|
||||
|
||||
@@ -2051,6 +2051,8 @@ steps:
|
||||
inputs:
|
||||
some_file:
|
||||
type: data
|
||||
should_run:
|
||||
type: boolean
|
||||
steps:
|
||||
a_tool_step:
|
||||
tool_id: cat1
|
||||
@@ -2154,6 +2156,7 @@ steps:
|
||||
class: GalaxyWorkflow
|
||||
inputs:
|
||||
boolean_input_file: data
|
||||
should_run: boolean
|
||||
steps:
|
||||
consume_expression_parameter:
|
||||
tool_id: cat1
|
||||
|
||||
@@ -1012,6 +1012,7 @@ steps:
|
||||
class: GalaxyWorkflow
|
||||
inputs:
|
||||
boolean_input_file: data
|
||||
should_run: boolean
|
||||
steps:
|
||||
create_more_inputs:
|
||||
tool_id: collection_creates_dynamic_nested
|
||||
|
||||
@@ -697,7 +697,10 @@ steps:
|
||||
workflow_populator.upload_yaml_workflow(WORKFLOW_OPTIONAL_TRUE_INPUT_COLLECTION, name=child_workflow_name)
|
||||
parent_workflow_id = workflow_populator.upload_yaml_workflow(
|
||||
"""class: GalaxyWorkflow
|
||||
inputs: []
|
||||
inputs:
|
||||
input_collection:
|
||||
type: collection
|
||||
collection_type: "list"
|
||||
steps:
|
||||
- tool_id: multiple_versions
|
||||
tool_version: 0.1
|
||||
@@ -716,10 +719,16 @@ steps:
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
self.assert_workflow_has_changes_and_save()
|
||||
workflow = self.workflow_populator.download_workflow(parent_workflow_id)
|
||||
subworkflow_step = workflow["steps"]["1"]
|
||||
subworkflow_step = workflow["steps"]["2"]
|
||||
assert subworkflow_step["name"] == child_workflow_name
|
||||
assert subworkflow_step["type"] == "subworkflow"
|
||||
assert subworkflow_step["subworkflow"]["a_galaxy_workflow"] == "true"
|
||||
self.workflow_editor_connect("input_collection#output", f"{child_workflow_name}#input1")
|
||||
self.assert_connected("input_collection#output", f"{child_workflow_name}#input1")
|
||||
self.assert_workflow_has_changes_and_save()
|
||||
workflow = self.workflow_populator.download_workflow(parent_workflow_id)
|
||||
subworkflow_step = workflow["steps"]["2"]
|
||||
assert subworkflow_step["input_connections"]["input1"]["input_subworkflow_step_id"] == 0
|
||||
|
||||
@selenium_test
|
||||
def test_editor_insert_steps(self):
|
||||
|
||||
Reference in New Issue
Block a user