feat(editor): Add executing state and disable node run button while executing on new canvas (no-changelog) (#11079)

This commit is contained in:
Alex Grozav 2024-10-03 15:29:41 +03:00 committed by GitHub
parent 9ed8040ec0
commit 6d716494a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 59 additions and 10 deletions

View file

@ -1,11 +1,13 @@
import { CanvasNodeHandleKey, CanvasNodeKey } from '@/constants'; import { CanvasKey, CanvasNodeHandleKey, CanvasNodeKey } from '@/constants';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import type { import type {
CanvasInjectionData,
CanvasNode, CanvasNode,
CanvasNodeData, CanvasNodeData,
CanvasNodeEventBusEvents, CanvasNodeEventBusEvents,
CanvasNodeHandleInjectionData, CanvasNodeHandleInjectionData,
CanvasNodeInjectionData, CanvasNodeInjectionData,
ConnectStartEvent,
ExecutionOutputMapData, ExecutionOutputMapData,
} from '@/types'; } from '@/types';
import { CanvasConnectionMode, CanvasNodeRenderType } from '@/types'; import { CanvasConnectionMode, CanvasNodeRenderType } from '@/types';
@ -88,6 +90,21 @@ export function createCanvasNodeProps({
}; };
} }
export function createCanvasProvide({
isExecuting = false,
connectingHandle = undefined,
}: {
isExecuting?: boolean;
connectingHandle?: ConnectStartEvent;
} = {}) {
return {
[String(CanvasKey)]: {
isExecuting: ref(isExecuting),
connectingHandle: ref(connectingHandle),
} satisfies CanvasInjectionData,
};
}
export function createCanvasNodeProvide({ export function createCanvasNodeProvide({
id = 'node', id = 'node',
label = 'Test Node', label = 'Test Node',

View file

@ -18,7 +18,7 @@ import { Background } from '@vue-flow/background';
import { MiniMap } from '@vue-flow/minimap'; import { MiniMap } from '@vue-flow/minimap';
import Node from './elements/nodes/CanvasNode.vue'; import Node from './elements/nodes/CanvasNode.vue';
import Edge from './elements/edges/CanvasEdge.vue'; import Edge from './elements/edges/CanvasEdge.vue';
import { computed, onMounted, onUnmounted, provide, ref, useCssModule, watch } from 'vue'; import { computed, onMounted, onUnmounted, provide, ref, toRef, useCssModule, watch } from 'vue';
import type { EventBus } from 'n8n-design-system'; import type { EventBus } from 'n8n-design-system';
import { createEventBus } from 'n8n-design-system'; import { createEventBus } from 'n8n-design-system';
import { useContextMenu, type ContextMenuAction } from '@/composables/useContextMenu'; import { useContextMenu, type ContextMenuAction } from '@/composables/useContextMenu';
@ -78,6 +78,7 @@ const props = withDefaults(
controlsPosition?: PanelPosition; controlsPosition?: PanelPosition;
eventBus?: EventBus<CanvasEventBusEvents>; eventBus?: EventBus<CanvasEventBusEvents>;
readOnly?: boolean; readOnly?: boolean;
executing?: boolean;
keyBindings?: boolean; keyBindings?: boolean;
}>(), }>(),
{ {
@ -87,6 +88,7 @@ const props = withDefaults(
controlsPosition: PanelPosition.BottomLeft, controlsPosition: PanelPosition.BottomLeft,
eventBus: () => createEventBus(), eventBus: () => createEventBus(),
readOnly: false, readOnly: false,
executing: false,
keyBindings: true, keyBindings: true,
}, },
); );
@ -480,8 +482,11 @@ watch(() => props.readOnly, setReadonly, {
* Provide * Provide
*/ */
const isExecuting = toRef(props, 'executing');
provide(CanvasKey, { provide(CanvasKey, {
connectingHandle, connectingHandle,
isExecuting,
}); });
</script> </script>

View file

@ -21,6 +21,7 @@ const props = withDefaults(
fallbackNodes?: IWorkflowDb['nodes']; fallbackNodes?: IWorkflowDb['nodes'];
eventBus?: EventBus<CanvasEventBusEvents>; eventBus?: EventBus<CanvasEventBusEvents>;
readOnly?: boolean; readOnly?: boolean;
executing?: boolean;
}>(), }>(),
{ {
id: 'canvas', id: 'canvas',

View file

@ -1,7 +1,7 @@
import { fireEvent } from '@testing-library/vue'; import { fireEvent } from '@testing-library/vue';
import CanvasNodeToolbar from '@/components/canvas/elements/nodes/CanvasNodeToolbar.vue'; import CanvasNodeToolbar from '@/components/canvas/elements/nodes/CanvasNodeToolbar.vue';
import { createComponentRenderer } from '@/__tests__/render'; import { createComponentRenderer } from '@/__tests__/render';
import { createCanvasNodeProvide } from '@/__tests__/data'; import { createCanvasNodeProvide, createCanvasProvide } from '@/__tests__/data';
import { CanvasNodeRenderType } from '@/types'; import { CanvasNodeRenderType } from '@/types';
const renderComponent = createComponentRenderer(CanvasNodeToolbar); const renderComponent = createComponentRenderer(CanvasNodeToolbar);
@ -12,6 +12,7 @@ describe('CanvasNodeToolbar', () => {
global: { global: {
provide: { provide: {
...createCanvasNodeProvide(), ...createCanvasNodeProvide(),
...createCanvasProvide(),
}, },
}, },
}); });
@ -19,6 +20,21 @@ describe('CanvasNodeToolbar', () => {
expect(getByTestId('execute-node-button')).toBeInTheDocument(); expect(getByTestId('execute-node-button')).toBeInTheDocument();
}); });
it('should render disabled execute node button when canvas is executing', () => {
const { getByTestId } = renderComponent({
global: {
provide: {
...createCanvasNodeProvide(),
...createCanvasProvide({
isExecuting: true,
}),
},
},
});
expect(getByTestId('execute-node-button')).toBeDisabled();
});
it('should not render execute node button when renderType is configuration', async () => { it('should not render execute node button when renderType is configuration', async () => {
const { queryByTestId } = renderComponent({ const { queryByTestId } = renderComponent({
global: { global: {
@ -31,6 +47,7 @@ describe('CanvasNodeToolbar', () => {
}, },
}, },
}), }),
...createCanvasProvide(),
}, },
}, },
}); });
@ -43,6 +60,7 @@ describe('CanvasNodeToolbar', () => {
global: { global: {
provide: { provide: {
...createCanvasNodeProvide(), ...createCanvasNodeProvide(),
...createCanvasProvide(),
}, },
}, },
}); });
@ -57,6 +75,7 @@ describe('CanvasNodeToolbar', () => {
global: { global: {
provide: { provide: {
...createCanvasNodeProvide(), ...createCanvasNodeProvide(),
...createCanvasProvide(),
}, },
}, },
}); });
@ -71,6 +90,7 @@ describe('CanvasNodeToolbar', () => {
global: { global: {
provide: { provide: {
...createCanvasNodeProvide(), ...createCanvasNodeProvide(),
...createCanvasProvide(),
}, },
}, },
}); });
@ -85,6 +105,7 @@ describe('CanvasNodeToolbar', () => {
global: { global: {
provide: { provide: {
...createCanvasNodeProvide(), ...createCanvasNodeProvide(),
...createCanvasProvide(),
}, },
}, },
}); });
@ -106,6 +127,7 @@ describe('CanvasNodeToolbar', () => {
}, },
}, },
}), }),
...createCanvasProvide(),
}, },
}, },
}); });

