mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-14 08:34:07 -08:00
feat(editor): Add executing state and disable node run button while executing on new canvas (no-changelog) (#11079)
This commit is contained in:
parent
9ed8040ec0
commit
6d716494a4
|
@ -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',
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in a new issue