feat(Loop Over Items (Split in Batches) Node): Automatically add a loop + rename (#7228)
Github issue / Community forum post (link here to close automatically): --------- Co-authored-by: Michael Kret <michael.k@radency.com>
This commit is contained in:
@@ -1,8 +1,107 @@
|
||||
<script setup lang="ts">
|
||||
import { defineAsyncComponent, reactive } from 'vue';
|
||||
import { getMidCanvasPosition } from '@/utils/nodeViewUtils';
|
||||
import {
|
||||
DEFAULT_STICKY_HEIGHT,
|
||||
DEFAULT_STICKY_WIDTH,
|
||||
NODE_CREATOR_OPEN_SOURCES,
|
||||
STICKY_NODE_TYPE,
|
||||
} from '@/constants';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import type { AddedNodesAndConnections, ToggleNodeCreatorOptions } from '@/Interface';
|
||||
import { useActions } from './NodeCreator/composables/useActions';
|
||||
|
||||
type Props = {
|
||||
nodeViewScale: number;
|
||||
createNodeActive?: boolean;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unsafe-assignment
|
||||
const NodeCreator = defineAsyncComponent(
|
||||
async () => import('@/components/Node/NodeCreator/NodeCreator.vue'),
|
||||
);
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
createNodeActive: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'addNodes', value: AddedNodesAndConnections): void;
|
||||
(event: 'toggleNodeCreator', value: ToggleNodeCreatorOptions): void;
|
||||
}>();
|
||||
|
||||
const state = reactive({
|
||||
showStickyButton: false,
|
||||
});
|
||||
|
||||
const uiStore = useUIStore();
|
||||
|
||||
const { getAddedNodesAndConnections } = useActions();
|
||||
|
||||
function onCreateMenuHoverIn(mouseinEvent: MouseEvent) {
|
||||
const buttonsWrapper = mouseinEvent.target as Element;
|
||||
|
||||
// Once the popup menu is hovered, it's pointer events are disabled so it's not interfering with element underneath it.
|
||||
state.showStickyButton = true;
|
||||
const moveCallback = (mousemoveEvent: MouseEvent) => {
|
||||
if (buttonsWrapper) {
|
||||
const wrapperBounds = buttonsWrapper.getBoundingClientRect();
|
||||
const wrapperH = wrapperBounds.height;
|
||||
const wrapperW = wrapperBounds.width;
|
||||
const wrapperLeftNear = wrapperBounds.left;
|
||||
const wrapperLeftFar = wrapperLeftNear + wrapperW;
|
||||
const wrapperTopNear = wrapperBounds.top;
|
||||
const wrapperTopFar = wrapperTopNear + wrapperH;
|
||||
const inside =
|
||||
mousemoveEvent.pageX > wrapperLeftNear &&
|
||||
mousemoveEvent.pageX < wrapperLeftFar &&
|
||||
mousemoveEvent.pageY > wrapperTopNear &&
|
||||
mousemoveEvent.pageY < wrapperTopFar;
|
||||
if (!inside) {
|
||||
state.showStickyButton = false;
|
||||
document.removeEventListener('mousemove', moveCallback, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousemove', moveCallback, false);
|
||||
}
|
||||
|
||||
function openNodeCreator() {
|
||||
emit('toggleNodeCreator', {
|
||||
source: NODE_CREATOR_OPEN_SOURCES.ADD_NODE_BUTTON,
|
||||
createNodeActive: true,
|
||||
});
|
||||
}
|
||||
|
||||
function addStickyNote() {
|
||||
if (document.activeElement) {
|
||||
(document.activeElement as HTMLElement).blur();
|
||||
}
|
||||
|
||||
const offset: [number, number] = [...uiStore.nodeViewOffsetPosition];
|
||||
|
||||
const position = getMidCanvasPosition(props.nodeViewScale, offset);
|
||||
position[0] -= DEFAULT_STICKY_WIDTH / 2;
|
||||
position[1] -= DEFAULT_STICKY_HEIGHT / 2;
|
||||
|
||||
emit('addNodes', getAddedNodesAndConnections([{ type: STICKY_NODE_TYPE, position }]));
|
||||
}
|
||||
|
||||
function closeNodeCreator() {
|
||||
emit('toggleNodeCreator', { createNodeActive: false });
|
||||
}
|
||||
|
||||
function nodeTypeSelected(nodeTypes: string[]) {
|
||||
emit('addNodes', getAddedNodesAndConnections(nodeTypes.map((type) => ({ type }))));
|
||||
closeNodeCreator();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
v-if="!createNodeActive"
|
||||
:class="[$style.nodeButtonsWrapper, showStickyButton ? $style.noEvents : '']"
|
||||
:class="[$style.nodeButtonsWrapper, state.showStickyButton ? $style.noEvents : '']"
|
||||
@mouseenter="onCreateMenuHoverIn"
|
||||
>
|
||||
<div :class="$style.nodeCreatorButton" data-test-id="node-creator-plus-button">
|
||||
@@ -15,7 +114,7 @@
|
||||
:title="$locale.baseText('nodeView.addNode')"
|
||||
/>
|
||||
<div
|
||||
:class="[$style.addStickyButton, showStickyButton ? $style.visibleButton : '']"
|
||||
:class="[$style.addStickyButton, state.showStickyButton ? $style.visibleButton : '']"
|
||||
@click="addStickyNote"
|
||||
data-test-id="add-sticky-button"
|
||||
>
|
||||
@@ -37,111 +136,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent, defineComponent } from 'vue';
|
||||
import { getMidCanvasPosition } from '@/utils/nodeViewUtils';
|
||||
import {
|
||||
DEFAULT_STICKY_HEIGHT,
|
||||
DEFAULT_STICKY_WIDTH,
|
||||
NODE_CREATOR_OPEN_SOURCES,
|
||||
STICKY_NODE_TYPE,
|
||||
} from '@/constants';
|
||||
import { mapStores } from 'pinia';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
|
||||
const NodeCreator = defineAsyncComponent(
|
||||
async () => import('@/components/Node/NodeCreator/NodeCreator.vue'),
|
||||
);
|
||||
|
||||
export default defineComponent({
|
||||
name: 'node-creation',
|
||||
components: {
|
||||
NodeCreator,
|
||||
},
|
||||
props: {
|
||||
nodeViewScale: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
createNodeActive: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showStickyButton: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useUIStore),
|
||||
},
|
||||
methods: {
|
||||
onCreateMenuHoverIn(mouseinEvent: MouseEvent) {
|
||||
const buttonsWrapper = mouseinEvent.target as Element;
|
||||
|
||||
// Once the popup menu is hovered, it's pointer events are disabled so it's not interfering with element underneath it.
|
||||
this.showStickyButton = true;
|
||||
const moveCallback = (mousemoveEvent: MouseEvent) => {
|
||||
if (buttonsWrapper) {
|
||||
const wrapperBounds = buttonsWrapper.getBoundingClientRect();
|
||||
const wrapperH = wrapperBounds.height;
|
||||
const wrapperW = wrapperBounds.width;
|
||||
const wrapperLeftNear = wrapperBounds.left;
|
||||
const wrapperLeftFar = wrapperLeftNear + wrapperW;
|
||||
const wrapperTopNear = wrapperBounds.top;
|
||||
const wrapperTopFar = wrapperTopNear + wrapperH;
|
||||
const inside =
|
||||
mousemoveEvent.pageX > wrapperLeftNear &&
|
||||
mousemoveEvent.pageX < wrapperLeftFar &&
|
||||
mousemoveEvent.pageY > wrapperTopNear &&
|
||||
mousemoveEvent.pageY < wrapperTopFar;
|
||||
if (!inside) {
|
||||
this.showStickyButton = false;
|
||||
document.removeEventListener('mousemove', moveCallback, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousemove', moveCallback, false);
|
||||
},
|
||||
openNodeCreator() {
|
||||
this.$emit('toggleNodeCreator', {
|
||||
source: NODE_CREATOR_OPEN_SOURCES.ADD_NODE_BUTTON,
|
||||
createNodeActive: true,
|
||||
});
|
||||
},
|
||||
addStickyNote() {
|
||||
if (document.activeElement) {
|
||||
(document.activeElement as HTMLElement).blur();
|
||||
}
|
||||
|
||||
const offset: [number, number] = [...this.uiStore.nodeViewOffsetPosition];
|
||||
|
||||
const position = getMidCanvasPosition(this.nodeViewScale, offset);
|
||||
position[0] -= DEFAULT_STICKY_WIDTH / 2;
|
||||
position[1] -= DEFAULT_STICKY_HEIGHT / 2;
|
||||
|
||||
this.$emit('addNode', [
|
||||
{
|
||||
nodeTypeName: STICKY_NODE_TYPE,
|
||||
position,
|
||||
},
|
||||
]);
|
||||
},
|
||||
closeNodeCreator() {
|
||||
this.$emit('toggleNodeCreator', { createNodeActive: false });
|
||||
},
|
||||
nodeTypeSelected(nodeTypeNames: string[]) {
|
||||
this.$emit(
|
||||
'addNode',
|
||||
nodeTypeNames.map((nodeTypeName) => ({ nodeTypeName })),
|
||||
);
|
||||
this.closeNodeCreator();
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.nodeButtonsWrapper {
|
||||
position: absolute;
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, computed, toRefs, getCurrentInstance } from 'vue';
|
||||
import type { ActionTypeDescription, SimplifiedNodeType } from '@/Interface';
|
||||
import { WEBHOOK_NODE_TYPE } from '@/constants';
|
||||
import { WEBHOOK_NODE_TYPE, DRAG_EVENT_DATA_KEY } from '@/constants';
|
||||
|
||||
import { getNewNodePosition, NODE_SIZE } from '@/utils/nodeViewUtils';
|
||||
import NodeIcon from '@/components/NodeIcon.vue';
|
||||
@@ -41,7 +41,7 @@ const props = defineProps<Props>();
|
||||
const instance = getCurrentInstance();
|
||||
const telemetry = instance?.proxy.$telemetry;
|
||||
|
||||
const { getActionData, getNodeTypesWithManualTrigger, setAddedNodeActionParameters } = useActions();
|
||||
const { getActionData, getAddedNodesAndConnections, setAddedNodeActionParameters } = useActions();
|
||||
const { activeViewStack } = useViewStacks();
|
||||
|
||||
const state = reactive({
|
||||
@@ -72,13 +72,13 @@ function onDragStart(event: DragEvent): void {
|
||||
*/
|
||||
document.body.addEventListener('dragover', onDragOver);
|
||||
const { pageX: x, pageY: y } = event;
|
||||
if (event.dataTransfer) {
|
||||
if (event.dataTransfer && actionData.value.key) {
|
||||
event.dataTransfer.effectAllowed = 'copy';
|
||||
event.dataTransfer.dropEffect = 'copy';
|
||||
event.dataTransfer.setDragImage(state.draggableDataTransfer as Element, 0, 0);
|
||||
event.dataTransfer.setData(
|
||||
'nodeTypeName',
|
||||
getNodeTypesWithManualTrigger(actionData.value?.key).join(','),
|
||||
DRAG_EVENT_DATA_KEY,
|
||||
JSON.stringify(getAddedNodesAndConnections([{ type: actionData.value.key }])),
|
||||
);
|
||||
if (telemetry) {
|
||||
state.storeWatcher = setAddedNodeActionParameters(
|
||||
|
||||
@@ -42,7 +42,11 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import type { SimplifiedNodeType } from '@/Interface';
|
||||
import { COMMUNITY_NODES_INSTALLATION_DOCS_URL, DEFAULT_SUBCATEGORY } from '@/constants';
|
||||
import {
|
||||
COMMUNITY_NODES_INSTALLATION_DOCS_URL,
|
||||
DEFAULT_SUBCATEGORY,
|
||||
DRAG_EVENT_DATA_KEY,
|
||||
} from '@/constants';
|
||||
|
||||
import { isCommunityPackageName } from '@/utils';
|
||||
import { getNewNodePosition, NODE_SIZE } from '@/utils/nodeViewUtils';
|
||||
@@ -67,7 +71,7 @@ const i18n = useI18n();
|
||||
const telemetry = useTelemetry();
|
||||
|
||||
const { actions } = useNodeCreatorStore();
|
||||
const { getNodeTypesWithManualTrigger } = useActions();
|
||||
const { getAddedNodesAndConnections } = useActions();
|
||||
|
||||
const dragging = ref(false);
|
||||
const draggablePosition = ref({ x: -100, y: -100 });
|
||||
@@ -140,8 +144,8 @@ function onDragStart(event: DragEvent): void {
|
||||
event.dataTransfer.dropEffect = 'copy';
|
||||
event.dataTransfer.setDragImage(draggableDataTransfer.value as Element, 0, 0);
|
||||
event.dataTransfer.setData(
|
||||
'nodeTypeName',
|
||||
getNodeTypesWithManualTrigger(props.nodeType.name).join(','),
|
||||
DRAG_EVENT_DATA_KEY,
|
||||
JSON.stringify(getAddedNodesAndConnections([{ type: props.nodeType.name }])),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ function onSelected(actionCreateElement: INodeCreateElement) {
|
||||
|
||||
emit('nodeTypeSelected', [actionData.key as string, actionNode]);
|
||||
} else {
|
||||
emit('nodeTypeSelected', getNodeTypesWithManualTrigger(actionData.key));
|
||||
emit('nodeTypeSelected', [actionData.key as string]);
|
||||
}
|
||||
|
||||
if (telemetry) setAddedNodeActionParameters(actionData, telemetry, rootView.value);
|
||||
|
||||
@@ -18,7 +18,6 @@ import { useNodeCreatorStore } from '@/stores/nodeCreator.store';
|
||||
import { TriggerView, RegularView, AIView, AINodesView } from '../viewsData';
|
||||
import { transformNodeType } from '../utils';
|
||||
import { useViewStacks } from '../composables/useViewStacks';
|
||||
import { useActions } from '../composables/useActions';
|
||||
import { useKeyboardNavigation } from '../composables/useKeyboardNavigation';
|
||||
import ItemsRenderer from '../Renderers/ItemsRenderer.vue';
|
||||
import CategorizedItemsRenderer from '../Renderers/CategorizedItemsRenderer.vue';
|
||||
@@ -38,7 +37,6 @@ const telemetry = useTelemetry();
|
||||
|
||||
const { mergedNodes, actions } = useNodeCreatorStore();
|
||||
const { baseUrl } = useRootStore();
|
||||
const { getNodeTypesWithManualTrigger } = useActions();
|
||||
const { pushViewStack, popViewStack } = useViewStacks();
|
||||
|
||||
const { registerKeyHook } = useKeyboardNavigation();
|
||||
@@ -47,10 +45,7 @@ const activeViewStack = computed(() => useViewStacks().activeViewStack);
|
||||
const globalSearchItemsDiff = computed(() => useViewStacks().globalSearchItemsDiff);
|
||||
|
||||
function selectNodeType(nodeTypes: string[]) {
|
||||
emit(
|
||||
'nodeTypeSelected',
|
||||
nodeTypes.length === 1 ? getNodeTypesWithManualTrigger(nodeTypes[0]) : nodeTypes,
|
||||
);
|
||||
emit('nodeTypeSelected', nodeTypes);
|
||||
}
|
||||
|
||||
function onSelected(item: INodeCreateElement) {
|
||||
|
||||
@@ -31,10 +31,11 @@ import { useKeyboardNavigation } from './composables/useKeyboardNavigation';
|
||||
import { useActionsGenerator } from './composables/useActionsGeneration';
|
||||
import NodesListPanel from './Panel/NodesListPanel.vue';
|
||||
import { useUIStore } from '@/stores';
|
||||
import { DRAG_EVENT_DATA_KEY } from '@/constants';
|
||||
|
||||
export interface Props {
|
||||
active?: boolean;
|
||||
onNodeTypeSelected?: (nodeType: string) => void;
|
||||
onNodeTypeSelected?: (nodeType: string[]) => void;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
@@ -93,12 +94,12 @@ function onDrop(event: DragEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeTypeName = event.dataTransfer.getData('nodeTypeName');
|
||||
const dragData = event.dataTransfer.getData(DRAG_EVENT_DATA_KEY);
|
||||
const nodeCreatorBoundingRect = (state.nodeCreator as Element).getBoundingClientRect();
|
||||
|
||||
// Abort drag end event propagation if dropped inside nodes panel
|
||||
if (
|
||||
nodeTypeName &&
|
||||
dragData &&
|
||||
event.pageX >= nodeCreatorBoundingRect.x &&
|
||||
event.pageY >= nodeCreatorBoundingRect.y
|
||||
) {
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import { setActivePinia } from 'pinia';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { useNodeCreatorStore } from '@/stores/nodeCreator.store';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { useActions } from '../composables/useActions';
|
||||
import {
|
||||
HTTP_REQUEST_NODE_TYPE,
|
||||
MANUAL_TRIGGER_NODE_TYPE,
|
||||
NODE_CREATOR_OPEN_SOURCES,
|
||||
NO_OP_NODE_TYPE,
|
||||
SCHEDULE_TRIGGER_NODE_TYPE,
|
||||
SPLIT_IN_BATCHES_NODE_TYPE,
|
||||
TRIGGER_NODE_CREATOR_VIEW,
|
||||
} from '@/constants';
|
||||
|
||||
describe('useActions', () => {
|
||||
beforeAll(() => {
|
||||
setActivePinia(createTestingPinia());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('getAddedNodesAndConnections', () => {
|
||||
test('should insert a manual trigger node when there are no triggers', () => {
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const nodeCreatorStore = useNodeCreatorStore();
|
||||
|
||||
vi.spyOn(workflowsStore, 'workflowTriggerNodes', 'get').mockReturnValue([]);
|
||||
vi.spyOn(nodeCreatorStore, 'openSource', 'get').mockReturnValue(
|
||||
NODE_CREATOR_OPEN_SOURCES.ADD_NODE_BUTTON,
|
||||
);
|
||||
vi.spyOn(nodeCreatorStore, 'selectedView', 'get').mockReturnValue(TRIGGER_NODE_CREATOR_VIEW);
|
||||
|
||||
const { getAddedNodesAndConnections } = useActions();
|
||||
|
||||
expect(getAddedNodesAndConnections([{ type: HTTP_REQUEST_NODE_TYPE }])).toEqual({
|
||||
connections: [{ from: { nodeIndex: 0 }, to: { nodeIndex: 1 } }],
|
||||
nodes: [
|
||||
{ type: MANUAL_TRIGGER_NODE_TYPE, isAutoAdd: true },
|
||||
{ type: HTTP_REQUEST_NODE_TYPE, openDetail: true },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('should not insert a manual trigger node when there is a trigger in the workflow', () => {
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const nodeCreatorStore = useNodeCreatorStore();
|
||||
|
||||
vi.spyOn(workflowsStore, 'workflowTriggerNodes', 'get').mockReturnValue([
|
||||
{ type: SCHEDULE_TRIGGER_NODE_TYPE } as never,
|
||||
]);
|
||||
vi.spyOn(nodeCreatorStore, 'openSource', 'get').mockReturnValue(
|
||||
NODE_CREATOR_OPEN_SOURCES.ADD_NODE_BUTTON,
|
||||
);
|
||||
vi.spyOn(nodeCreatorStore, 'selectedView', 'get').mockReturnValue(TRIGGER_NODE_CREATOR_VIEW);
|
||||
|
||||
const { getAddedNodesAndConnections } = useActions();
|
||||
|
||||
expect(getAddedNodesAndConnections([{ type: HTTP_REQUEST_NODE_TYPE }])).toEqual({
|
||||
connections: [],
|
||||
nodes: [{ type: HTTP_REQUEST_NODE_TYPE, openDetail: true }],
|
||||
});
|
||||
});
|
||||
|
||||
test('should insert a No Op node when a Loop Over Items Node is added', () => {
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const nodeCreatorStore = useNodeCreatorStore();
|
||||
|
||||
vi.spyOn(workflowsStore, 'workflowTriggerNodes', 'get').mockReturnValue([]);
|
||||
vi.spyOn(nodeCreatorStore, 'openSource', 'get').mockReturnValue(
|
||||
NODE_CREATOR_OPEN_SOURCES.ADD_NODE_BUTTON,
|
||||
);
|
||||
vi.spyOn(nodeCreatorStore, 'selectedView', 'get').mockReturnValue(TRIGGER_NODE_CREATOR_VIEW);
|
||||
|
||||
const { getAddedNodesAndConnections } = useActions();
|
||||
|
||||
expect(getAddedNodesAndConnections([{ type: SPLIT_IN_BATCHES_NODE_TYPE }])).toEqual({
|
||||
connections: [
|
||||
{ from: { nodeIndex: 0 }, to: { nodeIndex: 1 } },
|
||||
{ from: { nodeIndex: 1, outputIndex: 1 }, to: { nodeIndex: 2 } },
|
||||
{ from: { nodeIndex: 2 }, to: { nodeIndex: 1 } },
|
||||
],
|
||||
nodes: [
|
||||
{ isAutoAdd: true, type: MANUAL_TRIGGER_NODE_TYPE },
|
||||
{ openDetail: true, type: SPLIT_IN_BATCHES_NODE_TYPE },
|
||||
{ isAutoAdd: true, name: 'Replace Me', type: NO_OP_NODE_TYPE },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,6 +2,9 @@ import { getCurrentInstance, computed } from 'vue';
|
||||
import type { IDataObject, INodeParameters } from 'n8n-workflow';
|
||||
import type {
|
||||
ActionTypeDescription,
|
||||
AddedNode,
|
||||
AddedNodeConnection,
|
||||
AddedNodesAndConnections,
|
||||
INodeCreateElement,
|
||||
IUpdateInformation,
|
||||
LabelCreateElement,
|
||||
@@ -9,11 +12,14 @@ import type {
|
||||
import {
|
||||
MANUAL_TRIGGER_NODE_TYPE,
|
||||
NODE_CREATOR_OPEN_SOURCES,
|
||||
NO_OP_NODE_TYPE,
|
||||
SCHEDULE_TRIGGER_NODE_TYPE,
|
||||
SPLIT_IN_BATCHES_NODE_TYPE,
|
||||
STICKY_NODE_TYPE,
|
||||
TRIGGER_NODE_CREATOR_VIEW,
|
||||
WEBHOOK_NODE_TYPE,
|
||||
} from '@/constants';
|
||||
import { i18n } from '@/plugins/i18n';
|
||||
|
||||
import type { BaseTextKey } from '@/plugins/i18n';
|
||||
import type { Telemetry } from '@/plugins/telemetry';
|
||||
@@ -144,15 +150,13 @@ export const useActions = () => {
|
||||
};
|
||||
}
|
||||
|
||||
function getNodeTypesWithManualTrigger(nodeType?: string): string[] {
|
||||
if (!nodeType) return [];
|
||||
|
||||
function shouldPrependManualTrigger(addedNodes: AddedNode[]): boolean {
|
||||
const { selectedView, openSource } = useNodeCreatorStore();
|
||||
const { workflowTriggerNodes } = useWorkflowsStore();
|
||||
const isTrigger = useNodeTypesStore().isTriggerNode(nodeType);
|
||||
const hasTrigger = addedNodes.some((node) => useNodeTypesStore().isTriggerNode(node.type));
|
||||
const workflowContainsTrigger = workflowTriggerNodes.length > 0;
|
||||
const isTriggerPanel = selectedView === TRIGGER_NODE_CREATOR_VIEW;
|
||||
const isStickyNode = nodeType === STICKY_NODE_TYPE;
|
||||
const onlyStickyNodes = addedNodes.every((node) => node.type === STICKY_NODE_TYPE);
|
||||
const singleNodeOpenSources = [
|
||||
NODE_CREATOR_OPEN_SOURCES.PLUS_ENDPOINT,
|
||||
NODE_CREATOR_OPEN_SOURCES.NODE_CONNECTION_ACTION,
|
||||
@@ -162,16 +166,65 @@ export const useActions = () => {
|
||||
// If the node creator was opened from the plus endpoint, node connection action, or node connection drop
|
||||
// then we do not want to append the manual trigger
|
||||
const isSingleNodeOpenSource = singleNodeOpenSources.includes(openSource);
|
||||
const shouldAppendManualTrigger =
|
||||
return (
|
||||
!isSingleNodeOpenSource &&
|
||||
!isTrigger &&
|
||||
!hasTrigger &&
|
||||
!workflowContainsTrigger &&
|
||||
isTriggerPanel &&
|
||||
!isStickyNode;
|
||||
!onlyStickyNodes
|
||||
);
|
||||
}
|
||||
|
||||
const nodeTypes = shouldAppendManualTrigger ? [MANUAL_TRIGGER_NODE_TYPE, nodeType] : [nodeType];
|
||||
function getAddedNodesAndConnections(addedNodes: AddedNode[]): AddedNodesAndConnections {
|
||||
if (addedNodes.length === 0) {
|
||||
return { nodes: [], connections: [] };
|
||||
}
|
||||
|
||||
return nodeTypes;
|
||||
const nodes: AddedNode[] = [];
|
||||
const connections: AddedNodeConnection[] = [];
|
||||
|
||||
const nodeToAutoOpen = addedNodes.find((node) => node.type !== MANUAL_TRIGGER_NODE_TYPE);
|
||||
|
||||
if (nodeToAutoOpen) {
|
||||
nodeToAutoOpen.openDetail = true;
|
||||
}
|
||||
|
||||
if (shouldPrependManualTrigger(addedNodes)) {
|
||||
addedNodes.unshift({ type: MANUAL_TRIGGER_NODE_TYPE, isAutoAdd: true });
|
||||
connections.push({
|
||||
from: { nodeIndex: 0 },
|
||||
to: { nodeIndex: 1 },
|
||||
});
|
||||
}
|
||||
|
||||
addedNodes.forEach((node, index) => {
|
||||
nodes.push(node);
|
||||
|
||||
switch (node.type) {
|
||||
case SPLIT_IN_BATCHES_NODE_TYPE: {
|
||||
const splitInBatchesIndex = index;
|
||||
const noOpIndex = splitInBatchesIndex + 1;
|
||||
nodes.push({
|
||||
type: NO_OP_NODE_TYPE,
|
||||
isAutoAdd: true,
|
||||
name: i18n.baseText('nodeView.replaceMe'),
|
||||
});
|
||||
connections.push(
|
||||
{
|
||||
from: { nodeIndex: splitInBatchesIndex, outputIndex: 1 },
|
||||
to: { nodeIndex: noOpIndex },
|
||||
},
|
||||
{
|
||||
from: { nodeIndex: noOpIndex },
|
||||
to: { nodeIndex: splitInBatchesIndex },
|
||||
},
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { nodes, connections };
|
||||
}
|
||||
|
||||
// Hook into addNode action to set the last node parameters & track the action selected
|
||||
@@ -211,7 +264,7 @@ export const useActions = () => {
|
||||
actionsCategoryLocales,
|
||||
getPlaceholderTriggerActions,
|
||||
parseCategoryActions,
|
||||
getNodeTypesWithManualTrigger,
|
||||
getAddedNodesAndConnections,
|
||||
getActionData,
|
||||
setAddedNodeActionParameters,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user