refactor(editor): Convert NodeExecuteButton to composition api (#10703)

This commit is contained in:
Tomi Turtiainen 2024-09-12 17:12:08 +03:00 committed by GitHub
parent 8fdbf25837
commit a8a3de9ff7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts" setup>
import { defineComponent } from 'vue'; import { withDefaults, defineProps, defineEmits, ref, computed } from 'vue';
import { mapStores } from 'pinia';
import { import {
WEBHOOK_NODE_TYPE, WEBHOOK_NODE_TYPE,
MANUAL_TRIGGER_NODE_TYPE, MANUAL_TRIGGER_NODE_TYPE,
@ -8,7 +7,6 @@ import {
FORM_TRIGGER_NODE_TYPE, FORM_TRIGGER_NODE_TYPE,
CHAT_TRIGGER_NODE_TYPE, CHAT_TRIGGER_NODE_TYPE,
} from '@/constants'; } from '@/constants';
import type { INodeUi } from '@/Interface';
import type { INodeTypeDescription } from 'n8n-workflow'; import type { INodeTypeDescription } from 'n8n-workflow';
import { useWorkflowsStore } from '@/stores/workflows.store'; import { useWorkflowsStore } from '@/stores/workflows.store';
import { useNDVStore } from '@/stores/ndv.store'; import { useNDVStore } from '@/stores/ndv.store';
@ -21,274 +19,259 @@ import { usePinnedData } from '@/composables/usePinnedData';
import { useRunWorkflow } from '@/composables/useRunWorkflow'; import { useRunWorkflow } from '@/composables/useRunWorkflow';
import { useUIStore } from '@/stores/ui.store'; import { useUIStore } from '@/stores/ui.store';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useI18n } from '@/composables/useI18n';
import { useTelemetry } from '@/composables/useTelemetry';
const NODE_TEST_STEP_POPUP_COUNT_KEY = 'N8N_NODE_TEST_STEP_POPUP_COUNT'; const NODE_TEST_STEP_POPUP_COUNT_KEY = 'N8N_NODE_TEST_STEP_POPUP_COUNT';
const MAX_POPUP_COUNT = 10; const MAX_POPUP_COUNT = 10;
const POPUP_UPDATE_DELAY = 3000; const POPUP_UPDATE_DELAY = 3000;
export default defineComponent({ const props = withDefaults(
defineProps<{
nodeName: string;
telemetrySource: string;
disabled?: boolean;
label?: string;
type?: string;
size?: string;
transparent?: boolean;
hideIcon?: boolean;
tooltip?: string;
}>(),
{
disabled: false,
transparent: false,
},
);
const emit = defineEmits<{
stopExecution: [];
execute: [];
}>();
defineOptions({
inheritAttrs: false, inheritAttrs: false,
props: { });
nodeName: {
type: String,
required: true,
},
disabled: {
type: Boolean,
default: false,
},
label: {
type: String,
},
type: {
type: String,
},
size: {
type: String,
},
transparent: {
type: Boolean,
default: false,
},
telemetrySource: {
type: String,
},
hideIcon: {
type: Boolean,
},
tooltip: {
type: String,
},
},
emits: ['stopExecution', 'execute'],
setup(props) {
const router = useRouter();
const workflowsStore = useWorkflowsStore();
const node = workflowsStore.getNodeByName(props.nodeName);
const pinnedData = usePinnedData(node);
const externalHooks = useExternalHooks();
const { runWorkflow, stopCurrentExecution } = useRunWorkflow({ router });
return { const lastPopupCountUpdate = ref(0);
externalHooks,
pinnedData, const router = useRouter();
runWorkflow, const { runWorkflow, stopCurrentExecution } = useRunWorkflow({ router });
stopCurrentExecution,
lastPopupCountUpdate: 0, const workflowsStore = useWorkflowsStore();
...useToast(), const externalHooks = useExternalHooks();
...useMessage(), const toast = useToast();
}; const ndvStore = useNDVStore();
}, const nodeTypesStore = useNodeTypesStore();
computed: { const uiStore = useUIStore();
...mapStores(useNodeTypesStore, useNDVStore, useWorkflowsStore, useUIStore), const i18n = useI18n();
node(): INodeUi | null { const message = useMessage();
return this.workflowsStore.getNodeByName(this.nodeName); const telemetry = useTelemetry();
},
nodeType(): INodeTypeDescription | null { const node = computed(() => workflowsStore.getNodeByName(props.nodeName));
if (this.node) { const pinnedData = usePinnedData(node);
return this.nodeTypesStore.getNodeType(this.node.type, this.node.typeVersion);
} const nodeType = computed((): INodeTypeDescription | null => {
return null; return node.value ? nodeTypesStore.getNodeType(node.value.type, node.value.typeVersion) : null;
}, });
nodeRunning(): boolean {
const triggeredNode = this.workflowsStore.executedNode; const isNodeRunning = computed(() => {
const triggeredNode = workflowsStore.executedNode;
return ( return (
this.workflowRunning && uiStore.isActionActive.workflowRunning &&
(this.workflowsStore.isNodeExecuting(this.node?.name ?? '') || (workflowsStore.isNodeExecuting(node.value?.name ?? '') || triggeredNode === node.value?.name)
triggeredNode === this.node?.name)
); );
}, });
workflowRunning(): boolean {
return this.uiStore.isActionActive['workflowRunning']; const isTriggerNode = computed(() => {
}, return node.value ? nodeTypesStore.isTriggerNode(node.value.type) : false;
isTriggerNode(): boolean { });
if (!this.node) {
return false; const isWorkflowRunning = computed(() => uiStore.isActionActive.workflowRunning);
}
return this.nodeTypesStore.isTriggerNode(this.node.type); const isManualTriggerNode = computed(() =>
}, nodeType.value ? nodeType.value.name === MANUAL_TRIGGER_NODE_TYPE : false,
isManualTriggerNode(): boolean { );
return Boolean(this.nodeType && this.nodeType.name === MANUAL_TRIGGER_NODE_TYPE);
}, const isChatNode = computed(() =>
isChatNode(): boolean { nodeType.value ? nodeType.value.name === CHAT_TRIGGER_NODE_TYPE : false,
return Boolean(this.nodeType && this.nodeType.name === CHAT_TRIGGER_NODE_TYPE); );
},
isChatChild(): boolean { const isChatChild = computed(() => workflowsStore.checkIfNodeHasChatParent(props.nodeName));
return this.workflowsStore.checkIfNodeHasChatParent(this.nodeName);
}, const isFormTriggerNode = computed(() =>
isFormTriggerNode(): boolean { nodeType.value ? nodeType.value.name === FORM_TRIGGER_NODE_TYPE : false,
return Boolean(this.nodeType && this.nodeType.name === FORM_TRIGGER_NODE_TYPE); );
},
isPollingTypeNode(): boolean { const isPollingTypeNode = computed(() => !!nodeType.value?.polling);
return !!this.nodeType?.polling;
}, const isScheduleTrigger = computed(() => !!nodeType.value?.group.includes('schedule'));
isScheduleTrigger(): boolean {
return !!this.nodeType?.group.includes('schedule'); const isWebhookNode = computed(() =>
}, nodeType.value ? nodeType.value.name === WEBHOOK_NODE_TYPE : false,
isWebhookNode(): boolean { );
return Boolean(this.nodeType && this.nodeType.name === WEBHOOK_NODE_TYPE);
}, const isListeningForEvents = computed(() => {
isListeningForEvents(): boolean { const waitingOnWebhook = workflowsStore.executionWaitingForWebhook;
const waitingOnWebhook = this.workflowsStore.executionWaitingForWebhook; const executedNode = workflowsStore.executedNode;
const executedNode = this.workflowsStore.executedNode;
return ( return (
!!this.node && !!node.value &&
!this.node.disabled && !node.value.disabled &&
this.isTriggerNode && isTriggerNode.value &&
waitingOnWebhook && waitingOnWebhook &&
(!executedNode || executedNode === this.nodeName) (!executedNode || executedNode === props.nodeName)
); );
}, });
isListeningForWorkflowEvents(): boolean {
const isListeningForWorkflowEvents = computed(() => {
return ( return (
this.nodeRunning && isNodeRunning.value &&
this.isTriggerNode && isTriggerNode.value &&
!this.isScheduleTrigger && !isScheduleTrigger.value &&
!this.isManualTriggerNode !isManualTriggerNode.value
); );
}, });
hasIssues(): boolean {
return Boolean( const hasIssues = computed(() =>
this.node?.issues && (this.node.issues.parameters || this.node.issues.credentials), Boolean(node.value?.issues && (node.value.issues.parameters || node.value.issues.credentials)),
); );
},
disabledHint(): string { const disabledHint = computed(() => {
if (this.isListeningForEvents) { if (isListeningForEvents.value) {
return ''; return '';
} }
if (this.isTriggerNode && this.node?.disabled) { if (isTriggerNode.value && node?.value?.disabled) {
return this.$locale.baseText('ndv.execute.nodeIsDisabled'); return i18n.baseText('ndv.execute.nodeIsDisabled');
} }
if (this.isTriggerNode && this.hasIssues) { if (isTriggerNode.value && hasIssues.value) {
const activeNode = this.ndvStore.activeNode; const activeNode = ndvStore.activeNode;
if (activeNode && activeNode.name !== this.nodeName) { if (activeNode && activeNode.name !== props.nodeName) {
return this.$locale.baseText('ndv.execute.fixPrevious'); return i18n.baseText('ndv.execute.fixPrevious');
} }
return this.$locale.baseText('ndv.execute.requiredFieldsMissing'); return i18n.baseText('ndv.execute.requiredFieldsMissing');
} }
if (this.workflowRunning && !this.nodeRunning) { if (isWorkflowRunning.value && !isNodeRunning.value) {
return this.$locale.baseText('ndv.execute.workflowAlreadyRunning'); return i18n.baseText('ndv.execute.workflowAlreadyRunning');
} }
return ''; return '';
}, });
tooltipText(): string {
if (this.disabledHint) return this.disabledHint; const tooltipText = computed(() => {
if (this.tooltip && !this.loading && this.testStepButtonPopupCount() < MAX_POPUP_COUNT) if (disabledHint.value) return disabledHint.value;
return this.tooltip; if (props.tooltip && !isLoading.value && testStepButtonPopupCount() < MAX_POPUP_COUNT) {
return props.tooltip;
}
return ''; return '';
}, });
buttonLabel(): string {
if (this.isListeningForEvents || this.isListeningForWorkflowEvents) { const buttonLabel = computed(() => {
return this.$locale.baseText('ndv.execute.stopListening'); if (isListeningForEvents.value || isListeningForWorkflowEvents.value) {
return i18n.baseText('ndv.execute.stopListening');
} }
if (this.label) { if (props.label) {
return this.label; return props.label;
} }
if (this.isChatNode) { if (isChatNode.value) {
return this.$locale.baseText('ndv.execute.testChat'); return i18n.baseText('ndv.execute.testChat');
} }
if (this.isWebhookNode) { if (isWebhookNode.value) {
return this.$locale.baseText('ndv.execute.listenForTestEvent'); return i18n.baseText('ndv.execute.listenForTestEvent');
} }
if (this.isFormTriggerNode) { if (isFormTriggerNode.value) {
return this.$locale.baseText('ndv.execute.testStep'); return i18n.baseText('ndv.execute.testStep');
} }
if (this.isPollingTypeNode || this.nodeType?.mockManualExecution) { if (isPollingTypeNode.value || nodeType.value?.mockManualExecution) {
return this.$locale.baseText('ndv.execute.fetchEvent'); return i18n.baseText('ndv.execute.fetchEvent');
} }
return this.$locale.baseText('ndv.execute.testNode'); return i18n.baseText('ndv.execute.testNode');
}, });
loading(): boolean { const isLoading = computed(
return this.nodeRunning && !this.isListeningForEvents && !this.isListeningForWorkflowEvents; () => isNodeRunning.value && !isListeningForEvents.value && !isListeningForWorkflowEvents.value,
}, );
},
methods: { async function stopWaitingForWebhook() {
async stopWaitingForWebhook() {
try { try {
await this.workflowsStore.removeTestWebhook(this.workflowsStore.workflowId); await workflowsStore.removeTestWebhook(workflowsStore.workflowId);
} catch (error) { } catch (error) {
this.showError(error, this.$locale.baseText('ndv.execute.stopWaitingForWebhook.error')); toast.showError(error, 'Error stopping webhook');
return;
} }
}, }
testStepButtonPopupCount() { function testStepButtonPopupCount() {
return Number(localStorage.getItem(NODE_TEST_STEP_POPUP_COUNT_KEY)); return Number(localStorage.getItem(NODE_TEST_STEP_POPUP_COUNT_KEY));
}, }
onMouseOver() { function onMouseOver() {
const count = this.testStepButtonPopupCount(); const count = testStepButtonPopupCount();
if (count < MAX_POPUP_COUNT && !this.disabledHint && this.tooltipText) { if (count < MAX_POPUP_COUNT && !disabledHint.value && tooltipText.value) {
const now = Date.now(); const now = Date.now();
if (!this.lastPopupCountUpdate || now - this.lastPopupCountUpdate >= POPUP_UPDATE_DELAY) { if (!lastPopupCountUpdate.value || now - lastPopupCountUpdate.value >= POPUP_UPDATE_DELAY) {
localStorage.setItem(NODE_TEST_STEP_POPUP_COUNT_KEY, `${count + 1}`); localStorage.setItem(NODE_TEST_STEP_POPUP_COUNT_KEY, `${count + 1}`);
this.lastPopupCountUpdate = now; lastPopupCountUpdate.value = now;
} }
} }
}, }
async onClick() { async function onClick() {
// Show chat if it's a chat node or a child of a chat node with no input data if (isChatNode.value || (isChatChild.value && ndvStore.isNDVDataEmpty('input'))) {
if (this.isChatNode || (this.isChatChild && this.ndvStore.isNDVDataEmpty('input'))) { ndvStore.setActiveNodeName(null);
this.ndvStore.setActiveNodeName(null);
nodeViewEventBus.emit('openChat'); nodeViewEventBus.emit('openChat');
} else if (this.isListeningForEvents) { } else if (isListeningForEvents.value) {
await this.stopWaitingForWebhook(); await stopWaitingForWebhook();
} else if (this.isListeningForWorkflowEvents) { } else if (isListeningForWorkflowEvents.value) {
await this.stopCurrentExecution(); await stopCurrentExecution();
this.$emit('stopExecution'); emit('stopExecution');
} else { } else {
let shouldUnpinAndExecute = false; let shouldUnpinAndExecute = false;
if (this.pinnedData.hasData.value) { if (pinnedData.hasData.value) {
const confirmResult = await this.confirm( const confirmResult = await message.confirm(
this.$locale.baseText('ndv.pinData.unpinAndExecute.description'), i18n.baseText('ndv.pinData.unpinAndExecute.description'),
this.$locale.baseText('ndv.pinData.unpinAndExecute.title'), i18n.baseText('ndv.pinData.unpinAndExecute.title'),
{ {
confirmButtonText: this.$locale.baseText('ndv.pinData.unpinAndExecute.confirm'), confirmButtonText: i18n.baseText('ndv.pinData.unpinAndExecute.confirm'),
cancelButtonText: this.$locale.baseText('ndv.pinData.unpinAndExecute.cancel'), cancelButtonText: i18n.baseText('ndv.pinData.unpinAndExecute.cancel'),
}, },
); );
shouldUnpinAndExecute = confirmResult === MODAL_CONFIRM; shouldUnpinAndExecute = confirmResult === MODAL_CONFIRM;
if (shouldUnpinAndExecute && this.node) { if (shouldUnpinAndExecute && node.value) {
this.pinnedData.unsetData('unpin-and-execute-modal'); pinnedData.unsetData('unpin-and-execute-modal');
} }
} }
if (!this.pinnedData.hasData.value || shouldUnpinAndExecute) { if (!pinnedData.hasData.value || shouldUnpinAndExecute) {
const telemetryPayload = { const telemetryPayload = {
node_type: this.nodeType ? this.nodeType.name : null, node_type: nodeType.value ? nodeType.value.name : null,
workflow_id: this.workflowsStore.workflowId, workflow_id: workflowsStore.workflowId,
source: this.telemetrySource, source: props.telemetrySource,
push_ref: this.ndvStore.pushRef, push_ref: ndvStore.pushRef,
}; };
this.$telemetry.track('User clicked execute node button', telemetryPayload);
await this.externalHooks.run('nodeExecuteButton.onClick', telemetryPayload);
await this.runWorkflow({ telemetry.track('User clicked execute node button', telemetryPayload);
destinationNode: this.nodeName, await externalHooks.run('nodeExecuteButton.onClick', telemetryPayload);
await runWorkflow({
destinationNode: props.nodeName,
source: 'RunData.ExecuteNodeButton', source: 'RunData.ExecuteNodeButton',
}); });
this.$emit('execute'); emit('execute');
} }
} }
}, }
},
});
</script> </script>
<template> <template>
@ -300,7 +283,7 @@ export default defineComponent({
<div> <div>
<n8n-button <n8n-button
v-bind="$attrs" v-bind="$attrs"
:loading :loading="isLoading"
:disabled="disabled || !!disabledHint" :disabled="disabled || !!disabledHint"
:label="buttonLabel" :label="buttonLabel"
:type="type" :type="type"
@ -308,9 +291,7 @@ export default defineComponent({
:icon="!isListeningForEvents && !hideIcon ? 'flask' : undefined" :icon="!isListeningForEvents && !hideIcon ? 'flask' : undefined"
:transparent-background="transparent" :transparent-background="transparent"
:title=" :title="
!isTriggerNode && !tooltipText !isTriggerNode && !tooltipText ? i18n.baseText('ndv.execute.testNode.description') : ''
? $locale.baseText('ndv.execute.testNode.description')
: ''
" "
@mouseover="onMouseOver" @mouseover="onMouseOver"
@click="onClick" @click="onClick"