Skip to content

Commit

Permalink
Merge pull request galaxyproject#16938 from ahmedhamidawan/remove_cre…
Browse files Browse the repository at this point in the history
…ate_wf_form

Remove "Create Workflow" form and allow workflow creation in editor
  • Loading branch information
martenson authored Nov 8, 2023
2 parents 307938e + 901e3bb commit 930e6fd
Show file tree
Hide file tree
Showing 24 changed files with 309 additions and 107 deletions.
2 changes: 1 addition & 1 deletion client/src/components/Panels/WorkflowBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function userTitle(title: string) {
variant="link"
:title="userTitle('Create new workflow')"
:disabled="isAnonymous"
@click="$router.push('/workflows/create')">
@click="$router.push('/workflows/edit')">
<Icon fixed-width icon="plus" />
</b-button>
<b-button
Expand Down
1 change: 1 addition & 0 deletions client/src/components/Workflow/Editor/Attributes.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ describe("Attributes", () => {
name: TEST_NAME,
tags: ["workflow_tag_0", "workflow_tag_1"],
parameters: untypedParameters,
version: 0,
versions: TEST_VERSIONS,
annotation: TEST_ANNOTATION,
},
Expand Down
18 changes: 12 additions & 6 deletions client/src/components/Workflow/Editor/Attributes.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@
<div id="workflow-name-area">
<b>Name</b>
<meta itemprop="name" :content="name" />
<b-input id="workflow-name" v-model="nameCurrent" @keyup="$emit('update:nameCurrent', nameCurrent)" />
<b-input
id="workflow-name"
v-model="nameCurrent"
:state="!nameCurrent ? false : null"
@keyup="$emit('update:nameCurrent', nameCurrent)" />
</div>
<div id="workflow-version-area" class="mt-2">
<div v-if="versionOptions.length > 0" id="workflow-version-area" class="mt-2">
<b>Version</b>
<b-form-select v-model="versionCurrent" @change="onVersion">
<b-form-select-option v-for="v in versionOptions" :key="v.version" :value="v.version">
Expand Down Expand Up @@ -184,7 +188,7 @@ export default {
onTags(tags) {
this.tagsCurrent = tags;
this.onAttributes({ tags });
this.$emit("input", this.tagsCurrent);
this.$emit("onTags", this.tagsCurrent);
},
onVersion() {
this.$emit("onVersion", this.versionCurrent);
Expand All @@ -200,9 +204,11 @@ export default {
this.messageVariant = "danger";
},
onAttributes(data) {
this.services.updateWorkflow(this.id, data).catch((error) => {
this.onError(error);
});
if (!this.id.includes("workflow-editor")) {
this.services.updateWorkflow(this.id, data).catch((error) => {
this.onError(error);
});
}
},
},
};
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/Workflow/Editor/Index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ describe("Index", () => {
});
wrapper = shallowMount(Index, {
propsData: {
id: "workflow_id",
workflowId: "workflow_id",
initialVersion: 1,
tags: ["moo", "cow"],
workflowTags: ["moo", "cow"],
moduleSections: [],
dataManagers: [],
workflows: [],
Expand Down
165 changes: 124 additions & 41 deletions client/src/components/Workflow/Editor/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
title="Save As a New Workflow"
ok-title="Save"
cancel-title="Cancel"
@ok="doSaveAs">
@ok="doSaveAs(false)">
<b-form-group label="Name">
<b-form-input v-model="saveAsName" />
</b-form-group>
Expand All @@ -41,7 +41,8 @@
<div class="unified-panel-header" unselectable="on">
<div class="unified-panel-header-inner">
<span class="sr-only">Workflow Editor</span>
{{ name }}
<span v-if="!isNewTempWorkflow || name">{{ name }}</span>
<i v-else>Create New Workflow</i>
</div>
</div>
<WorkflowGraph
Expand All @@ -68,9 +69,11 @@
<div class="unified-panel-header" unselectable="on">
<div class="unified-panel-header-inner">
<WorkflowOptions
:is-new-temp-workflow="isNewTempWorkflow"
:has-changes="hasChanges"
:has-invalid-connections="hasInvalidConnections"
@onSave="onSave"
@onCreate="onCreate"
@onSaveAs="onSaveAs"
@onRun="onRun"
@onDownload="onDownload"
Expand All @@ -83,7 +86,7 @@
</div>
</div>
<div ref="right-panel" class="unified-panel-body workflow-right p-2">
<div>
<div v-if="!initialLoading">
<FormTool
v-if="hasActiveNodeTool"
:key="activeStep.id"
Expand Down Expand Up @@ -118,6 +121,7 @@
:license="license"
:creator="creator"
@onVersion="onVersion"
@onTags="onTags"
@onLicense="onLicense"
@onCreator="onCreator" />
<WorkflowLint
Expand Down Expand Up @@ -162,14 +166,16 @@
<script>
import axios from "axios";
import { storeToRefs } from "pinia";
import Vue, { computed, onUnmounted, ref } from "vue";
import Vue, { computed, onUnmounted, ref, unref } from "vue";
import { getUntypedWorkflowParameters } from "@/components/Workflow/Editor/modules/parameters";
import { ConfirmDialog } from "@/composables/confirmDialog";
import { useDatatypesMapper } from "@/composables/datatypesMapper";
import { useUid } from "@/composables/utils/uid";
import { provideScopedWorkflowStores } from "@/composables/workflowStores";
import { hide_modal } from "@/layout/modal";
import { getAppRoot } from "@/onload/loadConfig";
import { useScopePointerStore } from "@/stores/scopePointerStore";
import { LastQueue } from "@/utils/promise-queue";
import { defaultPosition } from "./composables/useDefaultStepPosition";
Expand Down Expand Up @@ -207,17 +213,17 @@ export default {
WorkflowGraph,
},
props: {
id: {
workflowId: {
type: String,
required: true,
default: undefined,
},
initialVersion: {
type: Number,
required: true,
default: undefined,
},
tags: {
workflowTags: {
type: Array,
required: true,
default: () => [],
},
moduleSections: {
type: Array,
Expand All @@ -235,7 +241,10 @@ export default {
setup(props, { emit }) {
const { datatypes, datatypesMapper, datatypesMapperLoading } = useDatatypesMapper();
const { connectionStore, stepStore, stateStore, commentStore } = provideScopedWorkflowStores(props.id);
const uid = unref(useUid("workflow-editor-"));
const id = ref(props.workflowId || uid);
const { connectionStore, stepStore, stateStore, commentStore } = provideScopedWorkflowStores(id);
const { comments } = storeToRefs(commentStore);
const { getStepIndex, steps } = storeToRefs(stepStore);
Expand All @@ -248,14 +257,19 @@ export default {
});
const hasChanges = ref(false);
const initialLoading = ref(true);
const hasInvalidConnections = computed(() => Object.keys(connectionStore.invalidConnections).length > 0);
stepStore.$subscribe((_mutation, _state) => {
hasChanges.value = true;
if (!initialLoading.value) {
hasChanges.value = true;
}
});
commentStore.$subscribe((_mutation, _state) => {
hasChanges.value = true;
if (!initialLoading.value) {
hasChanges.value = true;
}
});
function resetStores() {
Expand All @@ -271,6 +285,7 @@ export default {
});
return {
id,
connectionStore,
hasChanges,
hasInvalidConnections,
Expand All @@ -285,6 +300,7 @@ export default {
datatypesMapperLoading,
stateStore,
resetStores,
initialLoading,
};
},
data() {
Expand All @@ -300,6 +316,7 @@ export default {
creator: null,
annotation: null,
name: null,
tags: this.workflowTags,
stateMessages: [],
insertedStateMessages: [],
refactorActions: [],
Expand Down Expand Up @@ -333,6 +350,9 @@ export default {
hasActiveNodeTool() {
return this.activeStep?.type == "tool";
},
isNewTempWorkflow() {
return !this.workflowId;
},
},
watch: {
id(newId, oldId) {
Expand All @@ -341,23 +361,29 @@ export default {
}
},
annotation(newAnnotation, oldAnnotation) {
if (newAnnotation != oldAnnotation) {
if (newAnnotation != oldAnnotation && !this.isNewTempWorkflow) {
this.hasChanges = true;
}
},
name(newName, oldName) {
if (newName != oldName) {
if (newName != oldName && !this.isNewTempWorkflow) {
this.hasChanges = true;
}
},
hasChanges() {
this.$emit("update:confirmation", this.hasChanges);
},
initialVersion(newVal, oldVal) {
if (newVal != oldVal && oldVal === undefined) {
this.version = this.initialVersion;
}
},
},
created() {
async created() {
this.lastQueue = new LastQueue();
this._loadCurrent(this.id, this.version);
await this._loadCurrent(this.id, this.version);
hide_modal();
this.initialLoading = false;
},
methods: {
onUpdateStep(step) {
Expand Down Expand Up @@ -505,9 +531,9 @@ export default {
onDownload() {
window.location = `${getAppRoot()}api/workflows/${this.id}/download?format=json-download`;
},
doSaveAs() {
const rename_name = this.saveAsName ?? `SavedAs_${this.name}`;
const rename_annotation = this.saveAsAnnotation ?? "";
async doSaveAs(create = false) {
const rename_name = create ? this.name : this.saveAsName ?? `SavedAs_${this.name}`;
const rename_annotation = create ? this.annotation || "" : this.saveAsAnnotation ?? "";
// This is an old web controller endpoint that wants form data posted...
const formData = new FormData();
Expand All @@ -516,16 +542,20 @@ export default {
formData.append("from_tool_form", true);
formData.append("workflow_data", JSON.stringify(toSimple(this.id, this)));
axios
.post(`${getAppRoot()}workflow/save_workflow_as`, formData)
.then((response) => {
this.onWorkflowMessage("Workflow saved as", "success");
this.hideModal();
this.onNavigate(`${getAppRoot()}workflows/edit?id=${response.data}`, true);
})
.catch((response) => {
this.onWorkflowError("Saving workflow failed, please contact an administrator.");
});
try {
const response = await axios.post(`${getAppRoot()}workflow/save_workflow_as`, formData);
const newId = response.data;
if (!create) {
this.name = rename_name;
this.annotation = rename_annotation;
}
this.hasChanges = false;
await this.routeToWorkflow(newId);
} catch (e) {
this.onWorkflowError("Saving workflow failed, please contact an administrator.");
}
},
onSaveAs() {
this.showSaveAsModal = true;
Expand All @@ -550,6 +580,53 @@ export default {
const step = { ...this.steps[nodeId], annotation: newAnnotation };
this.onUpdateStep(step);
},
async routeToWorkflow(id) {
// map scoped stores to existing stores, before updating the id
const { addScopePointer } = useScopePointerStore();
addScopePointer(id, this.id);
this.id = id;
await this.onSave();
this.hasChanges = false;
this.$router.replace({ query: { id } });
},
async onCreate() {
if (!this.name) {
const response = "Please provide a name for your workflow.";
this.onWorkflowError("Creating workflow failed", response, {
Ok: () => {
this.hideModal();
},
});
this.onAttributes();
return;
}
try {
// if nothing other than payload vars changed, just use `create` endpoint
if (!this.hasChanges) {
const payload = {
workflow_name: this.name,
workflow_annotation: this.annotation || "",
workflow_tags: this.tags,
};
const { data } = await axios.put(`${getAppRoot()}workflow/create`, payload);
const { id } = data;
await this.routeToWorkflow(id);
} else {
// otherwise, use `save_as` endpoint to include steps, etc.
await this.doSaveAs(true);
}
} catch (e) {
this.onWorkflowError("Creating workflow failed"),
e || "Please contact an administrator.",
{
Ok: () => {
this.hideModal();
},
};
}
},
onSetData(stepId, newData) {
this.lastQueue
.enqueue(() => getModule(newData, stepId, this.stateStore.setLoadingState))
Expand Down Expand Up @@ -695,18 +772,24 @@ export default {
await Vue.nextTick();
this.hasChanges = has_changes;
},
_loadCurrent(id, version) {
this.resetStores();
this.onWorkflowMessage("Loading workflow...", "progress");
this.lastQueue
.enqueue(loadWorkflow, { id, version })
.then((data) => {
fromSimple(id, data);
this._loadEditorData(data);
})
.catch((response) => {
this.onWorkflowError("Loading workflow failed...", response);
});
async _loadCurrent(id, version) {
if (!this.isNewTempWorkflow) {
this.resetStores();
this.onWorkflowMessage("Loading workflow...", "progress");
try {
const data = await this.lastQueue.enqueue(loadWorkflow, { id, version });
await fromSimple(id, data);
await this._loadEditorData(data);
} catch (e) {
this.onWorkflowError("Loading workflow failed...", e);
}
}
},
onTags(tags) {
if (this.tags != tags) {
this.tags = tags;
}
},
onLicense(license) {
if (this.license != license) {
Expand Down
Loading

0 comments on commit 930e6fd

Please sign in to comment.