View file

@ -3,6 +3,7 @@ import { computed, useCssModule } from 'vue';
import { useI18n } from '@/composables/useI18n'; import { useI18n } from '@/composables/useI18n';
import { useCanvasNode } from '@/composables/useCanvasNode'; import { useCanvasNode } from '@/composables/useCanvasNode';
import { CanvasNodeRenderType } from '@/types'; import { CanvasNodeRenderType } from '@/types';
import { useCanvas } from '@/composables/useCanvas';
const emit = defineEmits<{ const emit = defineEmits<{
delete: []; delete: [];
@ -19,13 +20,12 @@ const props = defineProps<{
const $style = useCssModule(); const $style = useCssModule();
const i18n = useI18n(); const i18n = useI18n();
const { render } = useCanvasNode(); const { isExecuting } = useCanvas();
const { isDisabled, render } = useCanvasNode();
// @TODO const nodeDisabledTitle = computed(() => {
const workflowRunning = false; return isDisabled.value ? i18n.baseText('node.disable') : i18n.baseText('node.enable');
});
// @TODO
const nodeDisabledTitle = 'Test';
const classes = computed(() => ({ const classes = computed(() => ({
[$style.canvasNodeToolbar]: true, [$style.canvasNodeToolbar]: true,
@ -84,7 +84,7 @@ function onOpenContextMenu(event: MouseEvent) {
text text
size="small" size="small"
icon="play" icon="play"
:disabled="workflowRunning" :disabled="isExecuting"
:title="i18n.baseText('node.testStep')" :title="i18n.baseText('node.testStep')"
@click="executeNode" @click="executeNode"
/> />

View file

@ -5,8 +5,10 @@ export function useCanvas() {
const canvas = inject(CanvasKey); const canvas = inject(CanvasKey);
const connectingHandle = computed(() => canvas?.connectingHandle.value); const connectingHandle = computed(() => canvas?.connectingHandle.value);
const isExecuting = computed(() => canvas?.isExecuting.value);
return { return {
isExecuting,
connectingHandle, connectingHandle,
}; };
} }

View file

@ -132,6 +132,7 @@ export type CanvasConnectionCreateData = {
}; };
export interface CanvasInjectionData { export interface CanvasInjectionData {
isExecuting: Ref<boolean | undefined>;
connectingHandle: Ref<ConnectStartEvent | undefined>; connectingHandle: Ref<ConnectStartEvent | undefined>;
} }

View file

@ -1568,6 +1568,7 @@ onBeforeUnmount(() => {
:fallback-nodes="fallbackNodes" :fallback-nodes="fallbackNodes"
:event-bus="canvasEventBus" :event-bus="canvasEventBus"
:read-only="isCanvasReadOnly" :read-only="isCanvasReadOnly"
:executing="isWorkflowRunning"
:key-bindings="keyBindingsEnabled" :key-bindings="keyBindingsEnabled"
@update:nodes:position="onUpdateNodesPosition" @update:nodes:position="onUpdateNodesPosition"
@update:node:position="onUpdateNodePosition" @update:node:position="onUpdateNodePosition"