n8n-editor-ui
Version:
Workflow Editor UI for n8n
506 lines (505 loc) • 23.7 kB
JavaScript
import { It as ref } from "./vue.runtime.esm-bundler-tP5dCd7J.js";
import { _t as useI18n } from "./_MapCache-ficiegRb.js";
import { Ct as clearPopupWindowState, Fi as parse, Gn as useNodeHelpers, Hs as sendGetWorkerStatus, Kn as injectWorkflowState, N as useWorkflowsStore, Nt as useNodeTypesStore, O as useTelemetry, Ot as getExecutionErrorMessage, Rn as useWorkflowHelpers, Ti as getTriggerNodeServiceName, Ur as isValidNodeConnectionType, Yn as useDocumentTitle, b as useExternalHooks, en as SampleTemplates, gn as useCredentialsStore, gs as useSettingsStore, ia as codeNodeEditorEventBus, kt as getExecutionErrorToastConfiguration, n as useWorkflowSaving, na as globalLinkActionsEventBus, oa as useUIStore, on as isPrebuiltAgentTemplateId, sn as isTutorialTemplateId, t as useBuilderStore, v as useToast } from "./builder.store-sBTWwxRU.js";
import { Ss as WORKFLOW_SETTINGS_MODAL_KEY, Ta as isCommunityPackageName, Vi as createRunExecutionData, eo as EVALUATION_TRIGGER_NODE_TYPE, fo as TRIMMED_TASK_DATA_CONNECTIONS_KEY, qi as generateNodesGraph } from "./constants-BbpucWL4.js";
import { k as defineStore, r as useRootStore } from "./_baseOrderBy-B_6CV8x6.js";
import { t as useAssistantStore } from "./assistant.store-bS9AHOoo.js";
import { i as usePushConnectionStore, t as useRunWorkflow } from "./useRunWorkflow-BQJbxiOd.js";
import { t as useCanvasOperations } from "./useCanvasOperations-CAHezq7d.js";
import { t as useBannersStore } from "./banners.store-C9sgKjcT.js";
import { t as useSchemaPreviewStore } from "./schemaPreview.store-BFprwUEe.js";
import { t as useAITemplatesStarterCollectionStore } from "./aiTemplatesStarterCollection.store-DkHa_nSs.js";
import { t as useReadyToRunStore } from "./readyToRun.store-CsvyRJzn.js";
async function builderCreditsUpdated(event) {
useBuilderStore().updateBuilderCredits(event.data.creditsQuota, event.data.creditsClaimed);
}
async function executionFinished({ data }, options) {
const workflowsStore = useWorkflowsStore();
const uiStore = useUIStore();
const aiTemplatesStarterCollectionStore = useAITemplatesStarterCollectionStore();
const readyToRunStore = useReadyToRunStore();
options.workflowState.executingNode.lastAddedExecutingNode = null;
if (typeof workflowsStore.activeExecutionId === "undefined") return;
const telemetry = useTelemetry();
clearPopupWindowState();
const templateId = workflowsStore.getWorkflowById(data.workflowId)?.meta?.templateId;
if (templateId) {
if (templateId === SampleTemplates.EasyAiTemplate) telemetry.track("User executed test AI workflow", { status: data.status });
else if (templateId.startsWith("035_template_onboarding")) aiTemplatesStarterCollectionStore.trackUserExecutedWorkflow(templateId.split("-").pop() ?? "", data.status);
else if (templateId === "ready-to-run-ai-workflow" || templateId === "ready-to-run-ai-workflow-v5" || templateId === "ready-to-run-ai-workflow-v6") if (data.status === "success") readyToRunStore.trackExecuteAiWorkflowSuccess();
else readyToRunStore.trackExecuteAiWorkflow(data.status);
else if (isPrebuiltAgentTemplateId(templateId)) telemetry.track("User executed pre-built Agent", {
template: templateId,
status: data.status
});
else if (isTutorialTemplateId(templateId)) telemetry.track("User executed tutorial template", {
template: templateId,
status: data.status
});
}
uiStore.setProcessingExecutionResults(true);
let successToastAlreadyShown = false;
if (data.status === "success") {
handleExecutionFinishedWithSuccessOrOther(options.workflowState, data.status, successToastAlreadyShown);
successToastAlreadyShown = true;
}
const execution = await fetchExecutionData(data.executionId);
if (!execution) {
options.workflowState.setActiveExecutionId(void 0);
uiStore.setProcessingExecutionResults(false);
return;
}
const runExecutionData = getRunExecutionData(execution);
uiStore.setProcessingExecutionResults(false);
if (execution.data?.waitTill !== void 0) handleExecutionFinishedWithWaitTill(options);
else if (execution.status === "error" || execution.status === "canceled") handleExecutionFinishedWithErrorOrCanceled(execution, runExecutionData);
else handleExecutionFinishedWithSuccessOrOther(options.workflowState, execution.status, successToastAlreadyShown);
setRunExecutionData(execution, runExecutionData, options.workflowState);
continueEvaluationLoop(execution, options);
}
function continueEvaluationLoop(execution, opts) {
if (execution.status !== "success" || execution.data?.startData?.destinationNode !== void 0) return;
const evaluationTrigger = execution.workflowData.nodes.find((node) => node.type === EVALUATION_TRIGGER_NODE_TYPE);
const triggerRunData = evaluationTrigger ? execution?.data?.resultData?.runData[evaluationTrigger.name] : void 0;
if (!evaluationTrigger || triggerRunData === void 0) return;
const mainData = triggerRunData[0]?.data?.main[0];
const rowsLeft = mainData ? mainData[0]?.json?._rowsLeft : 0;
if (rowsLeft && rowsLeft > 0) {
const { runWorkflow } = useRunWorkflow(opts);
runWorkflow({
triggerNode: evaluationTrigger.name,
nodeData: triggerRunData[0],
rerunTriggerNode: true
});
}
}
async function fetchExecutionData(executionId) {
const workflowsStore = useWorkflowsStore();
try {
const executionResponse = await workflowsStore.fetchExecutionDataById(executionId);
if (!executionResponse?.data) return;
return {
id: executionId,
workflowId: executionResponse.workflowId,
workflowData: workflowsStore.workflow,
data: parse(executionResponse.data),
status: executionResponse.status,
startedAt: workflowsStore.workflowExecutionData?.startedAt,
stoppedAt: /* @__PURE__ */ new Date()
};
} catch {
return;
}
}
function getRunExecutionData(execution) {
return createRunExecutionData({
...execution.data,
startData: execution.data?.startData,
resultData: execution.data?.resultData ?? { runData: {} },
executionData: execution.data?.executionData
});
}
function getRunDataExecutedErrorMessage(execution) {
const i18n = useI18n();
if (execution.status === "crashed") return i18n.baseText("pushConnection.executionFailed.message");
else if (execution.status === "canceled") {
const workflowsStore = useWorkflowsStore();
return i18n.baseText("executionsList.showMessage.stopExecution.message", { interpolate: { activeExecutionId: workflowsStore.activeExecutionId ?? "" } });
}
return getExecutionErrorMessage({
error: execution.data?.resultData.error,
lastNodeExecuted: execution.data?.resultData.lastNodeExecuted
});
}
function handleExecutionFinishedWithWaitTill(options) {
const workflowsStore = useWorkflowsStore();
const settingsStore = useSettingsStore();
const workflowSaving = useWorkflowSaving(options);
const workflowObject = workflowsStore.workflowObject;
if (!(workflowsStore.workflowSettings.saveManualExecutions ?? settingsStore.saveManualExecutions)) {
const uiStore = useUIStore();
globalLinkActionsEventBus.emit("registerGlobalLinkAction", {
key: "open-settings",
action: async () => {
if (!workflowsStore.isWorkflowSaved[workflowsStore.workflowId]) await workflowSaving.saveAsNewWorkflow();
uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
}
});
}
useDocumentTitle().setDocumentTitle(workflowObject.name, "IDLE");
}
function handleExecutionFinishedWithErrorOrCanceled(execution, runExecutionData) {
const toast = useToast();
const i18n = useI18n();
const telemetry = useTelemetry();
const workflowsStore = useWorkflowsStore();
const documentTitle = useDocumentTitle();
const workflowHelpers = useWorkflowHelpers();
const workflowObject = workflowsStore.workflowObject;
documentTitle.setDocumentTitle(workflowObject.name, "ERROR");
if (runExecutionData.resultData.error?.name === "ExpressionError" && runExecutionData.resultData.error.functionality === "pairedItem") {
const error = runExecutionData.resultData.error;
workflowHelpers.getWorkflowDataToSave().then((workflowData) => {
const eventData = {
caused_by_credential: false,
error_message: error.description,
error_title: error.message,
error_type: error.context.type,
node_graph_string: JSON.stringify(generateNodesGraph(workflowData, workflowHelpers.getNodeTypes()).nodeGraph),
workflow_id: workflowsStore.workflowId
};
if (error.context.nodeCause && ["paired_item_no_info", "paired_item_invalid_info"].includes(error.context.type)) {
const node = workflowObject.getNode(error.context.nodeCause);
if (node) {
eventData.is_pinned = !!workflowObject.getPinDataOfNode(node.name);
eventData.mode = node.parameters.mode;
eventData.node_type = node.type;
eventData.operation = node.parameters.operation;
eventData.resource = node.parameters.resource;
}
}
telemetry.track("Instance FE emitted paired item error", eventData);
});
}
if (execution.status === "canceled") toast.showMessage({
title: i18n.baseText("nodeView.showMessage.stopExecutionTry.title"),
type: "success"
});
else if (execution.data?.resultData.error) {
const { message, title } = getExecutionErrorToastConfiguration({
error: execution.data.resultData.error,
lastNodeExecuted: execution.data?.resultData.lastNodeExecuted
});
toast.showMessage({
title,
message,
type: "error",
duration: 0
});
useBuilderStore().incrementManualExecutionStats("error");
}
}
function handleExecutionFinishedSuccessfully(workflowName, message, workflowState) {
const toast = useToast();
useDocumentTitle().setDocumentTitle(workflowName, "IDLE");
workflowState.setActiveExecutionId(void 0);
toast.showMessage({
title: message,
type: "success"
});
}
function handleExecutionFinishedWithSuccessOrOther(workflowState, executionStatus, successToastAlreadyShown) {
const workflowsStore = useWorkflowsStore();
const toast = useToast();
const i18n = useI18n();
const nodeTypesStore = useNodeTypesStore();
const workflowName = workflowsStore.workflowObject.name ?? "";
useDocumentTitle().setDocumentTitle(workflowName, "IDLE");
const workflowExecution = workflowsStore.getWorkflowExecution;
if (workflowExecution?.executedNode) {
const node = workflowsStore.getNodeByName(workflowExecution.executedNode);
const nodeType = node && nodeTypesStore.getNodeType(node.type, node.typeVersion);
const nodeOutput = workflowExecution.data?.resultData?.runData?.[workflowExecution.executedNode];
if (nodeType?.polling && !nodeOutput) toast.showMessage({
title: i18n.baseText("pushConnection.pollingNode.dataNotFound", { interpolate: { service: getTriggerNodeServiceName(nodeType) } }),
message: i18n.baseText("pushConnection.pollingNode.dataNotFound.message", { interpolate: { service: getTriggerNodeServiceName(nodeType) } }),
type: "success"
});
else if (!successToastAlreadyShown) handleExecutionFinishedSuccessfully(workflowName, i18n.baseText("pushConnection.nodeExecutedSuccessfully"), workflowState);
} else if (!successToastAlreadyShown) handleExecutionFinishedSuccessfully(workflowName, i18n.baseText("pushConnection.workflowExecutedSuccessfully"), workflowState);
if (executionStatus === "success" && !successToastAlreadyShown) useBuilderStore().incrementManualExecutionStats("success");
}
function setRunExecutionData(execution, runExecutionData, workflowState) {
const workflowsStore = useWorkflowsStore();
const nodeHelpers = useNodeHelpers({ workflowState });
const runDataExecutedErrorMessage = getRunDataExecutedErrorMessage(execution);
const workflowExecution = workflowsStore.getWorkflowExecution;
workflowState.executingNode.executingNode.length = 0;
if (workflowExecution === null) return;
workflowState.setWorkflowExecutionData({
...workflowExecution,
status: execution.status,
id: execution.id,
stoppedAt: execution.stoppedAt
});
workflowsStore.setWorkflowExecutionRunData(runExecutionData);
workflowState.setActiveExecutionId(void 0);
nodeHelpers.updateNodesExecutionIssues();
const lastNodeExecuted = runExecutionData.resultData.lastNodeExecuted;
let itemsCount = 0;
if (lastNodeExecuted && runExecutionData.resultData.runData[lastNodeExecuted] && !runDataExecutedErrorMessage) itemsCount = runExecutionData.resultData.runData[lastNodeExecuted][0].data?.main[0]?.length ?? 0;
workflowState.setActiveExecutionId(void 0);
useExternalHooks().run("pushConnection.executionFinished", {
itemsCount,
nodeName: runExecutionData.resultData.lastNodeExecuted,
errorMessage: runDataExecutedErrorMessage,
runDataExecutedStartData: runExecutionData.startData,
resultDataError: runExecutionData.resultData.error
});
const lineNumber = runExecutionData.resultData?.error?.lineNumber;
codeNodeEditorEventBus.emit("highlightLine", lineNumber ?? "last");
}
async function executionRecovered({ data }, options) {
const workflowsStore = useWorkflowsStore();
const uiStore = useUIStore();
if (typeof workflowsStore.activeExecutionId === "undefined") return;
uiStore.setProcessingExecutionResults(true);
const execution = await fetchExecutionData(data.executionId);
if (!execution) {
uiStore.setProcessingExecutionResults(false);
return;
}
const runExecutionData = getRunExecutionData(execution);
uiStore.setProcessingExecutionResults(false);
if (execution.data?.waitTill !== void 0) handleExecutionFinishedWithWaitTill(options);
else if (execution.status === "error" || execution.status === "canceled") handleExecutionFinishedWithErrorOrCanceled(execution, runExecutionData);
else handleExecutionFinishedWithSuccessOrOther(options.workflowState, execution.status, false);
setRunExecutionData(execution, runExecutionData, options.workflowState);
}
async function executionStarted({ data }, options) {
const workflowsStore = useWorkflowsStore();
if (typeof workflowsStore.activeExecutionId === "undefined") return;
else if (workflowsStore.activeExecutionId === null) options.workflowState.setActiveExecutionId(data.executionId);
if (workflowsStore.workflowExecutionData?.data && data.flattedRunData) workflowsStore.workflowExecutionData.data.resultData.runData = parse(data.flattedRunData);
}
async function nodeDescriptionUpdated(_event) {
const nodeTypesStore = useNodeTypesStore();
const credentialsStore = useCredentialsStore();
await nodeTypesStore.getNodeTypes();
await credentialsStore.fetchCredentialTypes(true);
}
async function nodeExecuteAfter({ data: pushData }, { workflowState }) {
const workflowsStore = useWorkflowsStore();
const assistantStore = useAssistantStore();
const placeholderOutputData = { main: [] };
if (pushData.itemCountByConnectionType && typeof pushData.itemCountByConnectionType === "object") {
const fillObject = { json: { [TRIMMED_TASK_DATA_CONNECTIONS_KEY]: true } };
for (const [connectionType, outputs] of Object.entries(pushData.itemCountByConnectionType)) if (isValidNodeConnectionType(connectionType)) placeholderOutputData[connectionType] = outputs.map((count) => Array.from({ length: count }, () => fillObject));
}
const pushDataWithPlaceholderOutputData = {
...pushData,
data: {
...pushData.data,
data: placeholderOutputData
}
};
workflowsStore.updateNodeExecutionStatus(pushDataWithPlaceholderOutputData);
workflowState.executingNode.removeExecutingNode(pushData.nodeName);
assistantStore.onNodeExecution(pushData);
}
async function nodeExecuteAfterData({ data: pushData }) {
const workflowsStore = useWorkflowsStore();
const schemaPreviewStore = useSchemaPreviewStore();
workflowsStore.updateNodeExecutionRunData(pushData);
schemaPreviewStore.trackSchemaPreviewExecution(pushData);
}
async function nodeExecuteBefore({ data }, { workflowState }) {
const workflowsStore = useWorkflowsStore();
workflowState.executingNode.addExecutingNode(data.nodeName);
workflowsStore.addNodeExecutionStartedData(data);
}
async function reloadNodeType({ data }) {
const nodeTypesStore = useNodeTypesStore();
await nodeTypesStore.getNodeTypes();
const isCommunityNode = isCommunityPackageName(data.name);
await nodeTypesStore.getFullNodesProperties([data], !isCommunityNode);
}
async function removeNodeType({ data }) {
const nodeTypesStore = useNodeTypesStore();
const credentialsStore = useCredentialsStore();
const nodesToBeRemoved = [data];
await credentialsStore.fetchCredentialTypes(false).then(() => {
nodeTypesStore.removeNodeTypes(nodesToBeRemoved);
});
}
async function sendConsoleMessage({ data }) {
console.log(data.source, ...data.messages);
}
const WORKER_HISTORY_LENGTH = 100;
var STALE_SECONDS = 120 * 1e3;
const useOrchestrationStore = defineStore("orchestrationManager", {
state: () => ({
initialStatusReceived: false,
workers: {},
workersHistory: {},
workersLastUpdated: {},
statusInterval: null
}),
actions: {
updateWorkerStatus(data) {
this.workers[data.senderId] = data;
if (!this.workersHistory[data.senderId]) this.workersHistory[data.senderId] = [];
this.workersHistory[data.senderId].push({
data,
timestamp: Date.now()
});
if (this.workersHistory[data.senderId].length > 100) this.workersHistory[data.senderId].shift();
this.workersLastUpdated[data.senderId] = Date.now();
this.initialStatusReceived = true;
},
removeStaleWorkers() {
for (const id in this.workersLastUpdated) if (this.workersLastUpdated[id] + STALE_SECONDS < Date.now()) {
delete this.workers[id];
delete this.workersHistory[id];
delete this.workersLastUpdated[id];
}
},
startWorkerStatusPolling() {
const rootStore = useRootStore();
if (!this.statusInterval) this.statusInterval = setInterval(async () => {
await sendGetWorkerStatus(rootStore.restApiContext);
this.removeStaleWorkers();
}, 1e3);
},
stopWorkerStatusPolling() {
if (this.statusInterval) {
clearInterval(this.statusInterval);
this.statusInterval = null;
}
},
getWorkerLastUpdated(workerId) {
return this.workersLastUpdated[workerId] ?? 0;
},
getWorkerStatus(workerId) {
return this.workers[workerId];
},
getWorkerStatusHistory(workerId) {
return this.workersHistory[workerId] ?? [];
}
}
});
async function sendWorkerStatusMessage({ data }) {
useOrchestrationStore().updateWorkerStatus(data.status);
}
async function testWebhookDeleted({ data }, options) {
const workflowsStore = useWorkflowsStore();
if (data.workflowId === workflowsStore.workflowId) {
workflowsStore.executionWaitingForWebhook = false;
options.workflowState.setActiveExecutionId(void 0);
}
}
async function testWebhookReceived({ data }, options) {
const workflowsStore = useWorkflowsStore();
if (data.workflowId === workflowsStore.workflowId) {
workflowsStore.executionWaitingForWebhook = false;
options.workflowState.setActiveExecutionId(data.executionId ?? null);
}
}
async function workflowActivated({ data }) {
const { initializeWorkspace } = useCanvasOperations();
const workflowsStore = useWorkflowsStore();
const bannersStore = useBannersStore();
const uiStore = useUIStore();
const { workflowId, activeVersionId } = data;
const workflowIsBeingViewed = workflowsStore.workflowId === workflowId;
const activeVersionIsSet = workflowsStore.workflow.activeVersionId !== activeVersionId;
if (workflowIsBeingViewed && activeVersionIsSet) {
if (!uiStore.stateIsDirty) {
const updatedWorkflow = await workflowsStore.fetchWorkflow(workflowId);
if (!updatedWorkflow.checksum) throw new Error("Failed to fetch workflow");
await initializeWorkspace(updatedWorkflow);
}
}
if (workflowIsBeingViewed) bannersStore.removeBannerFromStack("WORKFLOW_AUTO_DEACTIVATED");
}
async function workflowAutoDeactivated({ data }) {
const workflowsStore = useWorkflowsStore();
const { initializeWorkspace } = useCanvasOperations();
const bannersStore = useBannersStore();
const uiStore = useUIStore();
workflowsStore.setWorkflowInactive(data.workflowId);
if (workflowsStore.workflowId === data.workflowId) {
if (!uiStore.stateIsDirty) {
const updatedWorkflow = await workflowsStore.fetchWorkflow(data.workflowId);
if (!updatedWorkflow.checksum) throw new Error("Failed to fetch workflow");
await initializeWorkspace(updatedWorkflow);
}
bannersStore.pushBannerToStack("WORKFLOW_AUTO_DEACTIVATED");
}
}
async function workflowDeactivated({ data }) {
const { initializeWorkspace } = useCanvasOperations();
const workflowsStore = useWorkflowsStore();
const uiStore = useUIStore();
if (workflowsStore.workflowId === data.workflowId) {
if (!uiStore.stateIsDirty) {
const updatedWorkflow = await workflowsStore.fetchWorkflow(data.workflowId);
if (!updatedWorkflow.checksum) throw new Error("Failed to fetch workflow");
await initializeWorkspace(updatedWorkflow);
}
}
}
async function workflowFailedToActivate({ data }, options) {
const workflowsStore = useWorkflowsStore();
if (workflowsStore.workflowId !== data.workflowId) return;
workflowsStore.setWorkflowInactive(data.workflowId);
options.workflowState.setActive(null);
const toast = useToast();
const i18n = useI18n();
toast.showError(new Error(data.errorMessage), i18n.baseText("workflowActivator.showError.title", { interpolate: { newStateName: "activated" } }) + ":");
}
function createEventQueue(processEvent) {
const queue = [];
let processing = false;
async function processNext() {
if (processing || queue.length === 0) return;
processing = true;
const currentEvent = queue.shift();
if (currentEvent !== void 0) try {
await processEvent(currentEvent);
} catch (error) {
console.error("Error processing event:", error);
}
processing = false;
await processNext();
}
function enqueue(event) {
queue.push(event);
processNext();
}
return { enqueue };
}
function usePushConnection({ router, workflowState }) {
const pushStore = usePushConnectionStore();
const options = {
router,
workflowState: workflowState ?? injectWorkflowState()
};
const { enqueue } = createEventQueue(processEvent);
const removeEventListener = ref(null);
function initialize() {
removeEventListener.value = pushStore.addEventListener((message) => {
enqueue(message);
});
}
function terminate() {
if (typeof removeEventListener.value === "function") removeEventListener.value();
}
async function processEvent(event) {
switch (event.type) {
case "testWebhookDeleted": return await testWebhookDeleted(event, options);
case "testWebhookReceived": return await testWebhookReceived(event, options);
case "reloadNodeType": return await reloadNodeType(event);
case "removeNodeType": return await removeNodeType(event);
case "nodeDescriptionUpdated": return await nodeDescriptionUpdated(event);
case "nodeExecuteBefore": return await nodeExecuteBefore(event, options);
case "nodeExecuteAfter": return await nodeExecuteAfter(event, options);
case "nodeExecuteAfterData": return await nodeExecuteAfterData(event);
case "executionStarted": return await executionStarted(event, options);
case "sendWorkerStatusMessage": return await sendWorkerStatusMessage(event);
case "sendConsoleMessage": return await sendConsoleMessage(event);
case "workflowFailedToActivate": return await workflowFailedToActivate(event, options);
case "executionFinished": return await executionFinished(event, options);
case "executionRecovered": return await executionRecovered(event, options);
case "workflowActivated": return await workflowActivated(event);
case "workflowDeactivated": return await workflowDeactivated(event);
case "workflowAutoDeactivated": return await workflowAutoDeactivated(event);
case "updateBuilderCredits": return await builderCreditsUpdated(event);
}
}
return {
initialize,
terminate
};
}
export { WORKER_HISTORY_LENGTH as n, useOrchestrationStore as r, usePushConnection as t };