mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-19 10:51:34 +08:00
Merge pull request #16523 from ElectronicBlueberry/workflow-store-scope
Scope Workflow Stores to Workflow ID
This commit is contained in:
@@ -25,7 +25,7 @@
|
||||
import { computed, type ComputedRef, onMounted, ref, watch } from "vue";
|
||||
|
||||
import { useFocusWithin } from "@/composables/useActiveElement";
|
||||
import { useWorkflowStepStore } from "@/stores/workflowStepStore";
|
||||
import { useWorkflowStores } from "@/composables/workflowStores";
|
||||
import { assertDefined } from "@/utils/assertions";
|
||||
|
||||
import {
|
||||
@@ -62,7 +62,7 @@ watch(focused, (focused) => {
|
||||
}
|
||||
});
|
||||
|
||||
const stepStore = useWorkflowStepStore();
|
||||
const { connectionStore, stepStore } = useWorkflowStores();
|
||||
|
||||
interface InputObject {
|
||||
stepId: number;
|
||||
@@ -97,7 +97,7 @@ function inputObjectToTerminal(inputObject: InputObject): InputTerminals {
|
||||
const step = stepStore.getStep(inputObject.stepId);
|
||||
assertDefined(step);
|
||||
const inputSource = step.inputs.find((input) => input.name == inputObject.inputName)!;
|
||||
return terminalFactory(inputObject.stepId, inputSource, props.terminal.datatypesMapper);
|
||||
return terminalFactory(inputObject.stepId, inputSource, props.terminal.datatypesMapper, connectionStore, stepStore);
|
||||
}
|
||||
|
||||
const validInputs: ComputedRef<InputObject[]> = computed(() => {
|
||||
|
||||
@@ -34,6 +34,9 @@ describe("FormDefault", () => {
|
||||
},
|
||||
localVue,
|
||||
pinia: createTestingPinia(),
|
||||
provide: {
|
||||
workflowId: "mock-workflow",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -62,7 +62,8 @@ import { computed, toRef } from "vue";
|
||||
|
||||
import type { DatatypesMapperModel } from "@/components/Datatypes/model";
|
||||
import WorkflowIcons from "@/components/Workflow/icons";
|
||||
import { type Step, useWorkflowStepStore } from "@/stores/workflowStepStore";
|
||||
import { useWorkflowStores } from "@/composables/workflowStores";
|
||||
import type { Step } from "@/stores/workflowStepStore";
|
||||
|
||||
import { useStepProps } from "../composables/useStepProps";
|
||||
import { useUniqueLabelError } from "../composables/useUniqueLabelError";
|
||||
@@ -80,7 +81,7 @@ const props = defineProps<{
|
||||
const emit = defineEmits(["onAnnotation", "onLabel", "onAttemptRefactor", "onEditSubworkflow", "onSetData"]);
|
||||
const stepRef = toRef(props, "step");
|
||||
const { stepId, contentId, annotation, label, name, type, configForm } = useStepProps(stepRef);
|
||||
const stepStore = useWorkflowStepStore();
|
||||
const { stepStore } = useWorkflowStores();
|
||||
const uniqueErrorLabel = useUniqueLabelError(stepStore, label.value);
|
||||
const stepTitle = computed(() => {
|
||||
if (label.value) {
|
||||
|
||||
@@ -29,7 +29,9 @@ describe("FormOutputLabel", () => {
|
||||
},
|
||||
localVue,
|
||||
pinia,
|
||||
provide: { workflowId: "mock-workflow" },
|
||||
});
|
||||
|
||||
const stepTwo = { id: 1, outputs: [{ name: "other-name" }], workflow_outputs: outputs };
|
||||
wrapperOther = mount(FormOutputLabel, {
|
||||
propsData: {
|
||||
@@ -38,8 +40,9 @@ describe("FormOutputLabel", () => {
|
||||
},
|
||||
localVue,
|
||||
pinia,
|
||||
provide: { workflowId: "mock-workflow" },
|
||||
});
|
||||
stepStore = useWorkflowStepStore();
|
||||
stepStore = useWorkflowStepStore("mock-workflow");
|
||||
stepStore.addStep(stepOne);
|
||||
stepStore.addStep(stepTwo);
|
||||
});
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
import type { Ref } from "vue";
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
import { useWorkflowStores } from "@/composables/workflowStores";
|
||||
import type { Step } from "@/stores/workflowStepStore";
|
||||
import { useWorkflowStepStore } from "@/stores/workflowStepStore";
|
||||
|
||||
import FormElement from "@/components/Form/FormElement.vue";
|
||||
|
||||
@@ -29,7 +29,7 @@ const props = withDefaults(
|
||||
}
|
||||
);
|
||||
|
||||
const stepStore = useWorkflowStepStore();
|
||||
const { stepStore } = useWorkflowStores();
|
||||
|
||||
const error: Ref<string | undefined> = ref(undefined);
|
||||
const id = computed(() => `__label__${props.name}`);
|
||||
|
||||
@@ -48,6 +48,7 @@ describe("FormTool", () => {
|
||||
ToolFooter: { template: "<div>tool-footer</div>" },
|
||||
},
|
||||
pinia: createTestingPinia(),
|
||||
provide: { workflowId: "mock-workflow" },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
import Utils from "utils/utils";
|
||||
import { toRef } from "vue";
|
||||
|
||||
import { useWorkflowStepStore } from "@/stores/workflowStepStore";
|
||||
import { useWorkflowStores } from "@/composables/workflowStores";
|
||||
|
||||
import { useStepProps } from "../composables/useStepProps";
|
||||
import { useUniqueLabelError } from "../composables/useUniqueLabelError";
|
||||
@@ -93,7 +93,7 @@ export default {
|
||||
const { stepId, annotation, label, stepInputs, stepOutputs, configForm, postJobActions } = useStepProps(
|
||||
toRef(props, "step")
|
||||
);
|
||||
const stepStore = useWorkflowStepStore();
|
||||
const { stepStore } = useWorkflowStores();
|
||||
const uniqueErrorLabel = useUniqueLabelError(stepStore, label);
|
||||
|
||||
return {
|
||||
|
||||
@@ -166,11 +166,9 @@ import Vue, { computed, onUnmounted, ref } from "vue";
|
||||
import { getUntypedWorkflowParameters } from "@/components/Workflow/Editor/modules/parameters";
|
||||
import { ConfirmDialog } from "@/composables/confirmDialog";
|
||||
import { useDatatypesMapper } from "@/composables/datatypesMapper";
|
||||
import { provideScopedWorkflowStores } from "@/composables/workflowStores";
|
||||
import { hide_modal } from "@/layout/modal";
|
||||
import { getAppRoot } from "@/onload/loadConfig";
|
||||
import { useConnectionStore } from "@/stores/workflowConnectionStore";
|
||||
import { useWorkflowStateStore } from "@/stores/workflowEditorStateStore";
|
||||
import { useWorkflowStepStore } from "@/stores/workflowStepStore";
|
||||
import { LastQueue } from "@/utils/promise-queue";
|
||||
|
||||
import { defaultPosition } from "./composables/useDefaultStepPosition";
|
||||
@@ -235,10 +233,10 @@ export default {
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const { datatypes, datatypesMapper, datatypesMapperLoading } = useDatatypesMapper();
|
||||
const connectionsStore = useConnectionStore();
|
||||
const stepStore = useWorkflowStepStore();
|
||||
|
||||
const { connectionStore, stepStore, stateStore } = provideScopedWorkflowStores(props.id);
|
||||
|
||||
const { getStepIndex, steps } = storeToRefs(stepStore);
|
||||
const stateStore = useWorkflowStateStore();
|
||||
const { activeNodeId } = storeToRefs(stateStore);
|
||||
const activeStep = computed(() => {
|
||||
if (activeNodeId.value !== null) {
|
||||
@@ -248,14 +246,14 @@ export default {
|
||||
});
|
||||
|
||||
const hasChanges = ref(false);
|
||||
const hasInvalidConnections = computed(() => Object.keys(connectionsStore.invalidConnections).length > 0);
|
||||
const hasInvalidConnections = computed(() => Object.keys(connectionStore.invalidConnections).length > 0);
|
||||
|
||||
stepStore.$subscribe((mutation, state) => {
|
||||
hasChanges.value = true;
|
||||
});
|
||||
|
||||
function resetStores() {
|
||||
connectionsStore.$reset();
|
||||
connectionStore.$reset();
|
||||
stepStore.$reset();
|
||||
stateStore.$reset();
|
||||
}
|
||||
@@ -264,7 +262,7 @@ export default {
|
||||
emit("update:confirmation", false);
|
||||
});
|
||||
return {
|
||||
connectionsStore,
|
||||
connectionStore,
|
||||
hasChanges,
|
||||
hasInvalidConnections,
|
||||
stepStore,
|
||||
@@ -360,7 +358,7 @@ export default {
|
||||
this.onUpdateStep(step);
|
||||
},
|
||||
onConnect(connection) {
|
||||
this.connectionsStore.addConnection(connection);
|
||||
this.connectionStore.addConnection(connection);
|
||||
},
|
||||
onAttemptRefactor(actions) {
|
||||
if (this.hasChanges) {
|
||||
@@ -406,7 +404,7 @@ export default {
|
||||
},
|
||||
async onRefactor(response) {
|
||||
this.resetStores();
|
||||
await fromSimple(response.workflow);
|
||||
await fromSimple(this.id, response.workflow);
|
||||
this._loadEditorData(response.workflow);
|
||||
},
|
||||
onUpdate(step) {
|
||||
@@ -471,7 +469,7 @@ export default {
|
||||
// Load workflow definition
|
||||
this.onWorkflowMessage("Importing workflow", "progress");
|
||||
loadWorkflow({ id }).then((data) => {
|
||||
fromSimple(data, true, defaultPosition(this.graphOffset, this.transform));
|
||||
fromSimple(this.id, data, true, defaultPosition(this.graphOffset, this.transform));
|
||||
// Determine if any parameters were 'upgraded' and provide message
|
||||
const insertedStateMessages = getStateUpgradeMessages(data);
|
||||
this.onInsertedStateMessages(insertedStateMessages);
|
||||
@@ -523,7 +521,7 @@ export default {
|
||||
},
|
||||
onLayout() {
|
||||
return import(/* webpackChunkName: "workflowLayout" */ "./modules/layout.ts").then((layout) => {
|
||||
layout.autoLayout(this.steps).then((newSteps) => {
|
||||
layout.autoLayout(this.id, this.steps).then((newSteps) => {
|
||||
newSteps.map((step) => this.onUpdateStep(step));
|
||||
});
|
||||
});
|
||||
@@ -691,7 +689,7 @@ export default {
|
||||
this.lastQueue
|
||||
.enqueue(loadWorkflow, { id, version })
|
||||
.then((data) => {
|
||||
fromSimple(data);
|
||||
fromSimple(id, data);
|
||||
this._loadEditorData(data);
|
||||
})
|
||||
.catch((response) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createTestingPinia } from "@pinia/testing";
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { PiniaVuePlugin } from "pinia";
|
||||
import { PiniaVuePlugin, setActivePinia } from "pinia";
|
||||
import { getLocalVue } from "tests/jest/helpers";
|
||||
|
||||
import { testDatatypesMapper } from "@/components/Datatypes/test_fixtures";
|
||||
@@ -88,6 +88,9 @@ describe("Lint", () => {
|
||||
let stepStore;
|
||||
|
||||
beforeEach(() => {
|
||||
const pinia = createTestingPinia({ stubActions: false });
|
||||
setActivePinia(pinia);
|
||||
|
||||
wrapper = mount(Lint, {
|
||||
propsData: {
|
||||
untypedParameters: getUntypedWorkflowParameters(steps),
|
||||
@@ -98,9 +101,11 @@ describe("Lint", () => {
|
||||
datatypesMapper: testDatatypesMapper,
|
||||
},
|
||||
localVue,
|
||||
pinia: createTestingPinia({ stubActions: false }),
|
||||
pinia,
|
||||
provide: { workflowId: "mock-workflow" },
|
||||
});
|
||||
stepStore = useWorkflowStepStore();
|
||||
|
||||
stepStore = useWorkflowStepStore("mock-workflow");
|
||||
Object.values(steps).map((step) => stepStore.addStep(step));
|
||||
});
|
||||
|
||||
@@ -131,7 +136,7 @@ describe("Lint", () => {
|
||||
});
|
||||
|
||||
it("should fire refactor event to extract untyped parameter and remove unlabeled workflows", async () => {
|
||||
wrapper.vm.onRefactor();
|
||||
await wrapper.find(".refactor-button").trigger("click");
|
||||
expect(wrapper.emitted().onRefactor.length).toBe(1);
|
||||
const actions = wrapper.emitted().onRefactor[0][0];
|
||||
expect(actions.length).toBe(2);
|
||||
@@ -139,9 +144,10 @@ describe("Lint", () => {
|
||||
expect(actions[0].name).toBe("untyped_parameter");
|
||||
expect(actions[1].action_type).toBe("remove_unlabeled_workflow_outputs");
|
||||
});
|
||||
|
||||
it("should include connect input action when input disconnected", async () => {
|
||||
stepStore.removeStep(0);
|
||||
wrapper.vm.onRefactor();
|
||||
await wrapper.find(".refactor-button").trigger("click");
|
||||
expect(wrapper.emitted().onRefactor.length).toBe(1);
|
||||
const actions = wrapper.emitted().onRefactor[0][0];
|
||||
expect(actions.length).toBe(3);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
Best Practices Review
|
||||
</div>
|
||||
<div v-if="showRefactor">
|
||||
<a href="#" @click="onRefactor"> Try to automatically fix issues. </a>
|
||||
<a class="refactor-button" href="#" @click="onRefactor"> Try to automatically fix issues. </a>
|
||||
</div>
|
||||
</template>
|
||||
<b-card-body>
|
||||
@@ -85,7 +85,7 @@ import { storeToRefs } from "pinia";
|
||||
import Vue from "vue";
|
||||
|
||||
import { DatatypesMapperModel } from "@/components/Datatypes/model";
|
||||
import { useWorkflowStepStore } from "@/stores/workflowStepStore";
|
||||
import { useWorkflowStores } from "@/composables/workflowStores";
|
||||
|
||||
import {
|
||||
fixAllIssues,
|
||||
@@ -135,8 +135,9 @@ export default {
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { hasActiveOutputs } = storeToRefs(useWorkflowStepStore());
|
||||
return { hasActiveOutputs };
|
||||
const { connectionStore, stepStore } = useWorkflowStores();
|
||||
const { hasActiveOutputs } = storeToRefs(stepStore);
|
||||
return { connectionStore, stepStore, hasActiveOutputs };
|
||||
},
|
||||
computed: {
|
||||
showRefactor() {
|
||||
@@ -170,7 +171,7 @@ export default {
|
||||
return getUntypedParameters(this.untypedParameters);
|
||||
},
|
||||
warningDisconnectedInputs() {
|
||||
return getDisconnectedInputs(this.steps, this.datatypesMapper);
|
||||
return getDisconnectedInputs(this.steps, this.datatypesMapper, this.connectionStore, this.stepStore);
|
||||
},
|
||||
warningMissingMetadata() {
|
||||
return getMissingMetadata(this.steps);
|
||||
@@ -226,7 +227,13 @@ export default {
|
||||
this.$emit("onUnhighlight", item.stepId);
|
||||
},
|
||||
onRefactor() {
|
||||
const actions = fixAllIssues(this.steps, this.untypedParameters);
|
||||
const actions = fixAllIssues(
|
||||
this.steps,
|
||||
this.untypedParameters,
|
||||
this.datatypesMapper,
|
||||
this.connectionStore,
|
||||
this.stepStore
|
||||
);
|
||||
this.$emit("onRefactor", actions);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@ describe("Node", () => {
|
||||
it("test attributes", async () => {
|
||||
const testingPinia = createTestingPinia();
|
||||
setActivePinia(testingPinia);
|
||||
const wrapper = shallowMount(Node, {
|
||||
const wrapper = shallowMount(Node as any, {
|
||||
propsData: {
|
||||
id: 0,
|
||||
contentId: "tool name",
|
||||
@@ -30,6 +30,9 @@ describe("Node", () => {
|
||||
},
|
||||
localVue,
|
||||
pinia: testingPinia,
|
||||
provide: {
|
||||
workflowId: "mock-workflow",
|
||||
},
|
||||
});
|
||||
await flushPromises();
|
||||
// fa-wrench is the tool icon ...
|
||||
|
||||
@@ -120,10 +120,9 @@ import { getGalaxyInstance } from "@/app";
|
||||
import { DatatypesMapperModel } from "@/components/Datatypes/model";
|
||||
import { useNodePosition } from "@/components/Workflow/Editor/composables/useNodePosition";
|
||||
import WorkflowIcons from "@/components/Workflow/icons";
|
||||
import { useConnectionStore } from "@/stores/workflowConnectionStore";
|
||||
import { type TerminalPosition, useWorkflowStateStore, type XYPosition } from "@/stores/workflowEditorStateStore";
|
||||
import { useWorkflowStores } from "@/composables/workflowStores";
|
||||
import type { TerminalPosition, XYPosition } from "@/stores/workflowEditorStateStore";
|
||||
import type { Step } from "@/stores/workflowStepStore";
|
||||
import { useWorkflowStepStore } from "@/stores/workflowStepStore";
|
||||
|
||||
import type { OutputTerminals } from "./modules/terminals";
|
||||
|
||||
@@ -180,9 +179,7 @@ const elHtml: Ref<HTMLElement | null> = computed(() => (el.value?.$el as HTMLEle
|
||||
|
||||
const postJobActions = computed(() => props.step.post_job_actions || {});
|
||||
const workflowOutputs = computed(() => props.step.workflow_outputs || []);
|
||||
const connectionStore = useConnectionStore();
|
||||
const stateStore = useWorkflowStateStore();
|
||||
const stepStore = useWorkflowStepStore();
|
||||
const { connectionStore, stateStore, stepStore } = useWorkflowStores();
|
||||
const isLoading = computed(() => Boolean(stateStore.getStepLoadingState(props.id)?.loading));
|
||||
useNodePosition(
|
||||
elHtml,
|
||||
|
||||
@@ -33,8 +33,8 @@ import { computed, inject, ref, toRefs, watch, watchEffect } from "vue";
|
||||
|
||||
import { DatatypesMapperModel } from "@/components/Datatypes/model";
|
||||
import { ConnectionAcceptable, terminalFactory } from "@/components/Workflow/Editor/modules/terminals";
|
||||
import { getConnectionId, useConnectionStore } from "@/stores/workflowConnectionStore";
|
||||
import { useWorkflowStateStore } from "@/stores/workflowEditorStateStore";
|
||||
import { useWorkflowStores } from "@/composables/workflowStores";
|
||||
import { getConnectionId } from "@/stores/workflowConnectionStore";
|
||||
|
||||
import { useRelativePosition } from "./composables/relativePosition";
|
||||
import { useTerminal } from "./composables/useTerminal";
|
||||
@@ -85,7 +85,7 @@ export default {
|
||||
const isDragging = inject("isDragging");
|
||||
const id = computed(() => `node-${props.stepId}-input-${props.input.name}`);
|
||||
const iconId = computed(() => `${id.value}-icon`);
|
||||
const connectionStore = useConnectionStore();
|
||||
const { connectionStore, stateStore, stepStore } = useWorkflowStores();
|
||||
const { terminal, isMappedOver: isMultiple } = useTerminal(stepId, input, datatypesMapper);
|
||||
const hasTerminals = ref(false);
|
||||
|
||||
@@ -101,7 +101,6 @@ export default {
|
||||
return classes;
|
||||
});
|
||||
|
||||
const stateStore = useWorkflowStateStore();
|
||||
const { draggingTerminal } = storeToRefs(stateStore);
|
||||
|
||||
const terminalIsHovered = ref(false);
|
||||
@@ -153,6 +152,7 @@ export default {
|
||||
hasTerminals,
|
||||
terminal,
|
||||
stateStore,
|
||||
stepStore,
|
||||
isMultiple,
|
||||
showRemove,
|
||||
terminalIsHovered,
|
||||
@@ -214,7 +214,13 @@ export default {
|
||||
},
|
||||
onDrop(e) {
|
||||
const stepOut = JSON.parse(e.dataTransfer.getData("text/plain"));
|
||||
const droppedTerminal = terminalFactory(stepOut.stepId, stepOut.output, this.datatypesMapper);
|
||||
const droppedTerminal = terminalFactory(
|
||||
stepOut.stepId,
|
||||
stepOut.output,
|
||||
this.datatypesMapper,
|
||||
this.connectionStore,
|
||||
this.stepStore
|
||||
);
|
||||
this.$root.$emit("bv::hide::tooltip", this.iconId);
|
||||
if (this.terminal.canAccept(droppedTerminal).canAccept) {
|
||||
this.terminal.connect(droppedTerminal);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { getLocalVue } from "tests/jest/helpers";
|
||||
import { nextTick, ref } from "vue";
|
||||
|
||||
import { testDatatypesMapper } from "@/components/Datatypes/test_fixtures";
|
||||
import { useConnectionStore } from "@/stores/workflowConnectionStore";
|
||||
import { type Step, type Steps, useWorkflowStepStore } from "@/stores/workflowStepStore";
|
||||
|
||||
import { terminalFactory } from "./modules/terminals";
|
||||
@@ -51,36 +52,51 @@ const transform = ref({ x: 0, y: 0, k: 1 });
|
||||
describe("NodeOutput", () => {
|
||||
let pinia: ReturnType<typeof createPinia>;
|
||||
let stepStore: ReturnType<typeof useWorkflowStepStore>;
|
||||
let connectionStore: ReturnType<typeof useConnectionStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
pinia = createPinia();
|
||||
setActivePinia(pinia);
|
||||
stepStore = useWorkflowStepStore();
|
||||
stepStore = useWorkflowStepStore("mock-workflow");
|
||||
connectionStore = useConnectionStore("mock-workflow");
|
||||
Object.values(advancedSteps).map((step) => stepStore.addStep(step));
|
||||
});
|
||||
|
||||
it("does not display multiple icon if not mapped over", async () => {
|
||||
const simpleDataStep = stepForLabel("simple data", stepStore.steps);
|
||||
const propsData = propsForStep(simpleDataStep);
|
||||
const wrapper = shallowMount(NodeOutput, {
|
||||
const wrapper = shallowMount(NodeOutput as any, {
|
||||
propsData: propsData,
|
||||
localVue,
|
||||
pinia,
|
||||
provide: { transform },
|
||||
provide: { transform, workflowId: "mock-workflow" },
|
||||
});
|
||||
expect(wrapper.find(".multiple").exists()).toBe(false);
|
||||
});
|
||||
|
||||
it("displays multiple icon if not mapped over", async () => {
|
||||
const simpleDataStep = stepForLabel("simple data", stepStore.steps);
|
||||
const listInputStep = stepForLabel("list input", stepStore.steps);
|
||||
const inputTerminal = terminalFactory(simpleDataStep.id, simpleDataStep.inputs[0]!, testDatatypesMapper);
|
||||
const outputTerminal = terminalFactory(listInputStep.id, listInputStep.outputs[0]!, testDatatypesMapper);
|
||||
const inputTerminal = terminalFactory(
|
||||
simpleDataStep.id,
|
||||
simpleDataStep.inputs[0]!,
|
||||
testDatatypesMapper,
|
||||
connectionStore,
|
||||
stepStore
|
||||
);
|
||||
const outputTerminal = terminalFactory(
|
||||
listInputStep.id,
|
||||
listInputStep.outputs[0]!,
|
||||
testDatatypesMapper,
|
||||
connectionStore,
|
||||
stepStore
|
||||
);
|
||||
const propsData = propsForStep(simpleDataStep);
|
||||
const wrapper = shallowMount(NodeOutput, {
|
||||
const wrapper = shallowMount(NodeOutput as any, {
|
||||
propsData: propsData,
|
||||
localVue,
|
||||
pinia,
|
||||
provide: { transform },
|
||||
provide: { transform, workflowId: "mock-workflow" },
|
||||
});
|
||||
expect(wrapper.find(".multiple").exists()).toBe(false);
|
||||
inputTerminal.connect(outputTerminal);
|
||||
|
||||
@@ -6,13 +6,13 @@ import type { UseElementBoundingReturn, UseScrollReturn } from "@vueuse/core";
|
||||
import { computed, nextTick, onBeforeUnmount, type Ref, ref, toRefs, type UnwrapRef, watch } from "vue";
|
||||
|
||||
import type { DatatypesMapperModel } from "@/components/Datatypes/model";
|
||||
import { useWorkflowStateStore, type XYPosition } from "@/stores/workflowEditorStateStore";
|
||||
import { useWorkflowStores } from "@/composables/workflowStores";
|
||||
import type { XYPosition } from "@/stores/workflowEditorStateStore";
|
||||
import {
|
||||
type OutputTerminalSource,
|
||||
type PostJobAction,
|
||||
type PostJobActions,
|
||||
type Step,
|
||||
useWorkflowStepStore,
|
||||
} from "@/stores/workflowStepStore";
|
||||
import { assertDefined, ensureDefined } from "@/utils/assertions";
|
||||
|
||||
@@ -43,8 +43,7 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(["pan-by", "stopDragging", "onDragConnector"]);
|
||||
const stateStore = useWorkflowStateStore();
|
||||
const stepStore = useWorkflowStepStore();
|
||||
const { stateStore, stepStore } = useWorkflowStores();
|
||||
const icon: Ref<HTMLElement | null> = ref(null);
|
||||
const { rootOffset, output, stepId, datatypesMapper } = toRefs(props);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
import LoadingSpan from "components/LoadingSpan";
|
||||
import _l from "utils/localization";
|
||||
|
||||
import { useWorkflowStepStore } from "@/stores/workflowStepStore";
|
||||
import { useWorkflowStores } from "@/composables/workflowStores";
|
||||
|
||||
import { getToolPredictions } from "./modules/services";
|
||||
import { getCompatibleRecommendations } from "./modules/utilities";
|
||||
@@ -41,7 +41,7 @@ export default {
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const stepStore = useWorkflowStepStore();
|
||||
const { stepStore } = useWorkflowStores();
|
||||
return { stepStore };
|
||||
},
|
||||
data() {
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
import { curveBasis, line } from "d3";
|
||||
import { computed, type PropType } from "vue";
|
||||
|
||||
import { useWorkflowStores } from "@/composables/workflowStores";
|
||||
import { type Connection, getConnectionId } from "@/stores/workflowConnectionStore";
|
||||
import { useConnectionStore } from "@/stores/workflowConnectionStore";
|
||||
import { type TerminalPosition, useWorkflowStateStore } from "@/stores/workflowEditorStateStore";
|
||||
import { useWorkflowStepStore } from "@/stores/workflowStepStore";
|
||||
import type { TerminalPosition } from "@/stores/workflowEditorStateStore";
|
||||
|
||||
const props = defineProps({
|
||||
id: String,
|
||||
@@ -23,9 +22,7 @@ const ribbonMargin = 4;
|
||||
|
||||
const curve = line().curve(curveBasis);
|
||||
|
||||
const stateStore = useWorkflowStateStore();
|
||||
const connectionStore = useConnectionStore();
|
||||
const stepStore = useWorkflowStepStore();
|
||||
const { connectionStore, stateStore, stepStore } = useWorkflowStores();
|
||||
|
||||
const outputPos = computed(() => {
|
||||
if (props.terminalPosition) {
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed, type Ref } from "vue";
|
||||
|
||||
import { type Connection, type OutputTerminal, useConnectionStore } from "@/stores/workflowConnectionStore";
|
||||
import { useWorkflowStores } from "@/composables/workflowStores";
|
||||
import type { Connection, OutputTerminal } from "@/stores/workflowConnectionStore";
|
||||
import type { TerminalPosition } from "@/stores/workflowEditorStateStore";
|
||||
|
||||
import type { OutputTerminals } from "./modules/terminals";
|
||||
@@ -15,7 +16,7 @@ const props = defineProps<{
|
||||
transform: { x: number; y: number; k: number };
|
||||
}>();
|
||||
|
||||
const connectionStore = useConnectionStore();
|
||||
const { connectionStore } = useWorkflowStores();
|
||||
const { connections } = storeToRefs(connectionStore);
|
||||
|
||||
const draggingConnection: Ref<[Connection, TerminalPosition] | null> = computed(() => {
|
||||
|
||||
@@ -51,9 +51,9 @@ import { storeToRefs } from "pinia";
|
||||
import { computed, type PropType, provide, reactive, type Ref, ref, watch, watchEffect } from "vue";
|
||||
|
||||
import { DatatypesMapperModel } from "@/components/Datatypes/model";
|
||||
import { useWorkflowStores } from "@/composables/workflowStores";
|
||||
import type { TerminalPosition, XYPosition } from "@/stores/workflowEditorStateStore";
|
||||
import { useWorkflowStateStore } from "@/stores/workflowEditorStateStore";
|
||||
import { type Step, useWorkflowStepStore } from "@/stores/workflowStepStore";
|
||||
import type { Step } from "@/stores/workflowStepStore";
|
||||
import { assertDefined } from "@/utils/assertions";
|
||||
|
||||
import { useD3Zoom } from "./composables/d3Zoom";
|
||||
@@ -75,8 +75,7 @@ const props = defineProps({
|
||||
scrollToId: { type: null as unknown as PropType<number | null>, default: null },
|
||||
});
|
||||
|
||||
const stateStore = useWorkflowStateStore();
|
||||
const stepStore = useWorkflowStepStore();
|
||||
const { stateStore, stepStore } = useWorkflowStores();
|
||||
const { scale, activeNodeId, draggingPosition, draggingTerminal } = storeToRefs(stateStore);
|
||||
const canvas: Ref<HTMLElement | null> = ref(null);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { computed, onMounted, ref, unref, watch } from "vue";
|
||||
|
||||
import { useAnimationFrame } from "@/composables/sensors/animationFrame";
|
||||
import { useAnimationFrameThrottle } from "@/composables/throttle";
|
||||
import { useWorkflowStateStore } from "@/stores/workflowEditorStateStore";
|
||||
import { useWorkflowStores } from "@/composables/workflowStores";
|
||||
import type { Step, Steps } from "@/stores/workflowStepStore";
|
||||
|
||||
import { AxisAlignedBoundingBox, Transform } from "./modules/geometry";
|
||||
@@ -21,7 +21,7 @@ const emit = defineEmits<{
|
||||
(e: "moveTo", position: { x: number; y: number }): void;
|
||||
}>();
|
||||
|
||||
const stateStore = useWorkflowStateStore();
|
||||
const { stateStore } = useWorkflowStores();
|
||||
|
||||
/** reference to the main canvas element */
|
||||
const canvas: Ref<HTMLCanvasElement | null> = ref(null);
|
||||
|
||||
@@ -2,8 +2,8 @@ import { computed, type Ref, ref, watch } from "vue";
|
||||
|
||||
import type { DatatypesMapperModel } from "@/components/Datatypes/model";
|
||||
import { terminalFactory } from "@/components/Workflow/Editor/modules/terminals";
|
||||
import { useWorkflowStores } from "@/composables/workflowStores";
|
||||
import type { Step, TerminalSource } from "@/stores/workflowStepStore";
|
||||
import { useWorkflowStepStore } from "@/stores/workflowStepStore";
|
||||
|
||||
export function useTerminal(
|
||||
stepId: Ref<Step["id"]>,
|
||||
@@ -11,7 +11,7 @@ export function useTerminal(
|
||||
datatypesMapper: Ref<DatatypesMapperModel>
|
||||
) {
|
||||
const terminal: Ref<ReturnType<typeof terminalFactory> | null> = ref(null);
|
||||
const stepStore = useWorkflowStepStore();
|
||||
const { connectionStore, stepStore } = useWorkflowStores();
|
||||
const step = computed(() => stepStore.getStep(stepId.value));
|
||||
const isMappedOver = computed(() => stepStore.stepMapOver[stepId.value]?.isCollection ?? false);
|
||||
|
||||
@@ -19,7 +19,13 @@ export function useTerminal(
|
||||
[step, terminalSource, datatypesMapper],
|
||||
() => {
|
||||
// rebuild terminal if any of the tracked dependencies change
|
||||
const newTerminal = terminalFactory(stepId.value, terminalSource.value, datatypesMapper.value);
|
||||
const newTerminal = terminalFactory(
|
||||
stepId.value,
|
||||
terminalSource.value,
|
||||
datatypesMapper.value,
|
||||
connectionStore,
|
||||
stepStore
|
||||
);
|
||||
newTerminal.getInvalidConnectedTerminals();
|
||||
terminal.value = newTerminal;
|
||||
},
|
||||
|
||||
@@ -33,9 +33,9 @@ interface NewGraph {
|
||||
edges: GraphEdge[];
|
||||
}
|
||||
|
||||
export async function autoLayout(steps: { [index: string]: Step }) {
|
||||
const stateStore = useWorkflowStateStore();
|
||||
const connectionStore = useConnectionStore();
|
||||
export async function autoLayout(id: string, steps: { [index: string]: Step }) {
|
||||
const connectionStore = useConnectionStore(id);
|
||||
const stateStore = useWorkflowStateStore(id);
|
||||
|
||||
// Convert this to ELK compat.
|
||||
const newGraph: NewGraph = {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { DatatypesMapperModel } from "@/components/Datatypes/model";
|
||||
import type { UntypedParameters } from "@/components/Workflow/Editor/modules/parameters";
|
||||
import type { Step, Steps } from "@/stores/workflowStepStore";
|
||||
import type { useConnectionStore } from "@/stores/workflowConnectionStore";
|
||||
import type { Step, Steps, useWorkflowStepStore } from "@/stores/workflowStepStore";
|
||||
import { assertDefined } from "@/utils/assertions";
|
||||
|
||||
import { terminalFactory } from "./terminals";
|
||||
@@ -14,11 +15,16 @@ interface LintState {
|
||||
autofix?: boolean;
|
||||
}
|
||||
|
||||
export function getDisconnectedInputs(steps: Steps = {}, datatypesMapper: DatatypesMapperModel) {
|
||||
export function getDisconnectedInputs(
|
||||
steps: Steps = {},
|
||||
datatypesMapper: DatatypesMapperModel,
|
||||
connectionStore: ReturnType<typeof useConnectionStore>,
|
||||
stepStore: ReturnType<typeof useWorkflowStepStore>
|
||||
) {
|
||||
const inputs: LintState[] = [];
|
||||
Object.values(steps).forEach((step) => {
|
||||
step.inputs.map((inputSource) => {
|
||||
const inputTerminal = terminalFactory(step.id, inputSource, datatypesMapper);
|
||||
const inputTerminal = terminalFactory(step.id, inputSource, datatypesMapper, connectionStore, stepStore);
|
||||
if (!inputTerminal.optional && inputTerminal.connections.length === 0) {
|
||||
const inputLabel = inputSource.label || inputSource.name;
|
||||
inputs.push({
|
||||
@@ -114,7 +120,13 @@ export function getUntypedParameters(untypedParameters: UntypedParameters) {
|
||||
return items;
|
||||
}
|
||||
|
||||
export function fixAllIssues(steps: Steps, parameters: UntypedParameters, datatypesMapper: DatatypesMapperModel) {
|
||||
export function fixAllIssues(
|
||||
steps: Steps,
|
||||
parameters: UntypedParameters,
|
||||
datatypesMapper: DatatypesMapperModel,
|
||||
connectionStore: ReturnType<typeof useConnectionStore>,
|
||||
stepStore: ReturnType<typeof useWorkflowStepStore>
|
||||
) {
|
||||
const actions = [];
|
||||
const untypedParameters = getUntypedParameters(parameters);
|
||||
for (const untypedParameter of untypedParameters) {
|
||||
@@ -122,7 +134,7 @@ export function fixAllIssues(steps: Steps, parameters: UntypedParameters, dataty
|
||||
actions.push(fixUntypedParameter(untypedParameter));
|
||||
}
|
||||
}
|
||||
const disconnectedInputs = getDisconnectedInputs(steps, datatypesMapper);
|
||||
const disconnectedInputs = getDisconnectedInputs(steps, datatypesMapper, connectionStore, stepStore);
|
||||
for (const disconnectedInput of disconnectedInputs) {
|
||||
if (disconnectedInput.autofix) {
|
||||
actions.push(fixDisconnectedInput(disconnectedInput));
|
||||
|
||||
@@ -10,8 +10,21 @@ interface Workflow {
|
||||
steps: Steps;
|
||||
}
|
||||
|
||||
export async function fromSimple(data: Workflow, appendData = false, defaultPosition = { top: 0, left: 0 }) {
|
||||
const stepStore = useWorkflowStepStore();
|
||||
/**
|
||||
* Loads a workflow into the editor
|
||||
*
|
||||
* @param id ID of workflow to load data *into*
|
||||
* @param data Workflow data to load from
|
||||
* @param appendData if true appends data to current workflow, making sure to create new uuids
|
||||
* @param defaultPosition where to position workflow in the editor
|
||||
*/
|
||||
export async function fromSimple(
|
||||
id: string,
|
||||
data: Workflow,
|
||||
appendData = false,
|
||||
defaultPosition = { top: 0, left: 0 }
|
||||
) {
|
||||
const stepStore = useWorkflowStepStore(id);
|
||||
const stepIdOffset = stepStore.getStepIndex + 1;
|
||||
Object.values(data.steps).forEach((step) => {
|
||||
// If workflow being copied into another, wipe UUID and let
|
||||
|
||||
@@ -24,15 +24,31 @@ import {
|
||||
|
||||
function setupAdvanced() {
|
||||
const terminals: { [index: string]: { [index: string]: ReturnType<typeof terminalFactory> } } = {};
|
||||
|
||||
const connectionStore = useConnectionStore("mock-workflow");
|
||||
const stepStore = useWorkflowStepStore("mock-workflow");
|
||||
|
||||
Object.values(advancedSteps).map((step) => {
|
||||
const stepLabel = step.label;
|
||||
if (stepLabel) {
|
||||
terminals[stepLabel] = {};
|
||||
step.inputs?.map((input) => {
|
||||
terminals[stepLabel]![input.name] = terminalFactory(step.id, input, testDatatypesMapper);
|
||||
terminals[stepLabel]![input.name] = terminalFactory(
|
||||
step.id,
|
||||
input,
|
||||
testDatatypesMapper,
|
||||
connectionStore,
|
||||
stepStore
|
||||
);
|
||||
});
|
||||
step.outputs?.map((output) => {
|
||||
terminals[stepLabel]![output.name] = terminalFactory(step.id, output, testDatatypesMapper);
|
||||
terminals[stepLabel]![output.name] = terminalFactory(
|
||||
step.id,
|
||||
output,
|
||||
testDatatypesMapper,
|
||||
connectionStore,
|
||||
stepStore
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -42,12 +58,16 @@ function setupAdvanced() {
|
||||
function rebuildTerminal<T extends ReturnType<typeof terminalFactory>>(terminal: T): T {
|
||||
let terminalSource: TerminalSource;
|
||||
const step = terminal.stepStore.getStep(terminal.stepId);
|
||||
|
||||
const connectionStore = useConnectionStore("mock-workflow");
|
||||
const stepStore = useWorkflowStepStore("mock-workflow");
|
||||
|
||||
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;
|
||||
return terminalFactory(terminal.stepId, terminalSource, testDatatypesMapper, connectionStore, stepStore) as T;
|
||||
}
|
||||
|
||||
describe("terminalFactory", () => {
|
||||
@@ -83,7 +103,10 @@ describe("terminalFactory", () => {
|
||||
expect(terminals["filter_failed"]?.["output"]).toBeInstanceOf(OutputCollectionTerminal);
|
||||
});
|
||||
it("throws error on invalid terminalSource", () => {
|
||||
const invalidFactory = () => terminalFactory(1, {} as any, testDatatypesMapper);
|
||||
const connectionStore = useConnectionStore("mock-workflow");
|
||||
const stepStore = useWorkflowStepStore("mock-workflow");
|
||||
|
||||
const invalidFactory = () => terminalFactory(1, {} as any, testDatatypesMapper, connectionStore, stepStore);
|
||||
expect(invalidFactory).toThrow();
|
||||
});
|
||||
});
|
||||
@@ -95,8 +118,8 @@ describe("canAccept", () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
terminals = setupAdvanced();
|
||||
stepStore = useWorkflowStepStore();
|
||||
connectionStore = useConnectionStore();
|
||||
stepStore = useWorkflowStepStore("mock-workflow");
|
||||
connectionStore = useConnectionStore("mock-workflow");
|
||||
Object.values(JSON.parse(JSON.stringify(advancedSteps)) as Steps).map((step) => {
|
||||
stepStore.addStep(step);
|
||||
});
|
||||
@@ -542,18 +565,30 @@ describe("Input terminal", () => {
|
||||
let terminals: { [index: number]: { [index: string]: ReturnType<typeof terminalFactory> } };
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
stepStore = useWorkflowStepStore();
|
||||
connectionStore = useConnectionStore();
|
||||
stepStore = useWorkflowStepStore("mock-workflow");
|
||||
connectionStore = useConnectionStore("mock-workflow");
|
||||
terminals = {};
|
||||
Object.values(simpleSteps).map((step) => {
|
||||
stepStore.addStep(step);
|
||||
terminals[step.id] = {};
|
||||
const stepTerminals = terminals[step.id]!;
|
||||
step.inputs?.map((input) => {
|
||||
stepTerminals[input.name] = terminalFactory(step.id, input, testDatatypesMapper);
|
||||
stepTerminals[input.name] = terminalFactory(
|
||||
step.id,
|
||||
input,
|
||||
testDatatypesMapper,
|
||||
connectionStore,
|
||||
stepStore
|
||||
);
|
||||
});
|
||||
step.outputs?.map((output) => {
|
||||
stepTerminals[output.name] = terminalFactory(step.id, output, testDatatypesMapper);
|
||||
stepTerminals[output.name] = terminalFactory(
|
||||
step.id,
|
||||
output,
|
||||
testDatatypesMapper,
|
||||
connectionStore,
|
||||
stepStore
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
type Connection,
|
||||
type ConnectionId,
|
||||
getConnectionId,
|
||||
useConnectionStore,
|
||||
type useConnectionStore,
|
||||
} from "@/stores/workflowConnectionStore";
|
||||
import type {
|
||||
CollectionOutput,
|
||||
@@ -15,8 +15,8 @@ import type {
|
||||
ParameterOutput,
|
||||
ParameterStepInput,
|
||||
TerminalSource,
|
||||
useWorkflowStepStore,
|
||||
} from "@/stores/workflowStepStore";
|
||||
import { useWorkflowStepStore } from "@/stores/workflowStepStore";
|
||||
import { assertDefined } from "@/utils/assertions";
|
||||
|
||||
import {
|
||||
@@ -39,6 +39,8 @@ interface BaseTerminalArgs {
|
||||
name: string;
|
||||
stepId: number;
|
||||
datatypesMapper: DatatypesMapperModel;
|
||||
connectionStore: ReturnType<typeof useConnectionStore>;
|
||||
stepStore: ReturnType<typeof useWorkflowStepStore>;
|
||||
}
|
||||
|
||||
interface InputTerminalInputs {
|
||||
@@ -64,8 +66,8 @@ class Terminal extends EventEmitter {
|
||||
|
||||
constructor(attr: BaseTerminalArgs) {
|
||||
super();
|
||||
this.connectionStore = useConnectionStore();
|
||||
this.stepStore = useWorkflowStepStore();
|
||||
this.connectionStore = attr.connectionStore;
|
||||
this.stepStore = attr.stepStore;
|
||||
this.stepId = attr.stepId;
|
||||
this.name = attr.name;
|
||||
this.multiple = false;
|
||||
@@ -244,12 +246,24 @@ class BaseInputTerminal extends Terminal {
|
||||
// step must have an output, since it is or was connected to this step
|
||||
const terminalSource = step.outputs[0];
|
||||
if (terminalSource) {
|
||||
const terminal = terminalFactory(step.id, terminalSource, this.datatypesMapper);
|
||||
const terminal = terminalFactory(
|
||||
step.id,
|
||||
terminalSource,
|
||||
this.datatypesMapper,
|
||||
this.connectionStore,
|
||||
this.stepStore
|
||||
);
|
||||
// drop mapping restrictions
|
||||
terminal.resetMappingIfNeeded();
|
||||
// re-establish map over through inputs
|
||||
step.inputs.forEach((input) => {
|
||||
terminalFactory(step.id, input, this.datatypesMapper).getStepMapOver();
|
||||
terminalFactory(
|
||||
step.id,
|
||||
input,
|
||||
this.datatypesMapper,
|
||||
this.connectionStore,
|
||||
this.stepStore
|
||||
).getStepMapOver();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -356,6 +370,8 @@ class BaseInputTerminal extends Terminal {
|
||||
name: connection.output.name,
|
||||
valid: false,
|
||||
datatypesMapper: this.datatypesMapper,
|
||||
connectionStore: this.connectionStore,
|
||||
stepStore: this.stepStore,
|
||||
});
|
||||
}
|
||||
let terminalSource = outputStep.outputs.find((output) => output.name === connection.output.name);
|
||||
@@ -367,6 +383,8 @@ class BaseInputTerminal extends Terminal {
|
||||
name: connection.output.name,
|
||||
valid: false,
|
||||
datatypesMapper: this.datatypesMapper,
|
||||
connectionStore: this.connectionStore,
|
||||
stepStore: this.stepStore,
|
||||
});
|
||||
}
|
||||
const postJobActionKey = `ChangeDatatypeAction${connection.output.name}`;
|
||||
@@ -383,7 +401,13 @@ class BaseInputTerminal extends Terminal {
|
||||
};
|
||||
}
|
||||
|
||||
return terminalFactory(outputStep.id, terminalSource, this.datatypesMapper);
|
||||
return terminalFactory(
|
||||
outputStep.id,
|
||||
terminalSource,
|
||||
this.datatypesMapper,
|
||||
this.connectionStore,
|
||||
this.stepStore
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -658,9 +682,17 @@ class BaseOutputTerminal extends Terminal {
|
||||
optional: false,
|
||||
multiple: false,
|
||||
},
|
||||
connectionStore: this.connectionStore,
|
||||
stepStore: this.stepStore,
|
||||
});
|
||||
}
|
||||
return terminalFactory(inputStep.id, terminalSource, this.datatypesMapper);
|
||||
return terminalFactory(
|
||||
inputStep.id,
|
||||
terminalSource,
|
||||
this.datatypesMapper,
|
||||
this.connectionStore,
|
||||
this.stepStore
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -689,7 +721,13 @@ class BaseOutputTerminal extends Terminal {
|
||||
const validInputTerminals: InputTerminals[] = [];
|
||||
Object.values(this.stepStore.steps).map((step) => {
|
||||
step.inputs?.forEach((input) => {
|
||||
const inputTerminal = terminalFactory(step.id, input, this.datatypesMapper);
|
||||
const inputTerminal = terminalFactory(
|
||||
step.id,
|
||||
input,
|
||||
this.datatypesMapper,
|
||||
this.connectionStore,
|
||||
this.stepStore
|
||||
);
|
||||
if (inputTerminal.canAccept(this).canAccept) {
|
||||
validInputTerminals.push(inputTerminal);
|
||||
}
|
||||
@@ -738,8 +776,20 @@ export class OutputCollectionTerminal extends BaseOutputTerminal {
|
||||
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);
|
||||
const outputTerminal = terminalFactory(
|
||||
connection.output.stepId,
|
||||
stepOutput,
|
||||
this.datatypesMapper,
|
||||
this.connectionStore,
|
||||
this.stepStore
|
||||
);
|
||||
const inputTerminal = terminalFactory(
|
||||
connection.output.stepId,
|
||||
stepInput,
|
||||
this.datatypesMapper,
|
||||
this.connectionStore,
|
||||
this.stepStore
|
||||
);
|
||||
// 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
|
||||
@@ -885,7 +935,9 @@ type TerminalOf<T extends TerminalSourceAndInvalid> = T extends InvalidInputTerm
|
||||
export function terminalFactory<T extends TerminalSourceAndInvalid>(
|
||||
stepId: number,
|
||||
terminalSource: T,
|
||||
datatypesMapper: DatatypesMapperModel
|
||||
datatypesMapper: DatatypesMapperModel,
|
||||
connectionStore: ReturnType<typeof useConnectionStore>,
|
||||
stepStore: ReturnType<typeof useWorkflowStepStore>
|
||||
): TerminalOf<T> {
|
||||
if ("input_type" in terminalSource) {
|
||||
const terminalArgs = {
|
||||
@@ -893,6 +945,8 @@ export function terminalFactory<T extends TerminalSourceAndInvalid>(
|
||||
input_type: terminalSource.input_type,
|
||||
name: terminalSource.name,
|
||||
stepId: stepId,
|
||||
connectionStore,
|
||||
stepStore,
|
||||
};
|
||||
if ("valid" in terminalSource) {
|
||||
return new InvalidInputTerminal({
|
||||
@@ -940,6 +994,8 @@ export function terminalFactory<T extends TerminalSourceAndInvalid>(
|
||||
optional: terminalSource.optional,
|
||||
stepId: stepId,
|
||||
datatypesMapper: datatypesMapper,
|
||||
connectionStore,
|
||||
stepStore,
|
||||
};
|
||||
if (isOutputParameterArg(terminalSource)) {
|
||||
return new OutputParameterTerminal({
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { inject, onScopeDispose, provide } from "vue";
|
||||
|
||||
import { useConnectionStore } from "@/stores/workflowConnectionStore";
|
||||
import { useWorkflowStateStore } from "@/stores/workflowEditorStateStore";
|
||||
import { useWorkflowStepStore } from "@/stores/workflowStepStore";
|
||||
|
||||
/**
|
||||
* Creates stores scoped to a specific workflowId, and manages their lifetime.
|
||||
* In child components, use `useWorkflowStores` instead.
|
||||
*
|
||||
* Provides `workflowId` to all child components.
|
||||
*
|
||||
* @param workflowId the workflow to scope to
|
||||
* @returns workflow Stores
|
||||
*/
|
||||
export function provideScopedWorkflowStores(workflowId: string) {
|
||||
provide("workflowId", workflowId);
|
||||
|
||||
const connectionStore = useConnectionStore(workflowId);
|
||||
const stateStore = useWorkflowStateStore(workflowId);
|
||||
const stepStore = useWorkflowStepStore(workflowId);
|
||||
|
||||
onScopeDispose(() => {
|
||||
connectionStore.$dispose();
|
||||
stateStore.$dispose();
|
||||
stepStore.$dispose();
|
||||
});
|
||||
|
||||
return {
|
||||
connectionStore,
|
||||
stateStore,
|
||||
stepStore,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses all workflow related stores scoped to the workflow defined by a parent component.
|
||||
* Does not manage lifetime.
|
||||
*
|
||||
* `provideScopedWorkflowStores` needs to be called by a parent component,
|
||||
* or this composable will throw an error.
|
||||
*
|
||||
* @returns workflow stores
|
||||
*/
|
||||
export function useWorkflowStores() {
|
||||
const workflowId = inject("workflowId");
|
||||
|
||||
if (typeof workflowId !== "string") {
|
||||
throw new Error(
|
||||
"Workflow ID not provided by parent component. Use `setupWorkflowStores` on a parent component."
|
||||
);
|
||||
}
|
||||
|
||||
const connectionStore = useConnectionStore(workflowId);
|
||||
const stateStore = useWorkflowStateStore(workflowId);
|
||||
const stepStore = useWorkflowStepStore(workflowId);
|
||||
|
||||
return {
|
||||
connectionStore,
|
||||
stateStore,
|
||||
stepStore,
|
||||
};
|
||||
}
|
||||
@@ -38,25 +38,25 @@ const connection: Connection = {
|
||||
describe("Connection Store", () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
const workflowStepStore = useWorkflowStepStore();
|
||||
const workflowStepStore = useWorkflowStepStore("mock-workflow");
|
||||
workflowStepStore.addStep(workflowStepZero);
|
||||
workflowStepStore.addStep(workflowStepOne);
|
||||
});
|
||||
|
||||
it("adds connection", () => {
|
||||
const connectionStore = useConnectionStore();
|
||||
const connectionStore = useConnectionStore("mock-workflow");
|
||||
expect(connectionStore.connections.length).toBe(0);
|
||||
connectionStore.addConnection(connection);
|
||||
expect(connectionStore.connections.length).toBe(1);
|
||||
});
|
||||
it("removes connection", () => {
|
||||
const connectionStore = useConnectionStore();
|
||||
const connectionStore = useConnectionStore("mock-workflow");
|
||||
connectionStore.addConnection(connection);
|
||||
connectionStore.removeConnection(inputTerminal);
|
||||
expect(connectionStore.connections.length).toBe(0);
|
||||
});
|
||||
it("finds connections for steps", () => {
|
||||
const connectionStore = useConnectionStore();
|
||||
const connectionStore = useConnectionStore("mock-workflow");
|
||||
expect(connectionStore.getConnectionsForStep(0)).toStrictEqual([]);
|
||||
expect(connectionStore.getConnectionsForStep(1)).toStrictEqual([]);
|
||||
connectionStore.addConnection(connection);
|
||||
@@ -67,7 +67,7 @@ describe("Connection Store", () => {
|
||||
expect(connectionStore.getConnectionsForStep(1)).toStrictEqual([]);
|
||||
});
|
||||
it("finds output terminals for input terminal", () => {
|
||||
const connectionStore = useConnectionStore();
|
||||
const connectionStore = useConnectionStore("mock-workflow");
|
||||
expect(connectionStore.getOutputTerminalsForInputTerminal(getTerminalId(connection.input))).toStrictEqual([]);
|
||||
connectionStore.addConnection(connection);
|
||||
expect(connectionStore.getOutputTerminalsForInputTerminal(getTerminalId(connection.input))).toStrictEqual([
|
||||
|
||||
@@ -42,82 +42,88 @@ interface TerminalToOutputTerminals {
|
||||
[index: string]: OutputTerminal[];
|
||||
}
|
||||
|
||||
export const useConnectionStore = defineStore("workflowConnectionStore", {
|
||||
state: (): State => ({
|
||||
connections: [] as Array<Readonly<Connection>>,
|
||||
invalidConnections: {} as InvalidConnections,
|
||||
inputTerminalToOutputTerminals: {} as TerminalToOutputTerminals,
|
||||
terminalToConnection: {} as { [index: string]: Connection[] },
|
||||
stepToConnections: {} as { [index: number]: Connection[] },
|
||||
}),
|
||||
getters: {
|
||||
getOutputTerminalsForInputTerminal(state: State) {
|
||||
return (terminalId: string): OutputTerminal[] => {
|
||||
return state.inputTerminalToOutputTerminals[terminalId] || [];
|
||||
};
|
||||
},
|
||||
getConnectionsForTerminal(state: State) {
|
||||
return (terminalId: string): Connection[] => {
|
||||
return state.terminalToConnection[terminalId] || [];
|
||||
};
|
||||
},
|
||||
getConnectionsForStep(state: State) {
|
||||
return (stepId: number): Connection[] => state.stepToConnections[stepId] || [];
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
addConnection(this, _connection: Connection) {
|
||||
const connection = Object.freeze(_connection);
|
||||
this.connections.push(connection);
|
||||
const stepStore = useWorkflowStepStore();
|
||||
stepStore.addConnection(connection);
|
||||
this.terminalToConnection = updateTerminalToConnection(this.connections);
|
||||
this.inputTerminalToOutputTerminals = updateTerminalToTerminal(this.connections);
|
||||
this.stepToConnections = updateStepToConnections(this.connections);
|
||||
},
|
||||
markInvalidConnection(this: State, connectionId: string, reason: string) {
|
||||
Vue.set(this.invalidConnections, connectionId, reason);
|
||||
},
|
||||
dropFromInvalidConnections(this: State, connectionId: string) {
|
||||
Vue.delete(this.invalidConnections, connectionId);
|
||||
},
|
||||
removeConnection(this, terminal: InputTerminal | OutputTerminal | ConnectionId) {
|
||||
const stepStore = useWorkflowStepStore();
|
||||
this.connections = this.connections.filter((connection) => {
|
||||
const id = getConnectionId(connection);
|
||||
export const useConnectionStore = (workflowId: string) => {
|
||||
if (!workflowId) {
|
||||
throw new Error("WorkflowId is undefined");
|
||||
}
|
||||
|
||||
if (typeof terminal === "string") {
|
||||
if (id === terminal) {
|
||||
stepStore.removeConnection(connection);
|
||||
Vue.delete(this.invalidConnections, id);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else if (terminal.connectorType === "input") {
|
||||
if (connection.input.stepId == terminal.stepId && connection.input.name == terminal.name) {
|
||||
stepStore.removeConnection(connection);
|
||||
Vue.delete(this.invalidConnections, id);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (connection.output.stepId == terminal.stepId && connection.output.name == terminal.name) {
|
||||
stepStore.removeConnection(connection);
|
||||
Vue.delete(this.invalidConnections, id);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.terminalToConnection = updateTerminalToConnection(this.connections);
|
||||
this.inputTerminalToOutputTerminals = updateTerminalToTerminal(this.connections);
|
||||
this.stepToConnections = updateStepToConnections(this.connections);
|
||||
return defineStore(`workflowConnectionStore${workflowId}`, {
|
||||
state: (): State => ({
|
||||
connections: [] as Array<Readonly<Connection>>,
|
||||
invalidConnections: {} as InvalidConnections,
|
||||
inputTerminalToOutputTerminals: {} as TerminalToOutputTerminals,
|
||||
terminalToConnection: {} as { [index: string]: Connection[] },
|
||||
stepToConnections: {} as { [index: number]: Connection[] },
|
||||
}),
|
||||
getters: {
|
||||
getOutputTerminalsForInputTerminal(state: State) {
|
||||
return (terminalId: string): OutputTerminal[] => {
|
||||
return state.inputTerminalToOutputTerminals[terminalId] || [];
|
||||
};
|
||||
},
|
||||
getConnectionsForTerminal(state: State) {
|
||||
return (terminalId: string): Connection[] => {
|
||||
return state.terminalToConnection[terminalId] || [];
|
||||
};
|
||||
},
|
||||
getConnectionsForStep(state: State) {
|
||||
return (stepId: number): Connection[] => state.stepToConnections[stepId] || [];
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
actions: {
|
||||
addConnection(this, _connection: Connection) {
|
||||
const connection = Object.freeze(_connection);
|
||||
this.connections.push(connection);
|
||||
const stepStore = useWorkflowStepStore(workflowId);
|
||||
stepStore.addConnection(connection);
|
||||
this.terminalToConnection = updateTerminalToConnection(this.connections);
|
||||
this.inputTerminalToOutputTerminals = updateTerminalToTerminal(this.connections);
|
||||
this.stepToConnections = updateStepToConnections(this.connections);
|
||||
},
|
||||
markInvalidConnection(this: State, connectionId: string, reason: string) {
|
||||
Vue.set(this.invalidConnections, connectionId, reason);
|
||||
},
|
||||
dropFromInvalidConnections(this: State, connectionId: string) {
|
||||
Vue.delete(this.invalidConnections, connectionId);
|
||||
},
|
||||
removeConnection(this, terminal: InputTerminal | OutputTerminal | ConnectionId) {
|
||||
const stepStore = useWorkflowStepStore(workflowId);
|
||||
this.connections = this.connections.filter((connection) => {
|
||||
const id = getConnectionId(connection);
|
||||
|
||||
if (typeof terminal === "string") {
|
||||
if (id === terminal) {
|
||||
stepStore.removeConnection(connection);
|
||||
Vue.delete(this.invalidConnections, id);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else if (terminal.connectorType === "input") {
|
||||
if (connection.input.stepId == terminal.stepId && connection.input.name == terminal.name) {
|
||||
stepStore.removeConnection(connection);
|
||||
Vue.delete(this.invalidConnections, id);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (connection.output.stepId == terminal.stepId && connection.output.name == terminal.name) {
|
||||
stepStore.removeConnection(connection);
|
||||
Vue.delete(this.invalidConnections, id);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.terminalToConnection = updateTerminalToConnection(this.connections);
|
||||
this.inputTerminalToOutputTerminals = updateTerminalToTerminal(this.connections);
|
||||
this.stepToConnections = updateStepToConnections(this.connections);
|
||||
},
|
||||
},
|
||||
})();
|
||||
};
|
||||
|
||||
function updateTerminalToTerminal(connections: Connection[]) {
|
||||
const inputTerminalToOutputTerminals: TerminalToOutputTerminals = {};
|
||||
|
||||
@@ -33,67 +33,69 @@ interface State {
|
||||
stepLoadingState: { [index: number]: { loading?: boolean; error?: string } };
|
||||
}
|
||||
|
||||
export const useWorkflowStateStore = defineStore("workflowStateStore", {
|
||||
state: (): State => ({
|
||||
inputTerminals: {},
|
||||
outputTerminals: {},
|
||||
draggingPosition: null,
|
||||
draggingTerminal: null,
|
||||
activeNodeId: null,
|
||||
scale: 1,
|
||||
stepPosition: {},
|
||||
stepLoadingState: {},
|
||||
}),
|
||||
getters: {
|
||||
getInputTerminalPosition(state: State) {
|
||||
return (stepId: number, inputName: string) => {
|
||||
return state.inputTerminals[stepId]?.[inputName] as InputTerminalPosition | undefined;
|
||||
};
|
||||
export const useWorkflowStateStore = (workflowId: string) => {
|
||||
return defineStore(`workflowStateStore${workflowId}`, {
|
||||
state: (): State => ({
|
||||
inputTerminals: {},
|
||||
outputTerminals: {},
|
||||
draggingPosition: null,
|
||||
draggingTerminal: null,
|
||||
activeNodeId: null,
|
||||
scale: 1,
|
||||
stepPosition: {},
|
||||
stepLoadingState: {},
|
||||
}),
|
||||
getters: {
|
||||
getInputTerminalPosition(state: State) {
|
||||
return (stepId: number, inputName: string) => {
|
||||
return state.inputTerminals[stepId]?.[inputName] as InputTerminalPosition | undefined;
|
||||
};
|
||||
},
|
||||
getOutputTerminalPosition(state: State) {
|
||||
return (stepId: number, outputName: string) => {
|
||||
return state.outputTerminals[stepId]?.[outputName] as OutputTerminalPosition | undefined;
|
||||
};
|
||||
},
|
||||
getStepLoadingState(state: State) {
|
||||
return (stepId: number) => state.stepLoadingState[stepId];
|
||||
},
|
||||
},
|
||||
getOutputTerminalPosition(state: State) {
|
||||
return (stepId: number, outputName: string) => {
|
||||
return state.outputTerminals[stepId]?.[outputName] as OutputTerminalPosition | undefined;
|
||||
};
|
||||
},
|
||||
getStepLoadingState(state: State) {
|
||||
return (stepId: number) => state.stepLoadingState[stepId];
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setInputTerminalPosition(stepId: number, inputName: string, position: InputTerminalPosition) {
|
||||
if (!this.inputTerminals[stepId]) {
|
||||
Vue.set(this.inputTerminals, stepId, {});
|
||||
}
|
||||
actions: {
|
||||
setInputTerminalPosition(stepId: number, inputName: string, position: InputTerminalPosition) {
|
||||
if (!this.inputTerminals[stepId]) {
|
||||
Vue.set(this.inputTerminals, stepId, {});
|
||||
}
|
||||
|
||||
Vue.set(this.inputTerminals[stepId]!, inputName, position);
|
||||
},
|
||||
setOutputTerminalPosition(stepId: number, outputName: string, position: OutputTerminalPosition) {
|
||||
if (!this.outputTerminals[stepId]) {
|
||||
Vue.set(this.outputTerminals, stepId, reactive({}));
|
||||
}
|
||||
Vue.set(this.inputTerminals[stepId]!, inputName, position);
|
||||
},
|
||||
setOutputTerminalPosition(stepId: number, outputName: string, position: OutputTerminalPosition) {
|
||||
if (!this.outputTerminals[stepId]) {
|
||||
Vue.set(this.outputTerminals, stepId, reactive({}));
|
||||
}
|
||||
|
||||
Vue.set(this.outputTerminals[stepId]!, outputName, position);
|
||||
Vue.set(this.outputTerminals[stepId]!, outputName, position);
|
||||
},
|
||||
deleteInputTerminalPosition(stepId: number, inputName: string) {
|
||||
delete this.inputTerminals[stepId]?.[inputName];
|
||||
},
|
||||
deleteOutputTerminalPosition(stepId: number, outputName: string) {
|
||||
delete this.outputTerminals[stepId]?.[outputName];
|
||||
},
|
||||
setActiveNode(nodeId: number | null) {
|
||||
this.activeNodeId = nodeId;
|
||||
},
|
||||
setScale(scale: number) {
|
||||
this.scale = scale;
|
||||
},
|
||||
setStepPosition(stepId: number, position: UnwrapRef<UseElementBoundingReturn>) {
|
||||
Vue.set(this.stepPosition, stepId, position);
|
||||
},
|
||||
deleteStepPosition(stepId: number) {
|
||||
delete this.stepPosition[stepId];
|
||||
},
|
||||
setLoadingState(stepId: number, loading: boolean, error: string | undefined) {
|
||||
Vue.set(this.stepLoadingState, stepId, { loading, error });
|
||||
},
|
||||
},
|
||||
deleteInputTerminalPosition(stepId: number, inputName: string) {
|
||||
delete this.inputTerminals[stepId]?.[inputName];
|
||||
},
|
||||
deleteOutputTerminalPosition(stepId: number, outputName: string) {
|
||||
delete this.outputTerminals[stepId]?.[outputName];
|
||||
},
|
||||
setActiveNode(nodeId: number | null) {
|
||||
this.activeNodeId = nodeId;
|
||||
},
|
||||
setScale(scale: number) {
|
||||
this.scale = scale;
|
||||
},
|
||||
setStepPosition(stepId: number, position: UnwrapRef<UseElementBoundingReturn>) {
|
||||
Vue.set(this.stepPosition, stepId, position);
|
||||
},
|
||||
deleteStepPosition(stepId: number) {
|
||||
delete this.stepPosition[stepId];
|
||||
},
|
||||
setLoadingState(stepId: number, loading: boolean, error: string | undefined) {
|
||||
Vue.set(this.stepLoadingState, stepId, { loading, error });
|
||||
},
|
||||
},
|
||||
});
|
||||
})();
|
||||
};
|
||||
|
||||
@@ -32,29 +32,29 @@ describe("Connection Store", () => {
|
||||
});
|
||||
|
||||
it("adds step", () => {
|
||||
const stepStore = useWorkflowStepStore();
|
||||
const stepStore = useWorkflowStepStore("mock-workflow");
|
||||
expect(stepStore.steps).toStrictEqual({});
|
||||
stepStore.addStep(workflowStepZero);
|
||||
expect(stepStore.getStep(0)).toStrictEqual(workflowStepZero);
|
||||
expect(workflowStepZero.id).toBe(0);
|
||||
});
|
||||
it("removes step", () => {
|
||||
const stepStore = useWorkflowStepStore();
|
||||
const stepStore = useWorkflowStepStore("mock-workflow");
|
||||
const addedStep = stepStore.addStep(workflowStepZero);
|
||||
expect(addedStep.id).toBe(0);
|
||||
stepStore.removeStep(addedStep.id);
|
||||
expect(stepStore.getStep(0)).toBe(undefined);
|
||||
});
|
||||
it("creates connection if step has connection", () => {
|
||||
const stepStore = useWorkflowStepStore();
|
||||
const connectionStore = useConnectionStore();
|
||||
const stepStore = useWorkflowStepStore("mock-workflow");
|
||||
const connectionStore = useConnectionStore("mock-workflow");
|
||||
stepStore.addStep(workflowStepZero);
|
||||
stepStore.addStep(workflowStepOne);
|
||||
expect(connectionStore.connections.length).toBe(1);
|
||||
});
|
||||
it("removes connection if step has connection", () => {
|
||||
const stepStore = useWorkflowStepStore();
|
||||
const connectionStore = useConnectionStore();
|
||||
const stepStore = useWorkflowStepStore("mock-workflow");
|
||||
const connectionStore = useConnectionStore("mock-workflow");
|
||||
stepStore.addStep(workflowStepZero);
|
||||
const stepOne = stepStore.addStep(workflowStepOne);
|
||||
expect(connectionStore.connections.length).toBe(1);
|
||||
|
||||
@@ -142,165 +142,170 @@ interface StepInputMapOver {
|
||||
[index: number]: { [index: string]: CollectionTypeDescriptor };
|
||||
}
|
||||
|
||||
export const useWorkflowStepStore = defineStore("workflowStepStore", {
|
||||
state: (): State => ({
|
||||
steps: {} as Steps,
|
||||
stepMapOver: {} as { [index: number]: CollectionTypeDescriptor },
|
||||
stepInputMapOver: {} as StepInputMapOver,
|
||||
stepIndex: -1,
|
||||
stepExtraInputs: {} as { [index: number]: InputTerminalSource[] },
|
||||
}),
|
||||
getters: {
|
||||
getStep(state: State) {
|
||||
return (stepId: number): Step | undefined => {
|
||||
return state.steps[stepId.toString()];
|
||||
};
|
||||
export const useWorkflowStepStore = (workflowId: string) => {
|
||||
return defineStore(`workflowStepStore${workflowId}`, {
|
||||
state: (): State => ({
|
||||
steps: {} as Steps,
|
||||
stepMapOver: {} as { [index: number]: CollectionTypeDescriptor },
|
||||
stepInputMapOver: {} as StepInputMapOver,
|
||||
stepIndex: -1,
|
||||
stepExtraInputs: {} as { [index: number]: InputTerminalSource[] },
|
||||
}),
|
||||
getters: {
|
||||
getStep(state: State) {
|
||||
return (stepId: number): Step | undefined => {
|
||||
return state.steps[stepId.toString()];
|
||||
};
|
||||
},
|
||||
getStepExtraInputs(state: State) {
|
||||
return (stepId: number) => this.stepExtraInputs[stepId] || [];
|
||||
},
|
||||
getStepIndex(state: State) {
|
||||
return Math.max(...Object.values(state.steps).map((step) => step.id), state.stepIndex);
|
||||
},
|
||||
hasActiveOutputs(state: State) {
|
||||
return Boolean(Object.values(state.steps).find((step) => step.workflow_outputs?.length));
|
||||
},
|
||||
workflowOutputs(state: State) {
|
||||
const workflowOutputs: WorkflowOutputs = {};
|
||||
Object.values(state.steps).forEach((step) => {
|
||||
if (step.workflow_outputs?.length) {
|
||||
step.workflow_outputs.forEach((workflowOutput) => {
|
||||
if (workflowOutput.label) {
|
||||
workflowOutputs[workflowOutput.label] = {
|
||||
outputName: workflowOutput.output_name,
|
||||
stepId: step.id,
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return workflowOutputs;
|
||||
},
|
||||
},
|
||||
getStepExtraInputs(state: State) {
|
||||
return (stepId: number) => this.stepExtraInputs[stepId] || [];
|
||||
},
|
||||
getStepIndex(state: State) {
|
||||
return Math.max(...Object.values(state.steps).map((step) => step.id), state.stepIndex);
|
||||
},
|
||||
hasActiveOutputs(state: State) {
|
||||
return Boolean(Object.values(state.steps).find((step) => step.workflow_outputs?.length));
|
||||
},
|
||||
workflowOutputs(state: State) {
|
||||
const workflowOutputs: WorkflowOutputs = {};
|
||||
Object.values(state.steps).forEach((step) => {
|
||||
if (step.workflow_outputs?.length) {
|
||||
step.workflow_outputs.forEach((workflowOutput) => {
|
||||
if (workflowOutput.label) {
|
||||
workflowOutputs[workflowOutput.label] = {
|
||||
outputName: workflowOutput.output_name,
|
||||
stepId: step.id,
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return workflowOutputs;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
addStep(newStep: NewStep): Step {
|
||||
const stepId = newStep.id ? newStep.id : this.getStepIndex + 1;
|
||||
const step = Object.freeze({ ...newStep, id: stepId } as Step);
|
||||
Vue.set(this.steps, stepId.toString(), step);
|
||||
const connectionStore = useConnectionStore();
|
||||
stepToConnections(step).map((connection) => connectionStore.addConnection(connection));
|
||||
this.stepExtraInputs[step.id] = getStepExtraInputs(step);
|
||||
return step;
|
||||
},
|
||||
insertNewStep(
|
||||
contentId: NewStep["content_id"],
|
||||
name: NewStep["name"],
|
||||
type: NewStep["type"],
|
||||
position: NewStep["position"]
|
||||
) {
|
||||
const stepData: NewStep = {
|
||||
name: name,
|
||||
content_id: contentId,
|
||||
input_connections: {},
|
||||
type: type,
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
position: position,
|
||||
post_job_actions: {},
|
||||
tool_state: {},
|
||||
};
|
||||
return this.addStep(stepData);
|
||||
},
|
||||
updateStep(this: State, step: Step) {
|
||||
const workflow_outputs = step.workflow_outputs?.filter((workflowOutput) =>
|
||||
step.outputs.find((output) => workflowOutput.output_name == output.name)
|
||||
);
|
||||
this.steps[step.id.toString()] = Object.freeze({ ...step, workflow_outputs });
|
||||
this.stepExtraInputs[step.id] = getStepExtraInputs(step);
|
||||
},
|
||||
changeStepMapOver(stepId: number, mapOver: CollectionTypeDescriptor) {
|
||||
Vue.set(this.stepMapOver, stepId, mapOver);
|
||||
},
|
||||
resetStepInputMapOver(stepId: number) {
|
||||
Vue.set(this.stepInputMapOver, stepId, {});
|
||||
},
|
||||
changeStepInputMapOver(stepId: number, inputName: string, mapOver: CollectionTypeDescriptor) {
|
||||
if (this.stepInputMapOver[stepId]) {
|
||||
Vue.set(this.stepInputMapOver[stepId]!, inputName, mapOver);
|
||||
} else {
|
||||
Vue.set(this.stepInputMapOver, stepId, { [inputName]: mapOver });
|
||||
}
|
||||
},
|
||||
addConnection(connection: Connection) {
|
||||
const inputStep = this.getStep(connection.input.stepId);
|
||||
assertDefined(
|
||||
inputStep,
|
||||
`Failed to add connection, because step with id ${connection.input.stepId} is undefined`
|
||||
);
|
||||
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;
|
||||
}
|
||||
let connectionLinks: ConnectionOutputLink[] = [connectionLink];
|
||||
let inputConnection = inputStep.input_connections[connection.input.name];
|
||||
if (inputConnection) {
|
||||
if (!Array.isArray(inputConnection)) {
|
||||
inputConnection = [inputConnection];
|
||||
}
|
||||
inputConnection = inputConnection.filter(
|
||||
(connection) =>
|
||||
!(connection.id === connectionLink.id && connection.output_name === connectionLink.output_name)
|
||||
actions: {
|
||||
addStep(newStep: NewStep): Step {
|
||||
const stepId = newStep.id ? newStep.id : this.getStepIndex + 1;
|
||||
const step = Object.freeze({ ...newStep, id: stepId } as Step);
|
||||
Vue.set(this.steps, stepId.toString(), step);
|
||||
const connectionStore = useConnectionStore(workflowId);
|
||||
stepToConnections(step).map((connection) => connectionStore.addConnection(connection));
|
||||
this.stepExtraInputs[step.id] = getStepExtraInputs(step);
|
||||
return step;
|
||||
},
|
||||
insertNewStep(
|
||||
contentId: NewStep["content_id"],
|
||||
name: NewStep["name"],
|
||||
type: NewStep["type"],
|
||||
position: NewStep["position"]
|
||||
) {
|
||||
const stepData: NewStep = {
|
||||
name: name,
|
||||
content_id: contentId,
|
||||
input_connections: {},
|
||||
type: type,
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
position: position,
|
||||
post_job_actions: {},
|
||||
tool_state: {},
|
||||
};
|
||||
return this.addStep(stepData);
|
||||
},
|
||||
updateStep(this: State, step: Step) {
|
||||
const workflow_outputs = step.workflow_outputs?.filter((workflowOutput) =>
|
||||
step.outputs.find((output) => workflowOutput.output_name == output.name)
|
||||
);
|
||||
connectionLinks = [...connectionLinks, ...inputConnection];
|
||||
}
|
||||
const updatedStep = {
|
||||
...inputStep,
|
||||
input_connections: {
|
||||
...inputStep.input_connections,
|
||||
[connection.input.name]: connectionLinks.sort((a, b) =>
|
||||
a.id === b.id ? a.output_name.localeCompare(b.output_name) : a.id - b.id
|
||||
),
|
||||
},
|
||||
};
|
||||
this.updateStep(updatedStep);
|
||||
},
|
||||
removeConnection(connection: Connection) {
|
||||
const inputStep = this.getStep(connection.input.stepId);
|
||||
assertDefined(
|
||||
inputStep,
|
||||
`Failed to remove connection, because step with id ${connection.input.stepId} is undefined`
|
||||
);
|
||||
|
||||
const inputConnections = inputStep.input_connections[connection.input.name];
|
||||
if (this.getStepExtraInputs(inputStep.id).find((input) => connection.input.name === input.name)) {
|
||||
inputStep.input_connections[connection.input.name] = undefined;
|
||||
} else {
|
||||
if (Array.isArray(inputConnections)) {
|
||||
inputStep.input_connections[connection.input.name] = inputConnections.filter(
|
||||
(outputLink) =>
|
||||
!(outputLink.id === connection.output.stepId,
|
||||
outputLink.output_name === connection.output.name)
|
||||
);
|
||||
this.steps[step.id.toString()] = Object.freeze({ ...step, workflow_outputs });
|
||||
this.stepExtraInputs[step.id] = getStepExtraInputs(step);
|
||||
},
|
||||
changeStepMapOver(stepId: number, mapOver: CollectionTypeDescriptor) {
|
||||
Vue.set(this.stepMapOver, stepId, mapOver);
|
||||
},
|
||||
resetStepInputMapOver(stepId: number) {
|
||||
Vue.set(this.stepInputMapOver, stepId, {});
|
||||
},
|
||||
changeStepInputMapOver(stepId: number, inputName: string, mapOver: CollectionTypeDescriptor) {
|
||||
if (this.stepInputMapOver[stepId]) {
|
||||
Vue.set(this.stepInputMapOver[stepId]!, inputName, mapOver);
|
||||
} else {
|
||||
Vue.delete(inputStep.input_connections, connection.input.name);
|
||||
Vue.set(this.stepInputMapOver, stepId, { [inputName]: mapOver });
|
||||
}
|
||||
}
|
||||
this.updateStep(inputStep);
|
||||
},
|
||||
addConnection(connection: Connection) {
|
||||
const inputStep = this.getStep(connection.input.stepId);
|
||||
assertDefined(
|
||||
inputStep,
|
||||
`Failed to add connection, because step with id ${connection.input.stepId} is undefined`
|
||||
);
|
||||
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;
|
||||
}
|
||||
let connectionLinks: ConnectionOutputLink[] = [connectionLink];
|
||||
let inputConnection = inputStep.input_connections[connection.input.name];
|
||||
if (inputConnection) {
|
||||
if (!Array.isArray(inputConnection)) {
|
||||
inputConnection = [inputConnection];
|
||||
}
|
||||
inputConnection = inputConnection.filter(
|
||||
(connection) =>
|
||||
!(
|
||||
connection.id === connectionLink.id &&
|
||||
connection.output_name === connectionLink.output_name
|
||||
)
|
||||
);
|
||||
connectionLinks = [...connectionLinks, ...inputConnection];
|
||||
}
|
||||
const updatedStep = {
|
||||
...inputStep,
|
||||
input_connections: {
|
||||
...inputStep.input_connections,
|
||||
[connection.input.name]: connectionLinks.sort((a, b) =>
|
||||
a.id === b.id ? a.output_name.localeCompare(b.output_name) : a.id - b.id
|
||||
),
|
||||
},
|
||||
};
|
||||
this.updateStep(updatedStep);
|
||||
},
|
||||
removeConnection(connection: Connection) {
|
||||
const inputStep = this.getStep(connection.input.stepId);
|
||||
assertDefined(
|
||||
inputStep,
|
||||
`Failed to remove connection, because step with id ${connection.input.stepId} is undefined`
|
||||
);
|
||||
|
||||
const inputConnections = inputStep.input_connections[connection.input.name];
|
||||
if (this.getStepExtraInputs(inputStep.id).find((input) => connection.input.name === input.name)) {
|
||||
inputStep.input_connections[connection.input.name] = undefined;
|
||||
} else {
|
||||
if (Array.isArray(inputConnections)) {
|
||||
inputStep.input_connections[connection.input.name] = inputConnections.filter(
|
||||
(outputLink) =>
|
||||
!(outputLink.id === connection.output.stepId,
|
||||
outputLink.output_name === connection.output.name)
|
||||
);
|
||||
} else {
|
||||
Vue.delete(inputStep.input_connections, connection.input.name);
|
||||
}
|
||||
}
|
||||
this.updateStep(inputStep);
|
||||
},
|
||||
removeStep(this: State, stepId: number) {
|
||||
const connectionStore = useConnectionStore(workflowId);
|
||||
connectionStore
|
||||
.getConnectionsForStep(stepId)
|
||||
.forEach((connection) => connectionStore.removeConnection(getConnectionId(connection)));
|
||||
Vue.delete(this.steps, stepId.toString());
|
||||
Vue.delete(this.stepExtraInputs, stepId);
|
||||
},
|
||||
},
|
||||
removeStep(this: State, stepId: number) {
|
||||
const connectionStore = useConnectionStore();
|
||||
connectionStore
|
||||
.getConnectionsForStep(stepId)
|
||||
.forEach((connection) => connectionStore.removeConnection(getConnectionId(connection)));
|
||||
Vue.delete(this.steps, stepId.toString());
|
||||
Vue.delete(this.stepExtraInputs, stepId);
|
||||
},
|
||||
},
|
||||
});
|
||||
})();
|
||||
};
|
||||
|
||||
export function stepToConnections(step: Step): Connection[] {
|
||||
const connections: Connection[] = [];
|
||||
|
||||
Reference in New Issue
Block a user