n8n/packages/editor-ui/src/components/mixins/workflowRun.ts
Ahsan Virani 421dd72224
Introduce telemetry (#2099)
* introduce analytics

* add user survey backend

* add user survey backend

* set answers on survey submit

Co-authored-by: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com>

* change name to personalization

* lint

Co-authored-by: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com>

* N8n 2495 add personalization modal (#2280)

* update modals

* add onboarding modal

* implement questions

* introduce analytics

* simplify impl

* implement survey handling

* add personalized cateogry

* update modal behavior

* add thank you view

* handle empty cases

* rename modal

* standarize modal names

* update image, add tags to headings

* remove unused file

* remove unused interfaces

* clean up footer spacing

* introduce analytics

* refactor to fix bug

* update endpoint

* set min height

* update stories

* update naming from questions to survey

* remove spacing after core categories

* fix bug in logic

* sort nodes

* rename types

* merge with be

* rename userSurvey

* clean up rest api

* use constants for keys

* use survey keys

* clean up types

* move personalization to its own file

Co-authored-by: ahsan-virani <ahsan.virani@gmail.com>

* Survey new options (#2300)

* split up options

* fix quotes

* remove unused import

* add user created workflow event (#2301)

* simplify env vars

* fix versionCli on FE

* update personalization env

* fix event User opened Credentials panel

* fix select modal spacing

* fix nodes panel event

* fix workflow id in workflow execute event

* improve telemetry error logging

* fix config and stop process events

* add flush call on n8n stop

* ready for release

* improve telemetry process exit

* fix merge

* improve n8n stop events

Co-authored-by: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com>
Co-authored-by: Mutasem <mutdmour@gmail.com>
Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
2021-10-19 05:57:49 +02:00

208 lines
6.4 KiB
TypeScript

import {
IExecutionPushResponse,
IExecutionResponse,
IStartRunData,
} from '@/Interface';
import {
IRunData,
IRunExecutionData,
NodeHelpers,
} from 'n8n-workflow';
import { externalHooks } from '@/components/mixins/externalHooks';
import { restApi } from '@/components/mixins/restApi';
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
import { showMessage } from '@/components/mixins/showMessage';
import mixins from 'vue-typed-mixins';
import { titleChange } from './titleChange';
export const workflowRun = mixins(
externalHooks,
restApi,
workflowHelpers,
showMessage,
titleChange,
).extend({
methods: {
// Starts to executes a workflow on server.
async runWorkflowApi (runData: IStartRunData): Promise<IExecutionPushResponse> {
if (this.$store.getters.pushConnectionActive === false) {
// Do not start if the connection to server is not active
// because then it can not receive the data as it executes.
throw new Error('No active connection to server. It is maybe down.');
}
this.$store.commit('addActiveAction', 'workflowRunning');
let response: IExecutionPushResponse;
try {
response = await this.restApi().runWorkflow(runData);
} catch (error) {
this.$store.commit('removeActiveAction', 'workflowRunning');
throw error;
}
if (response.executionId !== undefined) {
this.$store.commit('setActiveExecutionId', response.executionId);
}
if (response.waitingForWebhook === true) {
this.$store.commit('setExecutionWaitingForWebhook', true);
}
return response;
},
async runWorkflow (nodeName?: string, source?: string): Promise<IExecutionPushResponse | undefined> {
const workflow = this.getWorkflow();
if(nodeName) {
this.$telemetry.track('User clicked execute node button', { node_type: nodeName, workflow_id: this.$store.getters.workflowId });
} else {
this.$telemetry.track('User clicked execute workflow button', { workflow_id: this.$store.getters.workflowId });
}
if (this.$store.getters.isActionActive('workflowRunning') === true) {
return;
}
this.$titleSet(workflow.name as string, 'EXECUTING');
this.clearAllStickyNotifications();
try {
// Check first if the workflow has any issues before execute it
const issuesExist = this.$store.getters.nodesIssuesExist;
if (issuesExist === true) {
// If issues exist get all of the issues of all nodes
const workflowIssues = this.checkReadyForExecution(workflow, nodeName);
if (workflowIssues !== null) {
const errorMessages = [];
let nodeIssues: string[];
for (const nodeName of Object.keys(workflowIssues)) {
nodeIssues = NodeHelpers.nodeIssuesToString(workflowIssues[nodeName]);
for (const nodeIssue of nodeIssues) {
errorMessages.push(`${nodeName}: ${nodeIssue}`);
}
}
this.$showMessage({
title: 'Workflow can not be executed',
message: 'The workflow has issues. Please fix them first:<br />&nbsp;&nbsp;- ' + errorMessages.join('<br />&nbsp;&nbsp;- '),
type: 'error',
duration: 0,
});
this.$titleSet(workflow.name as string, 'ERROR');
this.$externalHooks().run('workflowRun.runError', { errorMessages, nodeName });
return;
}
}
// Get the direct parents of the node
let directParentNodes: string[] = [];
if (nodeName !== undefined) {
directParentNodes = workflow.getParentNodes(nodeName, 'main', 1);
}
const runData = this.$store.getters.getWorkflowRunData;
let newRunData: IRunData | undefined;
const startNodes: string[] = [];
if (runData !== null && Object.keys(runData).length !== 0) {
newRunData = {};
// Go over the direct parents of the node
for (const directParentNode of directParentNodes) {
// Go over the parents of that node so that we can get a start
// node for each of the branches
const parentNodes = workflow.getParentNodes(directParentNode, 'main');
// Add also the direct parent to be checked
parentNodes.push(directParentNode);
for (const parentNode of parentNodes) {
if (runData[parentNode] === undefined || runData[parentNode].length === 0) {
// When we hit a node which has no data we stop and set it
// as a start node the execution from and then go on with other
// direct input nodes
startNodes.push(parentNode);
break;
}
newRunData[parentNode] = runData[parentNode].slice(0, 1);
}
}
if (Object.keys(newRunData).length === 0) {
// If there is no data for any of the parent nodes make sure
// that run data is empty that it runs regularly
newRunData = undefined;
}
}
if (startNodes.length === 0 && nodeName !== undefined) {
startNodes.push(nodeName);
}
const isNewWorkflow = this.$store.getters.isNewWorkflow;
const hasWebhookNode = this.$store.getters.currentWorkflowHasWebhookNode;
if (isNewWorkflow && hasWebhookNode) {
await this.saveCurrentWorkflow();
}
const workflowData = await this.getWorkflowDataToSave();
const startRunData: IStartRunData = {
workflowData,
runData: newRunData,
startNodes,
};
if (nodeName) {
startRunData.destinationNode = nodeName;
}
// Init the execution data to represent the start of the execution
// that data which gets reused is already set and data of newly executed
// nodes can be added as it gets pushed in
const executionData: IExecutionResponse = {
id: '__IN_PROGRESS__',
finished: false,
mode: 'manual',
startedAt: new Date(),
stoppedAt: undefined,
workflowId: workflow.id,
data: {
resultData: {
runData: newRunData || {},
startNodes,
workflowData,
},
} as IRunExecutionData,
workflowData: {
id: this.$store.getters.workflowId,
name: workflowData.name!,
active: workflowData.active!,
createdAt: 0,
updatedAt: 0,
...workflowData,
},
};
this.$store.commit('setWorkflowExecutionData', executionData);
const runWorkflowApiResponse = await this.runWorkflowApi(startRunData);
this.$externalHooks().run('workflowRun.runWorkflow', { nodeName, source });
return runWorkflowApiResponse;
} catch (error) {
this.$titleSet(workflow.name as string, 'ERROR');
this.$showError(error, 'Problem running workflow', 'There was a problem running the workflow:');
return undefined;
}
},
},
});