fix: Stop listening button not working in NDV (#9023)

This commit is contained in:
Milorad FIlipović 2024-04-03 12:16:16 +02:00 committed by कारतोफ्फेलस्क्रिप्ट™
parent e2bde6b751
commit 02219dde2f
5 changed files with 85 additions and 67 deletions

View file

@ -658,4 +658,18 @@ describe('NDV', () => {
cy.realPress('Escape'); cy.realPress('Escape');
}); });
}); });
it('Stop listening for trigger event from NDV', () => {
workflowPage.actions.addInitialNodeToCanvas('Local File Trigger', {
keepNdvOpen: true,
action: 'On Changes To A Specific File',
isTrigger: true,
});
ndv.getters.triggerPanelExecuteButton().should('exist');
ndv.getters.triggerPanelExecuteButton().click();
ndv.getters.triggerPanelExecuteButton().should('contain', 'Stop Listening');
ndv.getters.triggerPanelExecuteButton().click();
ndv.getters.triggerPanelExecuteButton().should('contain', 'Test step');
workflowPage.getters.successToast().should('exist');
});
}); });

View file

@ -145,19 +145,20 @@ export class WorkflowPage extends BasePage {
}, },
addInitialNodeToCanvas: ( addInitialNodeToCanvas: (
nodeDisplayName: string, nodeDisplayName: string,
opts?: { keepNdvOpen?: boolean; action?: string }, opts?: { keepNdvOpen?: boolean; action?: string, isTrigger?: boolean},
) => { ) => {
this.getters.canvasPlusButton().click(); this.getters.canvasPlusButton().click();
this.getters.nodeCreatorSearchBar().type(nodeDisplayName); this.getters.nodeCreatorSearchBar().type(nodeDisplayName);
this.getters.nodeCreatorSearchBar().type('{enter}'); this.getters.nodeCreatorSearchBar().type('{enter}');
if (opts?.action) { if (opts?.action) {
const itemId = opts.isTrigger ? 'Triggers' : 'Actions';
// Expand actions category if it's collapsed // Expand actions category if it's collapsed
nodeCreator.getters nodeCreator.getters
.getCategoryItem('Actions') .getCategoryItem(itemId)
.parent() .parent()
.then(($el) => { .then(($el) => {
if ($el.attr('data-category-collapsed') === 'true') { if ($el.attr('data-category-collapsed') === 'true') {
nodeCreator.getters.getCategoryItem('Actions').click(); nodeCreator.getters.getCategoryItem(itemId).click();
} }
}); });
nodeCreator.getters.getCreatorItem(opts.action).click(); nodeCreator.getters.getCreatorItem(opts.action).click();

View file

@ -77,18 +77,20 @@ export default defineComponent({
type: Boolean, type: Boolean,
}, },
}, },
emits: ['stopExecution', 'execute'],
setup(props) { setup(props) {
const router = useRouter(); const router = useRouter();
const workflowsStore = useWorkflowsStore(); const workflowsStore = useWorkflowsStore();
const node = workflowsStore.getNodeByName(props.nodeName); const node = workflowsStore.getNodeByName(props.nodeName);
const pinnedData = usePinnedData(node); const pinnedData = usePinnedData(node);
const externalHooks = useExternalHooks(); const externalHooks = useExternalHooks();
const { runWorkflow } = useRunWorkflow({ router }); const { runWorkflow, stopCurrentExecution } = useRunWorkflow({ router });
return { return {
externalHooks, externalHooks,
pinnedData, pinnedData,
runWorkflow, runWorkflow,
stopCurrentExecution,
...useToast(), ...useToast(),
...useMessage(), ...useMessage(),
}; };
@ -236,6 +238,7 @@ export default defineComponent({
} else if (this.isListeningForEvents) { } else if (this.isListeningForEvents) {
await this.stopWaitingForWebhook(); await this.stopWaitingForWebhook();
} else if (this.isListeningForWorkflowEvents) { } else if (this.isListeningForWorkflowEvents) {
await this.stopCurrentExecution();
this.$emit('stopExecution'); this.$emit('stopExecution');
} else { } else {
let shouldUnpinAndExecute = false; let shouldUnpinAndExecute = false;

View file

@ -1,6 +1,7 @@
import type { import type {
IExecutionPushResponse, IExecutionPushResponse,
IExecutionResponse, IExecutionResponse,
IPushDataExecutionFinished,
IStartRunData, IStartRunData,
IWorkflowDb, IWorkflowDb,
} from '@/Interface'; } from '@/Interface';
@ -13,6 +14,7 @@ import type {
IWorkflowBase, IWorkflowBase,
Workflow, Workflow,
StartNodeData, StartNodeData,
IRun,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
NodeHelpers, NodeHelpers,
@ -449,9 +451,67 @@ export function useRunWorkflow(options: { router: ReturnType<typeof useRouter> }
return { runData: newRunData, startNodeNames }; return { runData: newRunData, startNodeNames };
} }
async function stopCurrentExecution() {
const executionId = workflowsStore.activeExecutionId;
if (executionId === null) {
return;
}
try {
await workflowsStore.stopCurrentExecution(executionId);
} catch (error) {
// Execution stop might fail when the execution has already finished. Let's treat this here.
const execution = await this.workflowsStore.getExecution(executionId);
if (execution === undefined) {
// execution finished but was not saved (e.g. due to low connectivity)
workflowsStore.finishActiveExecution({
executionId,
data: { finished: true, stoppedAt: new Date() },
});
workflowsStore.executingNode.length = 0;
uiStore.removeActiveAction('workflowRunning');
titleSet(workflowsStore.workflowName, 'IDLE');
toast.showMessage({
title: i18n.baseText('nodeView.showMessage.stopExecutionCatch.unsaved.title'),
message: i18n.baseText('nodeView.showMessage.stopExecutionCatch.unsaved.message'),
type: 'success',
});
} else if (execution?.finished) {
// execution finished before it could be stopped
const executedData = {
data: execution.data,
finished: execution.finished,
mode: execution.mode,
startedAt: execution.startedAt,
stoppedAt: execution.stoppedAt,
} as IRun;
const pushData = {
data: executedData,
executionId,
retryOf: execution.retryOf,
} as IPushDataExecutionFinished;
workflowsStore.finishActiveExecution(pushData);
titleSet(execution.workflowData.name, 'IDLE');
workflowsStore.executingNode.length = 0;
workflowsStore.setWorkflowExecutionData(executedData as IExecutionResponse);
uiStore.removeActiveAction('workflowRunning');
toast.showMessage({
title: i18n.baseText('nodeView.showMessage.stopExecutionCatch.title'),
message: i18n.baseText('nodeView.showMessage.stopExecutionCatch.message'),
type: 'success',
});
} else {
toast.showError(error, i18n.baseText('nodeView.showError.stopExecution.title'));
}
}
}
return { return {
consolidateRunDataAndStartNodes, consolidateRunDataAndStartNodes,
runWorkflow, runWorkflow,
runWorkflowApi, runWorkflowApi,
stopCurrentExecution,
}; };
} }

View file

@ -279,7 +279,6 @@ import type {
INodeTypeDescription, INodeTypeDescription,
INodeTypeNameVersion, INodeTypeNameVersion,
IPinData, IPinData,
IRun,
ITaskData, ITaskData,
ITelemetryTrackProperties, ITelemetryTrackProperties,
IWorkflowBase, IWorkflowBase,
@ -303,7 +302,6 @@ import type {
IUpdateInformation, IUpdateInformation,
IWorkflowDataUpdate, IWorkflowDataUpdate,
XYPosition, XYPosition,
IPushDataExecutionFinished,
ITag, ITag,
INewWorkflowData, INewWorkflowData,
IWorkflowTemplate, IWorkflowTemplate,
@ -492,7 +490,7 @@ export default defineComponent({
const { callDebounced } = useDebounce(); const { callDebounced } = useDebounce();
const canvasPanning = useCanvasPanning(nodeViewRootRef, { onMouseMoveEnd }); const canvasPanning = useCanvasPanning(nodeViewRootRef, { onMouseMoveEnd });
const workflowHelpers = useWorkflowHelpers({ router }); const workflowHelpers = useWorkflowHelpers({ router });
const { runWorkflow } = useRunWorkflow({ router }); const { runWorkflow, stopCurrentExecution } = useRunWorkflow({ router });
return { return {
locale, locale,
@ -509,6 +507,7 @@ export default defineComponent({
onMouseMoveEnd, onMouseMoveEnd,
workflowHelpers, workflowHelpers,
runWorkflow, runWorkflow,
stopCurrentExecution,
callDebounced, callDebounced,
...useCanvasMouseSelect(), ...useCanvasMouseSelect(),
...useGlobalLinkActions(), ...useGlobalLinkActions(),
@ -1930,67 +1929,8 @@ export default defineComponent({
}); });
}, },
async stopExecution() { async stopExecution() {
const executionId = this.workflowsStore.activeExecutionId; await this.stopCurrentExecution();
if (executionId === null) {
return;
}
try {
this.stopExecutionInProgress = true;
await this.workflowsStore.stopCurrentExecution(executionId);
} catch (error) {
// Execution stop might fail when the execution has already finished. Let's treat this here.
const execution = await this.workflowsStore.getExecution(executionId);
if (execution === undefined) {
// execution finished but was not saved (e.g. due to low connectivity)
this.workflowsStore.finishActiveExecution({
executionId,
data: { finished: true, stoppedAt: new Date() },
});
this.workflowsStore.executingNode.length = 0;
this.uiStore.removeActiveAction('workflowRunning');
this.titleSet(this.workflowsStore.workflowName, 'IDLE');
this.showMessage({
title: this.$locale.baseText('nodeView.showMessage.stopExecutionCatch.unsaved.title'),
message: this.$locale.baseText(
'nodeView.showMessage.stopExecutionCatch.unsaved.message',
),
type: 'success',
});
} else if (execution?.finished) {
// execution finished before it could be stopped
const executedData = {
data: execution.data,
finished: execution.finished,
mode: execution.mode,
startedAt: execution.startedAt,
stoppedAt: execution.stoppedAt,
} as IRun;
const pushData = {
data: executedData,
executionId,
retryOf: execution.retryOf,
} as IPushDataExecutionFinished;
this.workflowsStore.finishActiveExecution(pushData);
this.titleSet(execution.workflowData.name, 'IDLE');
this.workflowsStore.executingNode.length = 0;
this.workflowsStore.setWorkflowExecutionData(executedData as IExecutionResponse);
this.uiStore.removeActiveAction('workflowRunning');
this.showMessage({
title: this.$locale.baseText('nodeView.showMessage.stopExecutionCatch.title'),
message: this.$locale.baseText('nodeView.showMessage.stopExecutionCatch.message'),
type: 'success',
});
} else {
this.showError(error, this.$locale.baseText('nodeView.showError.stopExecution.title'));
}
}
this.stopExecutionInProgress = false; this.stopExecutionInProgress = false;
void this.workflowHelpers.getWorkflowDataToSave().then((workflowData) => { void this.workflowHelpers.getWorkflowDataToSave().then((workflowData) => {
const trackProps = { const trackProps = {
workflow_id: this.workflowsStore.workflowId, workflow_id: this.workflowsStore.workflowId,