Skip to content

Commit

Permalink
Remove "Create Workflow" form and allow workflow creation in editor
Browse files Browse the repository at this point in the history
Skip the step of Creating a workflow, and instead go straight into an
empty editor, add nodes, tags, connections...; workflow will only
be created once you Save and provide the worfklow a name and annotation.
Fixes galaxyproject#16912
  • Loading branch information
ahmedhamidawan committed Oct 31, 2023
1 parent 5b44e84 commit 2ff15b8
Show file tree
Hide file tree
Showing 12 changed files with 144 additions and 45 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
17 changes: 12 additions & 5 deletions client/src/components/Workflow/Editor/Attributes.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
<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">
<b>Version</b>
Expand All @@ -30,6 +34,7 @@
<b-textarea
id="workflow-annotation"
v-model="annotationCurrent"
:state="!annotationCurrent ? false : null"
@keyup="$emit('update:annotationCurrent', annotationCurrent)" />
<div class="form-text text-muted">These notes will be visible when this workflow is viewed.</div>
</div>
Expand Down Expand Up @@ -184,7 +189,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 +205,11 @@ export default {
this.messageVariant = "danger";
},
onAttributes(data) {
this.services.updateWorkflow(this.id, data).catch((error) => {
this.onError(error);
});
if (this.id !== "new_temp_workflow") {
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
69 changes: 61 additions & 8 deletions client/src/components/Workflow/Editor/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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 Down Expand Up @@ -118,6 +121,7 @@
:license="license"
:creator="creator"
@onVersion="onVersion"
@onTags="onTags"
@onLicense="onLicense"
@onCreator="onCreator" />
<WorkflowLint
Expand Down Expand Up @@ -171,6 +175,7 @@ import { provideScopedWorkflowStores } from "@/composables/workflowStores";
import { hide_modal } from "@/layout/modal";
import { getAppRoot } from "@/onload/loadConfig";
import { LastQueue } from "@/utils/promise-queue";
import { withPrefix } from "@/utils/redirect";
import { defaultPosition } from "./composables/useDefaultStepPosition";
import { fromSimple, toSimple } from "./modules/model";
Expand All @@ -191,6 +196,8 @@ import ToolPanel from "@/components/Panels/ToolPanel.vue";
import FormDefault from "@/components/Workflow/Editor/Forms/FormDefault.vue";
import FormTool from "@/components/Workflow/Editor/Forms/FormTool.vue";
const NEW_TEMP_ID = "new_temp_workflow";
export default {
components: {
MarkdownEditor,
Expand All @@ -207,17 +214,17 @@ export default {
WorkflowGraph,
},
props: {
id: {
workflowId: {
type: String,
required: true,
default: NEW_TEMP_ID,
},
initialVersion: {
type: Number,
required: true,
default: 0,
},
tags: {
workflowTags: {
type: Array,
required: true,
default: () => [],
},
moduleSections: {
type: Array,
Expand Down Expand Up @@ -289,6 +296,7 @@ export default {
},
data() {
return {
id: this.workflowId,
isCanvas: true,
markdownConfig: null,
markdownText: null,
Expand All @@ -300,6 +308,7 @@ export default {
creator: null,
annotation: null,
name: null,
tags: this.workflowTags,
stateMessages: [],
insertedStateMessages: [],
refactorActions: [],
Expand Down Expand Up @@ -333,10 +342,13 @@ export default {
hasActiveNodeTool() {
return this.activeStep?.type == "tool";
},
isNewTempWorkflow() {
return this.id === NEW_TEMP_ID;
},
},
watch: {
id(newId, oldId) {
if (oldId) {
if (oldId && oldId !== NEW_TEMP_ID) {
this._loadCurrent(newId);
}
},
Expand All @@ -356,7 +368,9 @@ export default {
},
created() {
this.lastQueue = new LastQueue();
this._loadCurrent(this.id, this.version);
if (!this.isNewTempWorkflow) {
this._loadCurrent(this.id, this.version);
}
hide_modal();
},
methods: {
Expand Down Expand Up @@ -550,6 +564,40 @@ export default {
const step = { ...this.steps[nodeId], annotation: newAnnotation };
this.onUpdateStep(step);
},
async onCreate() {
if (!this.name || !this.annotation) {
const response = "Must provide name and annotation before creation...";
this.onWorkflowError("Creating workflow failed...", response, {
Ok: () => {
this.hideModal();
},
});
this.onAttributes();
return;
}
const payload = {
workflow_name: this.name,
workflow_annotation: this.annotation,
workflow_tags: this.tags,
};
try {
const { data } = await axios.put(withPrefix("/workflow/create"), payload);
const { id, message } = data;
this.id = id;
this.onWorkflowMessage("Success", message);
const editUrl = `/workflows/edit?id=${id}`;
this.onNavigate(editUrl);
} catch (e) {
this.onWorkflowError("Couldn't create workflow..."),
e,
{
Ok: () => {
this.hideModal();
},
};
}
},
onSetData(stepId, newData) {
this.lastQueue
.enqueue(() => getModule(newData, stepId, this.stateStore.setLoadingState))
Expand Down Expand Up @@ -708,6 +756,11 @@ export default {
this.onWorkflowError("Loading workflow failed...", response);
});
},
onTags(tags) {
if (this.tags != tags) {
this.tags = tags;
}
},
onLicense(license) {
if (this.license != license) {
this.hasChanges = true;
Expand Down
31 changes: 23 additions & 8 deletions client/src/components/Workflow/Editor/Options.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useConfirmDialog } from "@/composables/confirmDialog";
const emit = defineEmits<{
(e: "onAttributes"): void;
(e: "onSave"): void;
(e: "onCreate"): void;
(e: "onReport"): void;
(e: "onSaveAs"): void;
(e: "onLayout"): void;
Expand All @@ -17,6 +18,7 @@ const emit = defineEmits<{
}>();
const props = defineProps<{
isNewTempWorkflow?: boolean;
hasChanges?: boolean;
hasInvalidConnections?: boolean;
requiredReindex?: boolean;
Expand All @@ -25,7 +27,9 @@ const props = defineProps<{
const { confirm } = useConfirmDialog();
const saveHover = computed(() => {
if (!props.hasChanges) {
if (props.isNewTempWorkflow) {
return "Create a new workflow";
} else if (!props.hasChanges) {
return "Workflow has no changes";
} else if (props.hasInvalidConnections) {
return "Workflow has invalid connections, review and remove invalid connections";
Expand All @@ -34,6 +38,14 @@ const saveHover = computed(() => {
}
});
function emitSaveOrCreate() {
if (props.isNewTempWorkflow) {
emit("onCreate");
} else {
emit("onSave");
}
}
async function onSave() {
if (props.hasInvalidConnections) {
console.log("getting confirmation");
Expand All @@ -45,18 +57,18 @@ async function onSave() {
}
);
if (confirmed) {
emit("onSave");
emitSaveOrCreate();
}
} else {
emit("onSave");
emitSaveOrCreate();
}
}
</script>
<template>
<div class="panel-header-buttons">
<BButton
id="workflow-home-button"
v-b-tooltip.hover
v-b-tooltip.hover.noninteractive
role="button"
title="Edit Attributes"
variant="link"
Expand All @@ -79,25 +91,27 @@ async function onSave() {
</b-button-group>
<BButton
id="workflow-report-button"
v-b-tooltip.hover
v-b-tooltip.hover.noninteractive
role="button"
title="Edit Report"
variant="link"
aria-label="Edit Report"
class="editor-button-report"
:disabled="isNewTempWorkflow"
@click="$emit('onReport')">
<span class="fa fa-edit" />
</BButton>
<BDropdown
id="workflow-options-button"
v-b-tooltip.hover
v-b-tooltip.hover.noninteractive
no-caret
right
role="button"
title="Workflow Options"
variant="link"
aria-label="Workflow Options"
class="editor-button-options">
class="editor-button-options"
:disabled="isNewTempWorkflow">
<template v-slot:button-content>
<span class="fa fa-cog" />
</template>
Expand All @@ -113,12 +127,13 @@ async function onSave() {
</BDropdown>
<BButton
id="workflow-run-button"
v-b-tooltip.hover
v-b-tooltip.hover.noninteractive
role="button"
title="Run Workflow"
variant="link"
aria-label="Run Workflow"
class="editor-button-run"
:disabled="isNewTempWorkflow"
@click="$emit('onRun')">
<span class="fa fa-play" />
</BButton>
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/Workflow/Editor/modules/services.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export async function loadWorkflow({ id, version = null }) {
export async function saveWorkflow(workflow) {
if (workflow.hasChanges) {
try {
const requestData = { workflow: toSimple(workflow.id, workflow), from_tool_form: true };
const requestData = { workflow: toSimple(workflow.id, workflow), from_tool_form: true, tags: workflow.tags };
const { data } = await axios.put(`${getAppRoot()}api/workflows/${workflow.id}`, requestData);
workflow.name = data.name;
workflow.hasChanges = false;
Expand Down
10 changes: 9 additions & 1 deletion client/src/components/Workflow/InvocationsList.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@ import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import { formatDistanceToNow, parseISO } from "date-fns";
import { getLocalVue } from "tests/jest/helpers";
import VueRouter from "vue-router";

import InvocationsList from "./InvocationsList";
import mockInvocationData from "./test/json/invocation.json";

const localVue = getLocalVue();
localVue.use(VueRouter);
const router = new VueRouter();

const pinia = createTestingPinia();
describe("InvocationsList.vue", () => {
let axiosMock;
let wrapper;
let $router;

beforeEach(async () => {
axiosMock = new MockAdapter(axios);
Expand Down Expand Up @@ -158,7 +162,9 @@ describe("InvocationsList.vue", () => {
},
localVue,
pinia,
router,
});
$router = wrapper.vm.$router;
});

it("renders one row", async () => {
Expand Down Expand Up @@ -194,8 +200,10 @@ describe("InvocationsList.vue", () => {
});

it("calls executeWorkflow", async () => {
const mockMethod = jest.fn();
$router.push = mockMethod;
await wrapper.find(".workflow-run").trigger("click");
expect(window.location).toBeAt("workflows/run?id=workflowId");
expect(mockMethod).toHaveBeenCalledWith("/workflows/run?id=workflowId");
});

it("should not render pager", async () => {
Expand Down
Loading

0 comments on commit 2ff15b8

Please sign in to comment.