From 7381c28af00148b329690021b921267a48a6eaa3 Mon Sep 17 00:00:00 2001 From: Shireen Missi <94372015+ShireenMissi@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:02:26 +0000 Subject: [PATCH 01/18] fix(YouTube Node): Issue in published before and after dates filters (#11741) --- .../nodes/Google/GenericFunctions.ts | 33 ++++++++++++---- .../nodes/Google/YouTube/YouTube.node.ts | 24 +++--------- .../YouTube/test/GenericFunctions.test.ts | 38 +++++++++++++++++++ 3 files changed, 69 insertions(+), 26 deletions(-) create mode 100644 packages/nodes-base/nodes/Google/YouTube/test/GenericFunctions.test.ts diff --git a/packages/nodes-base/nodes/Google/GenericFunctions.ts b/packages/nodes-base/nodes/Google/GenericFunctions.ts index 2c4781e5525..4a893ded009 100644 --- a/packages/nodes-base/nodes/Google/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/GenericFunctions.ts @@ -1,16 +1,18 @@ -import type { - IExecuteFunctions, - ILoadOptionsFunctions, - ICredentialTestFunctions, - IDataObject, - IPollFunctions, - IRequestOptions, +import { + type IExecuteFunctions, + type ILoadOptionsFunctions, + type ICredentialTestFunctions, + type IDataObject, + type IPollFunctions, + type IRequestOptions, + NodeOperationError, } from 'n8n-workflow'; import moment from 'moment-timezone'; import * as jwt from 'jsonwebtoken'; import { formatPrivateKey } from '@utils/utilities'; +import { DateTime } from 'luxon'; const googleServiceAccountScopes = { bigquery: ['https://www.googleapis.com/auth/bigquery'], @@ -110,3 +112,20 @@ export async function getGoogleAccessToken( return await this.helpers.request(options); } + +export function validateAndSetDate( + filter: IDataObject, + key: string, + timezone: string, + context: IExecuteFunctions, +) { + const date = DateTime.fromISO(filter[key] as string); + if (date.isValid) { + filter[key] = date.setZone(timezone).toISO(); + } else { + throw new NodeOperationError( + context.getNode(), + `The value "${filter[key] as string}" is not a valid DateTime.`, + ); + } +} diff --git a/packages/nodes-base/nodes/Google/YouTube/YouTube.node.ts b/packages/nodes-base/nodes/Google/YouTube/YouTube.node.ts index 07de1d36fac..663ec618964 100644 --- a/packages/nodes-base/nodes/Google/YouTube/YouTube.node.ts +++ b/packages/nodes-base/nodes/Google/YouTube/YouTube.node.ts @@ -10,7 +10,6 @@ import type { } from 'n8n-workflow'; import { NodeConnectionType, BINARY_ENCODING, NodeOperationError } from 'n8n-workflow'; -import { DateTime } from 'luxon'; import { googleApiRequest, googleApiRequestAllItems } from './GenericFunctions'; import { channelFields, channelOperations } from './ChannelDescription'; @@ -24,6 +23,7 @@ import { videoFields, videoOperations } from './VideoDescription'; import { videoCategoryFields, videoCategoryOperations } from './VideoCategoryDescription'; import { isoCountryCodes } from '@utils/ISOCountryCodes'; +import { validateAndSetDate } from '../GenericFunctions'; const UPLOAD_CHUNK_SIZE = 1024 * 1024; @@ -763,27 +763,13 @@ export class YouTube implements INodeType { qs.type = 'video'; qs.forMine = true; + if (filters.publishedAfter) { - const publishedAfter = DateTime.fromISO(filters.publishedAfter as string); - if (publishedAfter.isValid) { - filters.publishedAfter = publishedAfter.setZone(this.getTimezone()).toISO(); - } else { - throw new NodeOperationError( - this.getNode(), - `The value "${filters.publishedAfter as string}" is not a valid DateTime.`, - ); - } + validateAndSetDate(filters, 'publishedAfter', this.getTimezone(), this); } + if (filters.publishedBefore) { - const publishedBefore = DateTime.fromISO(filters.publishedBefore as string); - if (publishedBefore.isValid) { - filters.publishedAfter = publishedBefore.setZone(this.getTimezone()).toISO(); - } else { - throw new NodeOperationError( - this.getNode(), - `The value "${filters.publishedBefore as string}" is not a valid DateTime.`, - ); - } + validateAndSetDate(filters, 'publishedBefore', this.getTimezone(), this); } Object.assign(qs, options, filters); diff --git a/packages/nodes-base/nodes/Google/YouTube/test/GenericFunctions.test.ts b/packages/nodes-base/nodes/Google/YouTube/test/GenericFunctions.test.ts new file mode 100644 index 00000000000..d56e1b452b5 --- /dev/null +++ b/packages/nodes-base/nodes/Google/YouTube/test/GenericFunctions.test.ts @@ -0,0 +1,38 @@ +import { DateTime } from 'luxon'; +import { NodeOperationError, type IExecuteFunctions } from 'n8n-workflow'; + +import { validateAndSetDate } from '../../GenericFunctions'; + +const mockContext = { + getNode: jest.fn().mockReturnValue('Youtube'), +} as unknown as IExecuteFunctions; + +describe('validateAndSetDate', () => { + const timezone = 'America/New_York'; + let filter: { [key: string]: string }; + + beforeEach(() => { + filter = {}; + }); + + it('should convert a valid ISO date and set it with the specified timezone', () => { + filter.publishedAfter = '2023-10-05T10:00:00.000Z'; + validateAndSetDate(filter, 'publishedAfter', timezone, mockContext); + + expect(filter.publishedAfter).toBe( + DateTime.fromISO('2023-10-05T10:00:00.000Z').setZone(timezone).toISO(), + ); + }); + + it('should throw NodeOperationError for an invalid date', () => { + filter.publishedAfter = 'invalid-date'; + + expect(() => validateAndSetDate(filter, 'publishedAfter', timezone, mockContext)).toThrow( + NodeOperationError, + ); + + expect(() => validateAndSetDate(filter, 'publishedAfter', timezone, mockContext)).toThrow( + `The value "${filter.publishedAfter}" is not a valid DateTime.`, + ); + }); +}); From 7bb9002cbc10cf58550f53a30c6fd7151f8e7355 Mon Sep 17 00:00:00 2001 From: Shireen Missi <94372015+ShireenMissi@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:21:59 +0000 Subject: [PATCH 02/18] fix(editor): Fix the issue with RMC Values to Send collection disappears (#11710) --- .../editor-ui/src/components/ResourceMapper/ResourceMapper.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue b/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue index 78fb0952359..b09354ed00a 100644 --- a/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue +++ b/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue @@ -190,7 +190,7 @@ const matchingColumns = computed(() => { }); const hasAvailableMatchingColumns = computed(() => { - if (resourceMapperMode.value !== 'add') { + if (resourceMapperMode.value !== 'add' && resourceMapperMode.value !== 'upsert') { return ( state.paramValue.schema.filter( (field) => From 15ca2c4e450f4ab5144f67cf27c5f24e9e0c46d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Thu, 14 Nov 2024 17:39:46 +0100 Subject: [PATCH 03/18] refactor(editor): Stop cloning and serializing full execution data for `executionFinished` push message (#11703) --- cypress/package.json | 1 + cypress/support/index.ts | 4 +- cypress/utils/executions.ts | 74 ++++---- packages/@n8n/api-types/src/push/execution.ts | 4 +- packages/cli/package.json | 2 +- .../src/workflow-execute-additional-data.ts | 46 +---- packages/editor-ui/package.json | 2 +- packages/editor-ui/src/Interface.ts | 12 -- .../src/composables/usePushConnection.test.ts | 100 ++++++----- .../src/composables/usePushConnection.ts | 169 ++++++------------ .../src/composables/useRunWorkflow.ts | 11 -- .../src/stores/workflows.store.test.ts | 52 +----- .../editor-ui/src/stores/workflows.store.ts | 110 ------------ packages/editor-ui/src/views/NodeView.vue | 11 -- pnpm-lock.yaml | 152 ++++++++++++---- pnpm-workspace.yaml | 1 + 16 files changed, 273 insertions(+), 478 deletions(-) diff --git a/cypress/package.json b/cypress/package.json index 02e2a74c032..832abd9ef58 100644 --- a/cypress/package.json +++ b/cypress/package.json @@ -26,6 +26,7 @@ "cypress": "^13.14.2", "cypress-otp": "^1.0.3", "cypress-real-events": "^1.13.0", + "flatted": "catalog:", "lodash": "catalog:", "nanoid": "catalog:", "start-server-and-test": "^2.0.8" diff --git a/cypress/support/index.ts b/cypress/support/index.ts index 2fd1faeb22a..6be46315030 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -1,7 +1,7 @@ // Load type definitions that come with Cypress module /// -import type { FrontendSettings } from '@n8n/api-types'; +import type { FrontendSettings, PushPayload, PushType } from '@n8n/api-types'; Cypress.Keyboard.defaults({ keystrokeDelay: 0, @@ -66,7 +66,7 @@ declare global { droppableSelector: string, options?: Partial, ): void; - push(type: string, data: unknown): void; + push(type: Type, data: PushPayload): void; shouldNotHaveConsoleErrors(): void; window(): Chainable< AUTWindow & { diff --git a/cypress/utils/executions.ts b/cypress/utils/executions.ts index 0f429728560..12f4d2454a4 100644 --- a/cypress/utils/executions.ts +++ b/cypress/utils/executions.ts @@ -1,5 +1,5 @@ +import { stringify } from 'flatted'; import type { IDataObject, IPinData, ITaskData, ITaskDataConnections } from 'n8n-workflow'; -import { nanoid } from 'nanoid'; import { clickExecuteWorkflowButton } from '../composables/workflow'; @@ -39,41 +39,35 @@ export function createMockNodeExecutionData( }; } -export function createMockWorkflowExecutionData({ - executionId, +function createMockWorkflowExecutionData({ runData, - pinData = {}, lastNodeExecuted, }: { - executionId: string; runData: Record; pinData?: IPinData; lastNodeExecuted: string; }) { return { - executionId, - data: { - data: { - startData: {}, - resultData: { - runData, - pinData, - lastNodeExecuted, - }, - executionData: { - contextData: {}, - nodeExecutionStack: [], - metadata: {}, - waitingExecution: {}, - waitingExecutionSource: {}, - }, + data: stringify({ + startData: {}, + resultData: { + runData, + pinData: {}, + lastNodeExecuted, }, - mode: 'manual', - startedAt: new Date().toISOString(), - stoppedAt: new Date().toISOString(), - status: 'success', - finished: true, - }, + executionData: { + contextData: {}, + nodeExecutionStack: [], + metadata: {}, + waitingExecution: {}, + waitingExecutionSource: {}, + }, + }), + mode: 'manual', + startedAt: new Date().toISOString(), + stoppedAt: new Date().toISOString(), + status: 'success', + finished: true, }; } @@ -81,14 +75,12 @@ export function runMockWorkflowExecution({ trigger, lastNodeExecuted, runData, - workflowExecutionData, }: { trigger?: () => void; lastNodeExecuted: string; runData: Array>; - workflowExecutionData?: ReturnType; }) { - const executionId = nanoid(8); + const executionId = Math.floor(Math.random() * 1_000_000).toString(); cy.intercept('POST', '/rest/workflows/**/run?**', { statusCode: 201, @@ -125,13 +117,17 @@ export function runMockWorkflowExecution({ resolvedRunData[nodeName] = nodeExecution[nodeName]; }); - cy.push( - 'executionFinished', - createMockWorkflowExecutionData({ - executionId, - lastNodeExecuted, - runData: resolvedRunData, - ...workflowExecutionData, - }), - ); + cy.intercept('GET', `/rest/executions/${executionId}`, { + statusCode: 200, + body: { + data: createMockWorkflowExecutionData({ + lastNodeExecuted, + runData: resolvedRunData, + }), + }, + }).as('getExecution'); + + cy.push('executionFinished', { executionId }); + + cy.wait('@getExecution'); } diff --git a/packages/@n8n/api-types/src/push/execution.ts b/packages/@n8n/api-types/src/push/execution.ts index 78c0b34a363..3c7459dec57 100644 --- a/packages/@n8n/api-types/src/push/execution.ts +++ b/packages/@n8n/api-types/src/push/execution.ts @@ -1,4 +1,4 @@ -import type { IRun, ITaskData, WorkflowExecuteMode } from 'n8n-workflow'; +import type { ITaskData, WorkflowExecuteMode } from 'n8n-workflow'; type ExecutionStarted = { type: 'executionStarted'; @@ -16,8 +16,6 @@ type ExecutionFinished = { type: 'executionFinished'; data: { executionId: string; - data: IRun; - retryOf?: string; }; }; diff --git a/packages/cli/package.json b/packages/cli/package.json index 88f84389319..3f2b0397ab2 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -122,7 +122,7 @@ "express-rate-limit": "7.2.0", "fast-glob": "catalog:", "flat": "5.0.2", - "flatted": "3.2.7", + "flatted": "catalog:", "formidable": "3.5.1", "handlebars": "4.7.8", "helmet": "7.1.0", diff --git a/packages/cli/src/workflow-execute-additional-data.ts b/packages/cli/src/workflow-execute-additional-data.ts index 476ebf492dc..78c21b44c07 100644 --- a/packages/cli/src/workflow-execute-additional-data.ts +++ b/packages/cli/src/workflow-execute-additional-data.ts @@ -309,54 +309,18 @@ function hookFunctionsPush(): IWorkflowExecuteHooks { }, ], workflowExecuteAfter: [ - async function (this: WorkflowHooks, fullRunData: IRun): Promise { - const { pushRef, executionId, retryOf } = this; + async function (this: WorkflowHooks): Promise { + const { pushRef, executionId } = this; + if (pushRef === undefined) return; + const { id: workflowId } = this.workflowData; logger.debug('Executing hook (hookFunctionsPush)', { executionId, pushRef, workflowId, }); - // Push data to session which started the workflow - if (pushRef === undefined) { - return; - } - // Clone the object except the runData. That one is not supposed - // to be send. Because that data got send piece by piece after - // each node which finished executing - // Edit: we now DO send the runData to the UI if mode=manual so that it shows the point of crashes - let pushRunData; - if (fullRunData.mode === 'manual') { - pushRunData = fullRunData; - } else { - pushRunData = { - ...fullRunData, - data: { - ...fullRunData.data, - resultData: { - ...fullRunData.data.resultData, - runData: {}, - }, - }, - }; - } - - // Push data to editor-ui once workflow finished - logger.debug(`Save execution progress to database for execution ID ${executionId} `, { - executionId, - workflowId, - }); - // TODO: Look at this again - pushInstance.send( - 'executionFinished', - { - executionId, - data: pushRunData, - retryOf, - }, - pushRef, - ); + pushInstance.send('executionFinished', { executionId }, pushRef); }, ], }; diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index c9b866bb37b..905d0819a13 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -56,7 +56,7 @@ "esprima-next": "5.8.4", "fast-json-stable-stringify": "^2.1.0", "file-saver": "^2.0.2", - "flatted": "^3.2.4", + "flatted": "catalog:", "highlight.js": "catalog:frontend", "humanize-duration": "^3.27.2", "jsonpath": "^1.1.1", diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index 9db1ac10407..4bf5f02e84a 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -392,15 +392,10 @@ export interface IExecutionsListResponse { export interface IExecutionsCurrentSummaryExtended { id: string; - finished?: boolean; status: ExecutionStatus; mode: WorkflowExecuteMode; - retryOf?: string | null; - retrySuccessId?: string | null; startedAt: Date; - stoppedAt?: Date; workflowId: string; - workflowName?: string; } export interface IExecutionsStopData { @@ -839,14 +834,12 @@ export interface IUsedCredential { } export interface WorkflowsState { - activeExecutions: IExecutionsCurrentSummaryExtended[]; activeWorkflows: string[]; activeWorkflowExecution: ExecutionSummary | null; currentWorkflowExecutions: ExecutionSummary[]; activeExecutionId: string | null; executingNode: string[]; executionWaitingForWebhook: boolean; - finishedExecutionsCount: number; nodeMetadata: NodeMetadataMap; subWorkflowExecutionError: Error | null; usedCredentials: Record; @@ -1083,11 +1076,6 @@ export interface IVersionsState { currentVersion: IVersion | undefined; } -export interface IWorkflowsState { - currentWorkflowExecutions: ExecutionSummary[]; - activeWorkflowExecution: ExecutionSummary | null; - finishedExecutionsCount: number; -} export interface IWorkflowsMap { [name: string]: IWorkflowDb; } diff --git a/packages/editor-ui/src/composables/usePushConnection.test.ts b/packages/editor-ui/src/composables/usePushConnection.test.ts index 819b9e842e9..297235a4241 100644 --- a/packages/editor-ui/src/composables/usePushConnection.test.ts +++ b/packages/editor-ui/src/composables/usePushConnection.test.ts @@ -1,6 +1,9 @@ +import { stringify } from 'flatted'; import { useRouter } from 'vue-router'; import { createPinia, setActivePinia } from 'pinia'; import type { PushMessage, PushPayload } from '@n8n/api-types'; +import { mock } from 'vitest-mock-extended'; +import type { WorkflowOperationError } from 'n8n-workflow'; import { usePushConnection } from '@/composables/usePushConnection'; import { usePushConnectionStore } from '@/stores/pushConnection.store'; @@ -8,7 +11,7 @@ import { useOrchestrationStore } from '@/stores/orchestration.store'; import { useUIStore } from '@/stores/ui.store'; import { useWorkflowsStore } from '@/stores/workflows.store'; import { useToast } from '@/composables/useToast'; -import type { WorkflowOperationError } from 'n8n-workflow'; +import type { IExecutionResponse } from '@/Interface'; vi.mock('vue-router', () => { return { @@ -135,34 +138,40 @@ describe('usePushConnection()', () => { }); describe('executionFinished', () => { - it('should handle executionFinished event correctly', async () => { - const event: PushMessage = { - type: 'executionFinished', - data: { - executionId: '1', - data: { - data: { - resultData: { - runData: {}, - }, - }, - finished: true, - mode: 'manual', - startedAt: new Date(), - stoppedAt: new Date(), - status: 'success', - }, - }, - }; + const executionId = '1'; + const event: PushMessage = { + type: 'executionFinished', + data: { executionId: '1' }, + }; - workflowsStore.activeExecutionId = '1'; + beforeEach(() => { + workflowsStore.activeExecutionId = executionId; uiStore.isActionActive.workflowRunning = true; + }); + + it('should handle executionFinished event correctly', async () => { + const spy = vi.spyOn(workflowsStore, 'fetchExecutionDataById').mockResolvedValue( + mock({ + id: executionId, + data: stringify({ + resultData: { + runData: {}, + }, + }) as unknown as IExecutionResponse['data'], + finished: true, + mode: 'manual', + startedAt: new Date(), + stoppedAt: new Date(), + status: 'success', + }), + ); const result = await pushConnection.pushMessageReceived(event); expect(result).toBeTruthy(); expect(workflowsStore.workflowExecutionData).toBeDefined(); expect(uiStore.isActionActive['workflowRunning']).toBeTruthy(); + expect(spy).toHaveBeenCalledWith(executionId); expect(toast.showMessage).toHaveBeenCalledWith({ title: 'Workflow executed successfully', @@ -171,35 +180,29 @@ describe('usePushConnection()', () => { }); it('should handle isManualExecutionCancelled correctly', async () => { - const event: PushMessage = { - type: 'executionFinished', - data: { - executionId: '1', - data: { - data: { - startData: {}, - resultData: { - runData: { - 'Last Node': [], - }, - lastNodeExecuted: 'Last Node', - error: { - message: - 'Your trial has ended. Upgrade now to keep automating', - name: 'NodeApiError', - node: 'Last Node', - } as unknown as WorkflowOperationError, + const spy = vi.spyOn(workflowsStore, 'fetchExecutionDataById').mockResolvedValue( + mock({ + id: executionId, + data: stringify({ + startData: {}, + resultData: { + runData: { + 'Last Node': [], }, + lastNodeExecuted: 'Last Node', + error: { + message: + 'Your trial has ended. Upgrade now to keep automating', + name: 'NodeApiError', + node: 'Last Node', + } as unknown as WorkflowOperationError, }, - startedAt: new Date(), - mode: 'manual', - status: 'running', - }, - }, - }; - - workflowsStore.activeExecutionId = '1'; - uiStore.isActionActive['workflowRunning'] = true; + }) as unknown as IExecutionResponse['data'], + mode: 'manual', + startedAt: new Date(), + status: 'running', + }), + ); const result = await pushConnection.pushMessageReceived(event); @@ -215,6 +218,7 @@ describe('usePushConnection()', () => { expect(result).toBeTruthy(); expect(workflowsStore.workflowExecutionData).toBeDefined(); expect(uiStore.isActionActive.workflowRunning).toBeTruthy(); + expect(spy).toHaveBeenCalledWith(executionId); }); }); }); diff --git a/packages/editor-ui/src/composables/usePushConnection.ts b/packages/editor-ui/src/composables/usePushConnection.ts index a5976d35d48..1d03ffe2d7b 100644 --- a/packages/editor-ui/src/composables/usePushConnection.ts +++ b/packages/editor-ui/src/composables/usePushConnection.ts @@ -6,7 +6,6 @@ import type { ExpressionError, IDataObject, INodeTypeNameVersion, - IRun, IRunExecutionData, IWorkflowBase, SubworkflowOperationError, @@ -15,9 +14,8 @@ import type { INodeTypeDescription, NodeError, } from 'n8n-workflow'; -import type { PushMessage, PushPayload } from '@n8n/api-types'; +import type { PushMessage } from '@n8n/api-types'; -import type { IExecutionResponse, IExecutionsCurrentSummaryExtended } from '@/Interface'; import { useNodeHelpers } from '@/composables/useNodeHelpers'; import { useToast } from '@/composables/useToast'; import { WORKFLOW_SETTINGS_MODAL_KEY } from '@/constants'; @@ -38,8 +36,6 @@ import type { PushMessageQueueItem } from '@/types'; import { useAssistantStore } from '@/stores/assistant.store'; import NodeExecutionErrorMessage from '@/components/NodeExecutionErrorMessage.vue'; -type IPushDataExecutionFinishedPayload = PushPayload<'executionFinished'>; - export function usePushConnection({ router }: { router: ReturnType }) { const workflowHelpers = useWorkflowHelpers({ router }); const nodeHelpers = useNodeHelpers(); @@ -165,52 +161,6 @@ export function usePushConnection({ router }: { router: ReturnType 0 - ) - pushData.data.data.resultData.runData[key] = activeRunData[key]; - } - } - workflowsStore.finishActiveExecution(pushData); - } - if (!uiStore.isActionActive['workflowRunning']) { // No workflow is running so ignore the messages return false; } - if (activeExecutionId !== pushData.executionId) { + const { executionId } = receivedData.data; + const { activeExecutionId } = workflowsStore; + if (executionId !== activeExecutionId) { // The workflow which did finish execution did either not get started // by this session or we do not have the execution id yet. if (isRetry !== true) { @@ -277,13 +205,32 @@ export function usePushConnection({ router }: { router: ReturnType { const eventData: IDataObject = { @@ -365,8 +312,8 @@ export function usePushConnection({ router }: { router: ReturnType = { - data: executedData, - executionId, - retryOf: execution.retryOf, - }; - workflowsStore.finishActiveExecution(pushData); workflowHelpers.setDocumentTitle(execution.workflowData.name, 'IDLE'); workflowsStore.executingNode.length = 0; workflowsStore.setWorkflowExecutionData(executedData as IExecutionResponse); diff --git a/packages/editor-ui/src/stores/workflows.store.test.ts b/packages/editor-ui/src/stores/workflows.store.test.ts index 66471fafbac..8d56d8675ab 100644 --- a/packages/editor-ui/src/stores/workflows.store.test.ts +++ b/packages/editor-ui/src/stores/workflows.store.test.ts @@ -8,13 +8,7 @@ import { WAIT_NODE_TYPE, } from '@/constants'; import { useWorkflowsStore } from '@/stores/workflows.store'; -import type { - IExecutionResponse, - IExecutionsCurrentSummaryExtended, - INodeUi, - IWorkflowDb, - IWorkflowSettings, -} from '@/Interface'; +import type { IExecutionResponse, INodeUi, IWorkflowDb, IWorkflowSettings } from '@/Interface'; import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { SEND_AND_WAIT_OPERATION } from 'n8n-workflow'; @@ -619,50 +613,6 @@ describe('useWorkflowsStore', () => { }); }); }); - - describe('finishActiveExecution', () => { - it('should update execution', async () => { - const cursor = 1; - const ids = ['0', '1', '2']; - workflowsStore.setActiveExecutions( - ids.map((id) => ({ id })) as IExecutionsCurrentSummaryExtended[], - ); - - const stoppedAt = new Date(); - - workflowsStore.finishActiveExecution({ - executionId: ids[cursor], - data: { - finished: true, - stoppedAt, - }, - } as PushPayload<'executionFinished'>); - - expect(workflowsStore.activeExecutions[cursor]).toStrictEqual({ - id: ids[cursor], - finished: true, - stoppedAt, - }); - }); - - it('should handle parameter casting', async () => { - const cursor = 1; - const ids = ['0', '1', '2']; - workflowsStore.setActiveExecutions( - ids.map((id) => ({ id })) as IExecutionsCurrentSummaryExtended[], - ); - - workflowsStore.finishActiveExecution({ - executionId: ids[cursor], - } as PushPayload<'executionFinished'>); - - expect(workflowsStore.activeExecutions[cursor]).toStrictEqual({ - id: ids[cursor], - finished: undefined, - stoppedAt: undefined, - }); - }); - }); }); function getMockEditFieldsNode() { diff --git a/packages/editor-ui/src/stores/workflows.store.ts b/packages/editor-ui/src/stores/workflows.store.ts index a9305f23f22..4bf38546b1e 100644 --- a/packages/editor-ui/src/stores/workflows.store.ts +++ b/packages/editor-ui/src/stores/workflows.store.ts @@ -11,10 +11,8 @@ import { WAIT_NODE_TYPE, } from '@/constants'; import type { - ExecutionsQueryFilter, IExecutionPushResponse, IExecutionResponse, - IExecutionsCurrentSummaryExtended, IExecutionsListResponse, INewWorkflowData, INodeMetadata, @@ -125,10 +123,8 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => { const usedCredentials = ref>({}); const activeWorkflows = ref([]); - const activeExecutions = ref([]); const activeWorkflowExecution = ref(null); const currentWorkflowExecutions = ref([]); - const finishedExecutionsCount = ref(0); const workflowExecutionData = ref(null); const workflowExecutionPairedItemMappings = ref>>({}); const activeExecutionId = ref(null); @@ -266,8 +262,6 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => { const getWorkflowExecution = computed(() => workflowExecutionData.value); - const getTotalFinishedExecutionsCount = computed(() => finishedExecutionsCount.value); - const getPastChatMessages = computed(() => Array.from(new Set(chatMessages.value))); function getWorkflowResultDataByNodeName(nodeName: string): ITaskData[] | null { @@ -1338,52 +1332,6 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => { return ndvStore.activeNode; } - function addActiveExecution(newActiveExecution: IExecutionsCurrentSummaryExtended): void { - // Check if the execution exists already - const activeExecution = activeExecutions.value.find((execution) => { - return execution.id === newActiveExecution.id; - }); - - if (activeExecution !== undefined) { - // Exists already so no need to add it again - if (activeExecution.workflowName === undefined) { - activeExecution.workflowName = newActiveExecution.workflowName; - } - return; - } - - activeExecutions.value.unshift(newActiveExecution); - activeExecutionId.value = newActiveExecution.id; - } - - function finishActiveExecution(finishedActiveExecution: PushPayload<'executionFinished'>): void { - // Find the execution to set to finished - const activeExecutionIndex = activeExecutions.value.findIndex((execution) => { - return execution.id === finishedActiveExecution.executionId; - }); - - if (activeExecutionIndex === -1) { - // The execution could not be found - return; - } - - Object.assign(activeExecutions.value[activeExecutionIndex], { - ...(finishedActiveExecution.executionId !== undefined - ? { id: finishedActiveExecution.executionId } - : {}), - finished: finishedActiveExecution.data?.finished, - stoppedAt: finishedActiveExecution.data?.stoppedAt, - }); - - if (finishedActiveExecution.data?.data) { - setWorkflowExecutionRunData(finishedActiveExecution.data.data); - } - } - - function setActiveExecutions(newActiveExecutions: IExecutionsCurrentSummaryExtended[]): void { - activeExecutions.value = newActiveExecutions; - } - // TODO: For sure needs some kind of default filter like last day, with max 10 results, ... async function getPastExecutions( filter: IDataObject, @@ -1404,26 +1352,6 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => { return await makeRestApiRequest(rootStore.restApiContext, 'GET', '/executions', sendData); } - async function getActiveExecutions( - filter: IDataObject, - ): Promise { - let sendData = {}; - if (filter) { - sendData = { - filter, - }; - } - const rootStore = useRootStore(); - const output = await makeRestApiRequest<{ results: IExecutionsCurrentSummaryExtended[] }>( - rootStore.restApiContext, - 'GET', - '/executions', - sendData, - ); - - return output.results; - } - async function getExecution(id: string): Promise { const rootStore = useRootStore(); const response = await makeRestApiRequest( @@ -1507,36 +1435,6 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => { ); } - async function loadCurrentWorkflowExecutions( - requestFilter: ExecutionsQueryFilter, - ): Promise { - let retrievedActiveExecutions: IExecutionsCurrentSummaryExtended[] = []; - - if (!requestFilter.workflowId) { - return []; - } - - try { - const rootStore = useRootStore(); - if ((!requestFilter.status || !requestFilter.finished) && isEmpty(requestFilter.metadata)) { - retrievedActiveExecutions = await workflowsApi.getActiveExecutions( - rootStore.restApiContext, - { - workflowId: requestFilter.workflowId, - }, - ); - } - const retrievedFinishedExecutions = await workflowsApi.getExecutions( - rootStore.restApiContext, - requestFilter, - ); - finishedExecutionsCount.value = retrievedFinishedExecutions.count; - return [...retrievedActiveExecutions, ...(retrievedFinishedExecutions.results || [])]; - } catch (error) { - throw error; - } - } - async function fetchExecutionDataById(executionId: string): Promise { const rootStore = useRootStore(); return await workflowsApi.getExecutionData(rootStore.restApiContext, executionId); @@ -1644,10 +1542,8 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => { workflow, usedCredentials, activeWorkflows, - activeExecutions, activeWorkflowExecution, currentWorkflowExecutions, - finishedExecutionsCount, workflowExecutionData, workflowExecutionPairedItemMappings, activeExecutionId, @@ -1682,7 +1578,6 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => { executedNode, getAllLoadedFinishedExecutions, getWorkflowExecution, - getTotalFinishedExecutionsCount, getPastChatMessages, isChatPanelOpen: computed(() => isChatPanelOpen.value), isLogsPanelOpen: computed(() => isLogsPanelOpen.value), @@ -1760,17 +1655,12 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => { clearNodeExecutionData, pinDataByNodeName, activeNode, - addActiveExecution, - finishActiveExecution, - setActiveExecutions, getPastExecutions, - getActiveExecutions, getExecution, createNewWorkflow, updateWorkflow, runWorkflow, removeTestWebhook, - loadCurrentWorkflowExecutions, fetchExecutionDataById, deleteExecution, addToCurrentExecutions, diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 39d5a7c672c..72947fe4005 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -181,7 +181,6 @@ import { useNpsSurveyStore } from '@/stores/npsSurvey.store'; import { getResourcePermissions } from '@/permissions'; import { useBeforeUnload } from '@/composables/useBeforeUnload'; import NodeViewUnfinishedWorkflowMessage from '@/components/NodeViewUnfinishedWorkflowMessage.vue'; -import type { PushPayload } from '@n8n/api-types'; interface AddNodeOptions { position?: XYPosition; @@ -1728,10 +1727,6 @@ export default defineComponent({ 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() } as IRun, - }); this.workflowsStore.executingNode.length = 0; this.uiStore.removeActiveAction('workflowRunning'); @@ -1753,12 +1748,6 @@ export default defineComponent({ startedAt: execution.startedAt, stoppedAt: execution.stoppedAt, } as IRun; - const pushData: PushPayload<'executionFinished'> = { - data: executedData, - executionId, - retryOf: execution.retryOf, - }; - this.workflowsStore.finishActiveExecution(pushData); this.workflowHelpers.setDocumentTitle(execution.workflowData.name, 'IDLE'); this.workflowsStore.executingNode.length = 0; this.workflowsStore.setWorkflowExecutionData(executedData as IExecutionResponse); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dd9248e4148..1a43366f16a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,6 +33,9 @@ catalogs: fast-glob: specifier: 3.2.12 version: 3.2.12 + flatted: + specifier: 3.2.7 + version: 3.2.7 form-data: specifier: 4.0.0 version: 4.0.0 @@ -216,6 +219,9 @@ importers: cypress-real-events: specifier: ^1.13.0 version: 1.13.0(cypress@13.14.2) + flatted: + specifier: 'catalog:' + version: 3.2.7 lodash: specifier: 'catalog:' version: 4.17.21 @@ -265,7 +271,7 @@ importers: version: 4.0.7 axios: specifier: 'catalog:' - version: 1.7.4(debug@4.3.7) + version: 1.7.4 dotenv: specifier: 8.6.0 version: 8.6.0 @@ -333,7 +339,7 @@ importers: dependencies: axios: specifier: 'catalog:' - version: 1.7.4(debug@4.3.7) + version: 1.7.4 packages/@n8n/codemirror-lang: dependencies: @@ -407,7 +413,7 @@ importers: version: 3.666.0(@aws-sdk/client-sts@3.666.0) '@getzep/zep-cloud': specifier: 1.0.12 - version: 1.0.12(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.5(4ubssgvn2k3t3hxnzmxuoc2aja)) + version: 1.0.12(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.5(7umjwzmwnymi4lyinuvazmp6ki)) '@getzep/zep-js': specifier: 0.9.0 version: 0.9.0 @@ -434,7 +440,7 @@ importers: version: 0.3.1(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0))(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13) '@langchain/community': specifier: 0.3.11 - version: 0.3.11(tzffvezibmkr4px5bpuitcp7xu) + version: 0.3.11(simkpjwqw7qnwbripe37u5qu7a) '@langchain/core': specifier: 'catalog:' version: 0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)) @@ -521,7 +527,7 @@ importers: version: 23.0.1 langchain: specifier: 0.3.5 - version: 0.3.5(4ubssgvn2k3t3hxnzmxuoc2aja) + version: 0.3.5(7umjwzmwnymi4lyinuvazmp6ki) lodash: specifier: 'catalog:' version: 4.17.21 @@ -774,7 +780,7 @@ importers: version: 1.11.0 axios: specifier: 'catalog:' - version: 1.7.4(debug@4.3.7) + version: 1.7.4 bcryptjs: specifier: 2.4.3 version: 2.4.3 @@ -839,7 +845,7 @@ importers: specifier: 5.0.2 version: 5.0.2 flatted: - specifier: 3.2.7 + specifier: 'catalog:' version: 3.2.7 formidable: specifier: 3.5.1 @@ -1093,7 +1099,7 @@ importers: dependencies: '@langchain/core': specifier: 'catalog:' - version: 0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)) + version: 0.3.15(openai@4.69.0(zod@3.23.8)) '@n8n/client-oauth2': specifier: workspace:* version: link:../@n8n/client-oauth2 @@ -1105,7 +1111,7 @@ importers: version: 1.11.0 axios: specifier: 'catalog:' - version: 1.7.4(debug@4.3.7) + version: 1.7.4 concat-stream: specifier: 2.0.0 version: 2.0.0 @@ -1395,7 +1401,7 @@ importers: version: 10.11.0(vue@3.5.11(typescript@5.6.2)) axios: specifier: 'catalog:' - version: 1.7.4(debug@4.3.7) + version: 1.7.4 bowser: specifier: 2.11.0 version: 2.11.0 @@ -1424,7 +1430,7 @@ importers: specifier: ^2.0.2 version: 2.0.5 flatted: - specifier: ^3.2.4 + specifier: 'catalog:' version: 3.2.7 highlight.js: specifier: catalog:frontend @@ -1875,7 +1881,7 @@ importers: version: 0.15.2 axios: specifier: 'catalog:' - version: 1.7.4(debug@4.3.7) + version: 1.7.4 callsites: specifier: 3.1.0 version: 3.1.0 @@ -1921,7 +1927,7 @@ importers: devDependencies: '@langchain/core': specifier: 'catalog:' - version: 0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)) + version: 0.3.15(openai@4.69.0) '@types/deep-equal': specifier: ^1.0.1 version: 1.0.1 @@ -14076,7 +14082,7 @@ snapshots: '@gar/promisify@1.1.3': optional: true - '@getzep/zep-cloud@1.0.12(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.5(4ubssgvn2k3t3hxnzmxuoc2aja))': + '@getzep/zep-cloud@1.0.12(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.5(7umjwzmwnymi4lyinuvazmp6ki))': dependencies: form-data: 4.0.0 node-fetch: 2.7.0(encoding@0.1.13) @@ -14085,7 +14091,7 @@ snapshots: zod: 3.23.8 optionalDependencies: '@langchain/core': 0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)) - langchain: 0.3.5(4ubssgvn2k3t3hxnzmxuoc2aja) + langchain: 0.3.5(7umjwzmwnymi4lyinuvazmp6ki) transitivePeerDependencies: - encoding @@ -14552,7 +14558,7 @@ snapshots: - aws-crt - encoding - '@langchain/community@0.3.11(tzffvezibmkr4px5bpuitcp7xu)': + '@langchain/community@0.3.11(simkpjwqw7qnwbripe37u5qu7a)': dependencies: '@ibm-cloud/watsonx-ai': 1.1.2 '@langchain/core': 0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)) @@ -14562,7 +14568,7 @@ snapshots: flat: 5.0.2 ibm-cloud-sdk-core: 5.1.0 js-yaml: 4.1.0 - langchain: 0.3.5(4ubssgvn2k3t3hxnzmxuoc2aja) + langchain: 0.3.5(7umjwzmwnymi4lyinuvazmp6ki) langsmith: 0.2.3(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)) uuid: 10.0.0 zod: 3.23.8 @@ -14575,7 +14581,7 @@ snapshots: '@aws-sdk/client-s3': 3.666.0 '@aws-sdk/credential-provider-node': 3.666.0(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0))(@aws-sdk/client-sts@3.666.0) '@azure/storage-blob': 12.18.0(encoding@0.1.13) - '@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.5(4ubssgvn2k3t3hxnzmxuoc2aja)) + '@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.5(7umjwzmwnymi4lyinuvazmp6ki)) '@getzep/zep-js': 0.9.0 '@google-ai/generativelanguage': 2.6.0(encoding@0.1.13) '@google-cloud/storage': 7.12.1(encoding@0.1.13) @@ -14639,6 +14645,38 @@ snapshots: transitivePeerDependencies: - openai + '@langchain/core@0.3.15(openai@4.69.0(zod@3.23.8))': + dependencies: + ansi-styles: 5.2.0 + camelcase: 6.3.0 + decamelize: 1.2.0 + js-tiktoken: 1.0.12 + langsmith: 0.2.3(openai@4.69.0(zod@3.23.8)) + mustache: 4.2.0 + p-queue: 6.6.2 + p-retry: 4.6.2 + uuid: 10.0.0 + zod: 3.23.8 + zod-to-json-schema: 3.23.3(zod@3.23.8) + transitivePeerDependencies: + - openai + + '@langchain/core@0.3.15(openai@4.69.0)': + dependencies: + ansi-styles: 5.2.0 + camelcase: 6.3.0 + decamelize: 1.2.0 + js-tiktoken: 1.0.12 + langsmith: 0.2.3(openai@4.69.0) + mustache: 4.2.0 + p-queue: 6.6.2 + p-retry: 4.6.2 + uuid: 10.0.0 + zod: 3.23.8 + zod-to-json-schema: 3.23.3(zod@3.23.8) + transitivePeerDependencies: + - openai + '@langchain/google-common@0.1.1(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)))(zod@3.23.8)': dependencies: '@langchain/core': 0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)) @@ -15256,7 +15294,7 @@ snapshots: '@rudderstack/rudder-sdk-node@2.0.9(tslib@2.6.2)': dependencies: - axios: 1.7.4(debug@4.3.7) + axios: 1.7.4 axios-retry: 3.7.0 component-type: 1.2.1 join-component: 1.1.0 @@ -17512,7 +17550,23 @@ snapshots: '@babel/runtime': 7.24.7 is-retry-allowed: 2.2.0 + axios@1.7.4: + dependencies: + follow-redirects: 1.15.6(debug@4.3.6) + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + axios@1.7.4(debug@4.3.7): + dependencies: + follow-redirects: 1.15.6(debug@4.3.7) + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + axios@1.7.7: dependencies: follow-redirects: 1.15.6(debug@4.3.6) form-data: 4.0.0 @@ -19188,7 +19242,7 @@ snapshots: eslint-import-resolver-node@0.3.9: dependencies: - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7(supports-color@5.5.0) is-core-module: 2.13.1 resolve: 1.22.8 transitivePeerDependencies: @@ -19213,7 +19267,7 @@ snapshots: eslint-module-utils@2.8.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.6.2))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): dependencies: - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7(supports-color@5.5.0) optionalDependencies: '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.6.2) eslint: 8.57.0 @@ -19233,7 +19287,7 @@ snapshots: array.prototype.findlastindex: 1.2.3 array.prototype.flat: 1.3.2 array.prototype.flatmap: 1.3.2 - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7(supports-color@5.5.0) doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 @@ -20026,7 +20080,7 @@ snapshots: array-parallel: 0.1.3 array-series: 0.1.5 cross-spawn: 4.0.2 - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -20412,7 +20466,7 @@ snapshots: infisical-node@1.3.0: dependencies: - axios: 1.7.7(debug@4.3.6) + axios: 1.7.7 dotenv: 16.3.1 tweetnacl: 1.0.3 tweetnacl-util: 0.15.1 @@ -21342,7 +21396,7 @@ snapshots: kuler@2.0.0: {} - langchain@0.3.5(4ubssgvn2k3t3hxnzmxuoc2aja): + langchain@0.3.5(7umjwzmwnymi4lyinuvazmp6ki): dependencies: '@langchain/core': 0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)) '@langchain/openai': 0.3.11(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13) @@ -21366,7 +21420,7 @@ snapshots: '@langchain/groq': 0.1.2(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13) '@langchain/mistralai': 0.1.1(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13) '@langchain/ollama': 0.1.1(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8))) - axios: 1.7.4(debug@4.3.7) + axios: 1.7.4 cheerio: 1.0.0 handlebars: 4.7.8 transitivePeerDependencies: @@ -21385,6 +21439,28 @@ snapshots: optionalDependencies: openai: 4.69.0(encoding@0.1.13)(zod@3.23.8) + langsmith@0.2.3(openai@4.69.0(zod@3.23.8)): + dependencies: + '@types/uuid': 10.0.0 + commander: 10.0.1 + p-queue: 6.6.2 + p-retry: 4.6.2 + semver: 7.6.0 + uuid: 10.0.0 + optionalDependencies: + openai: 4.69.0(zod@3.23.8) + + langsmith@0.2.3(openai@4.69.0): + dependencies: + '@types/uuid': 10.0.0 + commander: 10.0.1 + p-queue: 6.6.2 + p-retry: 4.6.2 + semver: 7.6.0 + uuid: 10.0.0 + optionalDependencies: + openai: 4.69.0(zod@3.23.8) + lazy-ass@1.6.0: {} ldapts@4.2.6: @@ -22719,6 +22795,22 @@ snapshots: - encoding - supports-color + openai@4.69.0(zod@3.23.8): + dependencies: + '@types/node': 18.16.16 + '@types/node-fetch': 2.6.4 + abort-controller: 3.0.0 + agentkeepalive: 4.2.1 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0(encoding@0.1.13) + optionalDependencies: + zod: 3.23.8 + transitivePeerDependencies: + - encoding + - supports-color + optional: true + openapi-sampler@1.5.1: dependencies: '@types/json-schema': 7.0.15 @@ -22899,7 +22991,7 @@ snapshots: pdf-parse@1.1.1: dependencies: - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7(supports-color@5.5.0) node-ensure: 0.0.0 transitivePeerDependencies: - supports-color @@ -23101,7 +23193,7 @@ snapshots: posthog-node@3.2.1: dependencies: - axios: 1.7.7(debug@4.3.6) + axios: 1.7.7 rusha: 0.8.14 transitivePeerDependencies: - debug @@ -23730,7 +23822,7 @@ snapshots: rhea@1.0.24: dependencies: - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -24106,7 +24198,7 @@ snapshots: asn1.js: 5.4.1 asn1.js-rfc2560: 5.0.1(asn1.js@5.4.1) asn1.js-rfc5280: 3.0.0 - axios: 1.7.7(debug@4.3.6) + axios: 1.7.7 big-integer: 1.6.51 bignumber.js: 9.1.2 binascii: 0.0.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7dd0916282f..52e1230ca38 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -14,6 +14,7 @@ catalog: basic-auth: 2.0.1 chokidar: 4.0.1 fast-glob: 3.2.12 + flatted: 3.2.7 form-data: 4.0.0 lodash: 4.17.21 luxon: 3.4.4 From 76262ef8998751a0ebf63f9338f40acf825e0fd0 Mon Sep 17 00:00:00 2001 From: Eugene Date: Thu, 14 Nov 2024 18:47:57 +0100 Subject: [PATCH 04/18] chore: Migrate test definition PK to string nanoid (no-changelog) (#11742) --- .../databases/entities/test-definition.ee.ts | 18 ++--------- ...748663-MigrateTestDefinitionKeyToString.ts | 18 +++++++++++ .../src/databases/migrations/mysqldb/index.ts | 2 ++ ...748663-MigrateTestDefinitionKeyToString.ts | 30 +++++++++++++++++++ .../databases/migrations/postgresdb/index.ts | 2 ++ ...748663-MigrateTestDefinitionKeyToString.ts | 25 ++++++++++++++++ .../src/databases/migrations/sqlite/index.ts | 2 ++ .../test-definition.repository.ee.ts | 4 +-- .../evaluation/test-definition.service.ee.ts | 10 +++---- .../test-definitions.controller.ee.ts | 16 ++-------- 10 files changed, 92 insertions(+), 35 deletions(-) create mode 100644 packages/cli/src/databases/migrations/mysqldb/1731582748663-MigrateTestDefinitionKeyToString.ts create mode 100644 packages/cli/src/databases/migrations/postgresdb/1731582748663-MigrateTestDefinitionKeyToString.ts create mode 100644 packages/cli/src/databases/migrations/sqlite/1731582748663-MigrateTestDefinitionKeyToString.ts diff --git a/packages/cli/src/databases/entities/test-definition.ee.ts b/packages/cli/src/databases/entities/test-definition.ee.ts index f8ec8d6cb7f..dd39ebef02d 100644 --- a/packages/cli/src/databases/entities/test-definition.ee.ts +++ b/packages/cli/src/databases/entities/test-definition.ee.ts @@ -1,18 +1,10 @@ -import { - Column, - Entity, - Generated, - Index, - ManyToOne, - PrimaryColumn, - RelationId, -} from '@n8n/typeorm'; +import { Column, Entity, Index, ManyToOne, RelationId } from '@n8n/typeorm'; import { Length } from 'class-validator'; import { AnnotationTagEntity } from '@/databases/entities/annotation-tag-entity.ee'; import { WorkflowEntity } from '@/databases/entities/workflow-entity'; -import { WithTimestamps } from './abstract-entity'; +import { WithTimestampsAndStringId } from './abstract-entity'; /** * Entity representing a Test Definition @@ -24,11 +16,7 @@ import { WithTimestamps } from './abstract-entity'; @Entity() @Index(['workflow']) @Index(['evaluationWorkflow']) -export class TestDefinition extends WithTimestamps { - @Generated() - @PrimaryColumn() - id: number; - +export class TestDefinition extends WithTimestampsAndStringId { @Column({ length: 255 }) @Length(1, 255, { message: 'Test definition name must be $constraint1 to $constraint2 characters long.', diff --git a/packages/cli/src/databases/migrations/mysqldb/1731582748663-MigrateTestDefinitionKeyToString.ts b/packages/cli/src/databases/migrations/mysqldb/1731582748663-MigrateTestDefinitionKeyToString.ts new file mode 100644 index 00000000000..793aa3a262e --- /dev/null +++ b/packages/cli/src/databases/migrations/mysqldb/1731582748663-MigrateTestDefinitionKeyToString.ts @@ -0,0 +1,18 @@ +import type { MigrationContext, IrreversibleMigration } from '@/databases/types'; + +export class MigrateTestDefinitionKeyToString1731582748663 implements IrreversibleMigration { + async up(context: MigrationContext) { + const { queryRunner, tablePrefix } = context; + + await queryRunner.query( + `ALTER TABLE ${tablePrefix}test_definition CHANGE id tmp_id int NOT NULL AUTO_INCREMENT;`, + ); + await queryRunner.query( + `ALTER TABLE ${tablePrefix}test_definition ADD COLUMN id varchar(36) NOT NULL;`, + ); + await queryRunner.query(`UPDATE ${tablePrefix}test_definition SET id = CONVERT(tmp_id, CHAR);`); + await queryRunner.query( + `CREATE INDEX \`TMP_idx_${tablePrefix}test_definition_id\` ON ${tablePrefix}test_definition (\`id\`);`, + ); + } +} diff --git a/packages/cli/src/databases/migrations/mysqldb/index.ts b/packages/cli/src/databases/migrations/mysqldb/index.ts index 5f0b23d6013..4b219e9a266 100644 --- a/packages/cli/src/databases/migrations/mysqldb/index.ts +++ b/packages/cli/src/databases/migrations/mysqldb/index.ts @@ -43,6 +43,7 @@ import { MigrateIntegerKeysToString1690000000001 } from './1690000000001-Migrate import { SeparateExecutionData1690000000030 } from './1690000000030-SeparateExecutionData'; import { FixExecutionDataType1690000000031 } from './1690000000031-FixExecutionDataType'; import { AddActivatedAtUserSetting1717498465931 } from './1717498465931-AddActivatedAtUserSetting'; +import { MigrateTestDefinitionKeyToString1731582748663 } from './1731582748663-MigrateTestDefinitionKeyToString'; import { CreateLdapEntities1674509946020 } from '../common/1674509946020-CreateLdapEntities'; import { PurgeInvalidWorkflowConnections1675940580449 } from '../common/1675940580449-PurgeInvalidWorkflowConnections'; import { RemoveResetPasswordColumns1690000000030 } from '../common/1690000000030-RemoveResetPasswordColumns'; @@ -142,4 +143,5 @@ export const mysqlMigrations: Migration[] = [ UpdateProcessedDataValueColumnToText1729607673464, CreateTestDefinitionTable1730386903556, AddDescriptionToTestDefinition1731404028106, + MigrateTestDefinitionKeyToString1731582748663, ]; diff --git a/packages/cli/src/databases/migrations/postgresdb/1731582748663-MigrateTestDefinitionKeyToString.ts b/packages/cli/src/databases/migrations/postgresdb/1731582748663-MigrateTestDefinitionKeyToString.ts new file mode 100644 index 00000000000..9afc1c3ae38 --- /dev/null +++ b/packages/cli/src/databases/migrations/postgresdb/1731582748663-MigrateTestDefinitionKeyToString.ts @@ -0,0 +1,30 @@ +import type { MigrationContext, IrreversibleMigration } from '@/databases/types'; + +export class MigrateTestDefinitionKeyToString1731582748663 implements IrreversibleMigration { + async up(context: MigrationContext) { + const { queryRunner, tablePrefix } = context; + + await queryRunner.query( + `ALTER TABLE ${tablePrefix}test_definition RENAME COLUMN id to tmp_id;`, + ); + await queryRunner.query(`ALTER TABLE ${tablePrefix}test_definition ADD COLUMN id varchar(36);`); + await queryRunner.query(`UPDATE ${tablePrefix}test_definition SET id = tmp_id::text;`); + await queryRunner.query( + `ALTER TABLE ${tablePrefix}test_definition ALTER COLUMN id SET NOT NULL;`, + ); + await queryRunner.query( + `ALTER TABLE ${tablePrefix}test_definition ALTER COLUMN tmp_id DROP DEFAULT;`, + ); + await queryRunner.query(`DROP SEQUENCE IF EXISTS ${tablePrefix}test_definition_id_seq;`); + await queryRunner.query( + `CREATE UNIQUE INDEX "pk_${tablePrefix}test_definition_id" ON ${tablePrefix}test_definition ("id");`, + ); + + await queryRunner.query( + `ALTER TABLE ${tablePrefix}test_definition DROP CONSTRAINT IF EXISTS "PK_${tablePrefix}245a0013672c8cdc7727afa9b99";`, + ); + + await queryRunner.query(`ALTER TABLE ${tablePrefix}test_definition DROP COLUMN tmp_id;`); + await queryRunner.query(`ALTER TABLE ${tablePrefix}test_definition ADD PRIMARY KEY (id);`); + } +} diff --git a/packages/cli/src/databases/migrations/postgresdb/index.ts b/packages/cli/src/databases/migrations/postgresdb/index.ts index 53722563718..689840b9371 100644 --- a/packages/cli/src/databases/migrations/postgresdb/index.ts +++ b/packages/cli/src/databases/migrations/postgresdb/index.ts @@ -43,6 +43,7 @@ import { AddMissingPrimaryKeyOnExecutionData1690787606731 } from './169078760673 import { MigrateToTimestampTz1694091729095 } from './1694091729095-MigrateToTimestampTz'; import { AddActivatedAtUserSetting1717498465931 } from './1717498465931-AddActivatedAtUserSetting'; import { FixExecutionMetadataSequence1721377157740 } from './1721377157740-FixExecutionMetadataSequence'; +import { MigrateTestDefinitionKeyToString1731582748663 } from './1731582748663-MigrateTestDefinitionKeyToString'; import { CreateLdapEntities1674509946020 } from '../common/1674509946020-CreateLdapEntities'; import { PurgeInvalidWorkflowConnections1675940580449 } from '../common/1675940580449-PurgeInvalidWorkflowConnections'; import { RemoveResetPasswordColumns1690000000030 } from '../common/1690000000030-RemoveResetPasswordColumns'; @@ -142,4 +143,5 @@ export const postgresMigrations: Migration[] = [ UpdateProcessedDataValueColumnToText1729607673464, CreateTestDefinitionTable1730386903556, AddDescriptionToTestDefinition1731404028106, + MigrateTestDefinitionKeyToString1731582748663, ]; diff --git a/packages/cli/src/databases/migrations/sqlite/1731582748663-MigrateTestDefinitionKeyToString.ts b/packages/cli/src/databases/migrations/sqlite/1731582748663-MigrateTestDefinitionKeyToString.ts new file mode 100644 index 00000000000..4ffa3da9833 --- /dev/null +++ b/packages/cli/src/databases/migrations/sqlite/1731582748663-MigrateTestDefinitionKeyToString.ts @@ -0,0 +1,25 @@ +import type { MigrationContext, IrreversibleMigration } from '@/databases/types'; + +export class MigrateTestDefinitionKeyToString1731582748663 implements IrreversibleMigration { + transaction = false as const; + + async up(context: MigrationContext) { + const { queryRunner, tablePrefix } = context; + + await queryRunner.query(` + CREATE TABLE "${tablePrefix}TMP_test_definition" ("id" varchar(36) PRIMARY KEY NOT NULL, "name" varchar(255) NOT NULL, "workflowId" varchar(36) NOT NULL, "evaluationWorkflowId" varchar(36), "annotationTagId" varchar(16), "createdAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), "updatedAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), "description" text, CONSTRAINT "FK_${tablePrefix}test_definition_annotation_tag" FOREIGN KEY ("annotationTagId") REFERENCES "annotation_tag_entity" ("id") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_${tablePrefix}test_definition_evaluation_workflow_entity" FOREIGN KEY ("evaluationWorkflowId") REFERENCES "workflow_entity" ("id") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_${tablePrefix}test_definition_workflow_entity" FOREIGN KEY ("workflowId") REFERENCES "workflow_entity" ("id") ON DELETE CASCADE ON UPDATE NO ACTION);`); + await queryRunner.query( + `INSERT INTO "${tablePrefix}TMP_test_definition" SELECT * FROM "${tablePrefix}test_definition";`, + ); + await queryRunner.query(`DROP TABLE "${tablePrefix}test_definition";`); + await queryRunner.query( + `ALTER TABLE "${tablePrefix}TMP_test_definition" RENAME TO "${tablePrefix}test_definition";`, + ); + await queryRunner.query( + `CREATE INDEX "idx_${tablePrefix}test_definition_workflow_id" ON "${tablePrefix}test_definition" ("workflowId");`, + ); + await queryRunner.query( + `CREATE INDEX "idx_${tablePrefix}test_definition_evaluation_workflow_id" ON "${tablePrefix}test_definition" ("evaluationWorkflowId");`, + ); + } +} diff --git a/packages/cli/src/databases/migrations/sqlite/index.ts b/packages/cli/src/databases/migrations/sqlite/index.ts index e9b09ff483e..5c7c6d9a072 100644 --- a/packages/cli/src/databases/migrations/sqlite/index.ts +++ b/packages/cli/src/databases/migrations/sqlite/index.ts @@ -40,6 +40,7 @@ import { AddActivatedAtUserSetting1717498465931 } from './1717498465931-AddActiv import { AddApiKeysTable1724951148974 } from './1724951148974-AddApiKeysTable'; import { AddMissingPrimaryKeyOnAnnotationTagMapping1728659839644 } from './1728659839644-AddMissingPrimaryKeyOnAnnotationTagMapping'; import { AddDescriptionToTestDefinition1731404028106 } from './1731404028106-AddDescriptionToTestDefinition'; +import { MigrateTestDefinitionKeyToString1731582748663 } from './1731582748663-MigrateTestDefinitionKeyToString'; import { UniqueWorkflowNames1620821879465 } from '../common/1620821879465-UniqueWorkflowNames'; import { UpdateWorkflowCredentials1630330987096 } from '../common/1630330987096-UpdateWorkflowCredentials'; import { AddNodeIds1658930531669 } from '../common/1658930531669-AddNodeIds'; @@ -136,6 +137,7 @@ const sqliteMigrations: Migration[] = [ UpdateProcessedDataValueColumnToText1729607673464, CreateTestDefinitionTable1730386903556, AddDescriptionToTestDefinition1731404028106, + MigrateTestDefinitionKeyToString1731582748663, ]; export { sqliteMigrations }; diff --git a/packages/cli/src/databases/repositories/test-definition.repository.ee.ts b/packages/cli/src/databases/repositories/test-definition.repository.ee.ts index e9ec3da65d1..ecd4bdde345 100644 --- a/packages/cli/src/databases/repositories/test-definition.repository.ee.ts +++ b/packages/cli/src/databases/repositories/test-definition.repository.ee.ts @@ -37,7 +37,7 @@ export class TestDefinitionRepository extends Repository { return { testDefinitions, count }; } - async getOne(id: number, accessibleWorkflowIds: string[]) { + async getOne(id: string, accessibleWorkflowIds: string[]) { return await this.findOne({ where: { id, @@ -49,7 +49,7 @@ export class TestDefinitionRepository extends Repository { }); } - async deleteById(id: number, accessibleWorkflowIds: string[]) { + async deleteById(id: string, accessibleWorkflowIds: string[]) { return await this.delete({ id, workflow: { diff --git a/packages/cli/src/evaluation/test-definition.service.ee.ts b/packages/cli/src/evaluation/test-definition.service.ee.ts index 404167078bf..d32a2e153d1 100644 --- a/packages/cli/src/evaluation/test-definition.service.ee.ts +++ b/packages/cli/src/evaluation/test-definition.service.ee.ts @@ -30,7 +30,7 @@ export class TestDefinitionService { workflowId?: string; evaluationWorkflowId?: string; annotationTagId?: string; - id?: number; + id?: string; }) { const entity: TestDefinitionLike = {}; @@ -72,13 +72,13 @@ export class TestDefinitionService { workflowId?: string; evaluationWorkflowId?: string; annotationTagId?: string; - id?: number; + id?: string; }) { const entity = this.toEntityLike(attrs); return this.testDefinitionRepository.create(entity); } - async findOne(id: number, accessibleWorkflowIds: string[]) { + async findOne(id: string, accessibleWorkflowIds: string[]) { return await this.testDefinitionRepository.getOne(id, accessibleWorkflowIds); } @@ -88,7 +88,7 @@ export class TestDefinitionService { return await this.testDefinitionRepository.save(test); } - async update(id: number, attrs: TestDefinitionLike) { + async update(id: string, attrs: TestDefinitionLike) { if (attrs.name) { const updatedTest = this.toEntity(attrs); await validateEntity(updatedTest); @@ -115,7 +115,7 @@ export class TestDefinitionService { } } - async delete(id: number, accessibleWorkflowIds: string[]) { + async delete(id: string, accessibleWorkflowIds: string[]) { const deleteResult = await this.testDefinitionRepository.deleteById(id, accessibleWorkflowIds); if (deleteResult.affected === 0) { diff --git a/packages/cli/src/evaluation/test-definitions.controller.ee.ts b/packages/cli/src/evaluation/test-definitions.controller.ee.ts index c73afaeb3d2..eaa7745c519 100644 --- a/packages/cli/src/evaluation/test-definitions.controller.ee.ts +++ b/packages/cli/src/evaluation/test-definitions.controller.ee.ts @@ -2,7 +2,6 @@ import express from 'express'; import assert from 'node:assert'; import { Get, Post, Patch, RestController, Delete } from '@/decorators'; -import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { @@ -11,21 +10,12 @@ import { } from '@/evaluation/test-definition.schema'; import { listQueryMiddleware } from '@/middlewares'; import { getSharedWorkflowIds } from '@/public-api/v1/handlers/workflows/workflows.service'; -import { isPositiveInteger } from '@/utils'; import { TestDefinitionService } from './test-definition.service.ee'; import { TestDefinitionsRequest } from './test-definitions.types.ee'; @RestController('/evaluation/test-definitions') export class TestDefinitionsController { - private validateId(id: string) { - if (!isPositiveInteger(id)) { - throw new BadRequestError('Test ID is not a number'); - } - - return Number(id); - } - constructor(private readonly testDefinitionService: TestDefinitionService) {} @Get('/', { middlewares: listQueryMiddleware }) @@ -40,7 +30,7 @@ export class TestDefinitionsController { @Get('/:id') async getOne(req: TestDefinitionsRequest.GetOne) { - const testDefinitionId = this.validateId(req.params.id); + const { id: testDefinitionId } = req.params; const userAccessibleWorkflowIds = await getSharedWorkflowIds(req.user, ['workflow:read']); @@ -82,7 +72,7 @@ export class TestDefinitionsController { @Delete('/:id') async delete(req: TestDefinitionsRequest.Delete) { - const testDefinitionId = this.validateId(req.params.id); + const { id: testDefinitionId } = req.params; const userAccessibleWorkflowIds = await getSharedWorkflowIds(req.user, ['workflow:read']); @@ -96,7 +86,7 @@ export class TestDefinitionsController { @Patch('/:id') async patch(req: TestDefinitionsRequest.Patch, res: express.Response) { - const testDefinitionId = this.validateId(req.params.id); + const { id: testDefinitionId } = req.params; const bodyParseResult = testDefinitionPatchRequestBodySchema.safeParse(req.body); if (!bodyParseResult.success) { From f4ca4b792f94139f7a0ed50b955f1c9ab92a5432 Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> Date: Thu, 14 Nov 2024 20:02:06 +0100 Subject: [PATCH 05/18] test: Fix flaky tags tests (no-changelog) (#11746) --- cypress/pages/workflow.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cypress/pages/workflow.ts b/cypress/pages/workflow.ts index cd1e7d94623..ee90fa55e80 100644 --- a/cypress/pages/workflow.ts +++ b/cypress/pages/workflow.ts @@ -17,7 +17,8 @@ export class WorkflowPage extends BasePage { workflowTagsContainer: () => cy.getByTestId('workflow-tags-container'), workflowTagsInput: () => this.getters.workflowTagsContainer().then(($el) => cy.wrap($el.find('input').first())), - tagPills: () => cy.get('[data-test-id="workflow-tags-container"] span.el-tag'), + tagPills: () => + cy.get('[data-test-id="workflow-tags-container"] span.el-tag:not(.count-container)'), nthTagPill: (n: number) => cy.get(`[data-test-id="workflow-tags-container"] span.el-tag:nth-child(${n})`), tagsDropdown: () => cy.getByTestId('workflow-tags-dropdown'), From fd3254d5874a03b57421246b77a519787536a93e Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> Date: Thu, 14 Nov 2024 23:04:43 +0100 Subject: [PATCH 06/18] feat(core): Improve debugging of sub-workflows (#11602) --- cypress/composables/executions.ts | 29 ++ cypress/composables/ndv.ts | 33 ++ cypress/composables/workflow.ts | 24 ++ cypress/e2e/24-ndv-paired-item.cy.ts | 71 ++-- cypress/e2e/47-subworkflow-debugging.cy.ts | 140 +++++++ ...ubworkflow-debugging-execute-workflow.json | 354 ++++++++++++++++++ cypress/pages/ndv.ts | 6 + cypress/support/commands.ts | 10 + cypress/support/index.ts | 8 +- .../RetrieverWorkflow.node.ts | 23 +- .../tools/ToolWorkflow/ToolWorkflow.node.ts | 46 ++- .../@n8n/nodes-langchain/utils/logWrapper.ts | 25 +- packages/cli/package.json | 1 + .../workflow-execute-additional-data.test.ts | 212 +++++++++-- packages/cli/src/interfaces.ts | 8 - .../src/workflow-execute-additional-data.ts | 59 ++- packages/core/package.json | 3 +- packages/core/src/NodeExecuteFunctions.ts | 61 ++- packages/core/src/WorkflowExecute.ts | 6 +- .../__tests__/execute-single-context.test.ts | 30 ++ .../execute-single-context.ts | 8 + packages/editor-ui/src/Interface.ts | 8 + .../src/components/PanelDragButton.vue | 2 +- .../editor-ui/src/components/RunData.test.ts | 278 +++++++++++--- packages/editor-ui/src/components/RunData.vue | 213 ++++++++--- .../src/components/RunDataAi/RunDataAi.vue | 1 + .../components/RunDataAi/RunDataAiContent.vue | 24 ++ .../editor-ui/src/components/RunDataTable.vue | 108 +++++- .../global/GlobalExecutionsListItem.vue | 10 +- .../composables/useExecutionHelpers.test.ts | 82 ++++ .../src/composables/useExecutionHelpers.ts | 56 ++- .../src/composables/useNodeHelpers.ts | 42 ++- .../src/plugins/i18n/locales/en.json | 6 + .../src/views/WorkflowExecutionsView.vue | 11 + .../ExecuteWorkflow/ExecuteWorkflow.node.ts | 81 +++- packages/workflow/src/Interfaces.ts | 29 +- 36 files changed, 1843 insertions(+), 265 deletions(-) create mode 100644 cypress/composables/executions.ts create mode 100644 cypress/e2e/47-subworkflow-debugging.cy.ts create mode 100644 cypress/fixtures/Subworkflow-debugging-execute-workflow.json diff --git a/cypress/composables/executions.ts b/cypress/composables/executions.ts new file mode 100644 index 00000000000..cd07eb7e6a2 --- /dev/null +++ b/cypress/composables/executions.ts @@ -0,0 +1,29 @@ +/** + * Getters + */ + +export const getExecutionsSidebar = () => cy.getByTestId('executions-sidebar'); + +export const getWorkflowExecutionPreviewIframe = () => cy.getByTestId('workflow-preview-iframe'); + +export const getExecutionPreviewBody = () => + getWorkflowExecutionPreviewIframe() + .its('0.contentDocument.body') + .then((el) => cy.wrap(el)); + +export const getExecutionPreviewBodyNodes = () => + getExecutionPreviewBody().findChildByTestId('canvas-node'); + +export const getExecutionPreviewBodyNodesByName = (name: string) => + getExecutionPreviewBody().findChildByTestId('canvas-node').filter(`[data-name="${name}"]`).eq(0); + +export function getExecutionPreviewOutputPanelRelatedExecutionLink() { + return getExecutionPreviewBody().findChildByTestId('related-execution-link'); +} + +/** + * Actions + */ + +export const openExecutionPreviewNode = (name: string) => + getExecutionPreviewBodyNodesByName(name).dblclick(); diff --git a/cypress/composables/ndv.ts b/cypress/composables/ndv.ts index b7ea33cb69e..05d783ec5e8 100644 --- a/cypress/composables/ndv.ts +++ b/cypress/composables/ndv.ts @@ -48,10 +48,38 @@ export function getOutputTableRow(row: number) { return getOutputTableRows().eq(row); } +export function getOutputTableHeaders() { + return getOutputPanelDataContainer().find('table thead th'); +} + +export function getOutputTableHeaderByText(text: string) { + return getOutputTableHeaders().contains(text); +} + +export function getOutputTbodyCell(row: number, col: number) { + return getOutputTableRows().eq(row).find('td').eq(col); +} + +export function getOutputRunSelector() { + return getOutputPanel().findChildByTestId('run-selector'); +} + +export function getOutputRunSelectorInput() { + return getOutputRunSelector().find('input'); +} + export function getOutputPanelTable() { return getOutputPanelDataContainer().get('table'); } +export function getOutputPanelItemsCount() { + return getOutputPanel().getByTestId('ndv-items-count'); +} + +export function getOutputPanelRelatedExecutionLink() { + return getOutputPanel().getByTestId('related-execution-link'); +} + /** * Actions */ @@ -90,3 +118,8 @@ export function setParameterSelectByContent(name: string, content: string) { getParameterInputByName(name).realClick(); getVisibleSelect().find('.option-headline').contains(content).click(); } + +export function changeOutputRunSelector(runName: string) { + getOutputRunSelector().click(); + getVisibleSelect().find('.el-select-dropdown__item').contains(runName).click(); +} diff --git a/cypress/composables/workflow.ts b/cypress/composables/workflow.ts index bab18587e02..251db6e75d4 100644 --- a/cypress/composables/workflow.ts +++ b/cypress/composables/workflow.ts @@ -76,6 +76,14 @@ export function getCanvasNodes() { ); } +export function getSaveButton() { + return cy.getByTestId('workflow-save-button'); +} + +export function getZoomToFitButton() { + return cy.getByTestId('zoom-to-fit'); +} + /** * Actions */ @@ -170,3 +178,19 @@ export function clickManualChatButton() { export function openNode(nodeName: string) { getNodeByName(nodeName).dblclick(); } + +export function saveWorkflowOnButtonClick() { + cy.intercept('POST', '/rest/workflows').as('createWorkflow'); + getSaveButton().should('contain', 'Save'); + getSaveButton().click(); + getSaveButton().should('contain', 'Saved'); + cy.url().should('not.have.string', '/new'); +} + +export function pasteWorkflow(workflow: object) { + cy.get('body').paste(JSON.stringify(workflow)); +} + +export function clickZoomToFit() { + getZoomToFitButton().click(); +} diff --git a/cypress/e2e/24-ndv-paired-item.cy.ts b/cypress/e2e/24-ndv-paired-item.cy.ts index 1261a0fcd1b..49257a8a12f 100644 --- a/cypress/e2e/24-ndv-paired-item.cy.ts +++ b/cypress/e2e/24-ndv-paired-item.cy.ts @@ -31,29 +31,31 @@ describe('NDV', () => { ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); - ndv.getters.inputTableRow(1).realHover(); + ndv.actions.dragMainPanelToRight(); + ndv.getters.inputTableRow(1).realMouseMove(10, 1); ndv.getters.outputTableRow(4).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); - ndv.getters.inputTableRow(2).realHover(); + ndv.getters.inputTableRow(2).realMouseMove(10, 1); ndv.getters.outputTableRow(2).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); - ndv.getters.inputTableRow(3).realHover(); + ndv.getters.inputTableRow(3).realMouseMove(10, 1); ndv.getters.outputTableRow(6).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); // output to input - ndv.getters.outputTableRow(1).realHover(); + ndv.actions.dragMainPanelToLeft(); + ndv.getters.outputTableRow(1).realMouseMove(10, 1); ndv.getters.inputTableRow(4).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); - ndv.getters.outputTableRow(4).realHover(); + ndv.getters.outputTableRow(4).realMouseMove(10, 1); ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); - ndv.getters.outputTableRow(2).realHover(); + ndv.getters.outputTableRow(2).realMouseMove(10, 1); ndv.getters.inputTableRow(2).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); - ndv.getters.outputTableRow(6).realHover(); + ndv.getters.outputTableRow(6).realMouseMove(10, 1); ndv.getters.inputTableRow(3).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); - ndv.getters.outputTableRow(1).realHover(); + ndv.getters.outputTableRow(1).realMouseMove(10, 1); ndv.getters.inputTableRow(4).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); }); @@ -75,31 +77,32 @@ describe('NDV', () => { ndv.actions.switchInputMode('Table'); ndv.actions.switchOutputMode('Table'); - ndv.getters.backToCanvas().realHover(); // reset to default hover + ndv.getters.backToCanvas().realMouseMove(10, 1); // reset to default hover ndv.getters.outputHoveringItem().should('not.exist'); ndv.getters.parameterExpressionPreview('value').should('include.text', '1111'); ndv.actions.selectInputNode('Set1'); - ndv.getters.backToCanvas().realHover(); // reset to default hover + ndv.getters.backToCanvas().realMouseMove(10, 1); // reset to default hover ndv.getters.inputTableRow(1).should('have.text', '1000'); ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); - ndv.getters.inputTableRow(1).realHover(); - cy.wait(50); + ndv.actions.dragMainPanelToRight(); + ndv.getters.inputTbodyCell(1, 0).realMouseMove(10, 1); ndv.getters.outputHoveringItem().should('have.text', '1000'); ndv.getters.parameterExpressionPreview('value').should('include.text', '1000'); ndv.actions.selectInputNode('Sort'); + ndv.actions.dragMainPanelToLeft(); ndv.actions.changeOutputRunSelector('1 of 2 (6 items)'); - ndv.getters.backToCanvas().realHover(); // reset to default hover + ndv.getters.backToCanvas().realMouseMove(10, 1); // reset to default hover ndv.getters.inputTableRow(1).should('have.text', '1111'); ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); - ndv.getters.inputTableRow(1).realHover(); - cy.wait(50); + ndv.actions.dragMainPanelToRight(); + ndv.getters.inputTbodyCell(1, 0).realMouseMove(10, 1); ndv.getters.outputHoveringItem().should('have.text', '1111'); ndv.getters.parameterExpressionPreview('value').should('include.text', '1111'); }); @@ -132,20 +135,22 @@ describe('NDV', () => { ndv.getters.inputTableRow(1).should('have.text', '1111'); ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); + + ndv.actions.dragMainPanelToLeft(); ndv.getters.outputTableRow(1).should('have.text', '1111'); - ndv.getters.outputTableRow(1).realHover(); + ndv.getters.outputTableRow(1).realMouseMove(10, 1); ndv.getters.outputTableRow(3).should('have.text', '4444'); - ndv.getters.outputTableRow(3).realHover(); + ndv.getters.outputTableRow(3).realMouseMove(10, 1); ndv.getters.inputTableRow(3).should('have.text', '4444'); ndv.getters.inputTableRow(3).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); ndv.actions.changeOutputRunSelector('2 of 2 (6 items)'); - cy.wait(50); ndv.getters.inputTableRow(1).should('have.text', '1000'); - ndv.getters.inputTableRow(1).realHover(); + ndv.actions.dragMainPanelToRight(); + ndv.getters.inputTableRow(1).realMouseMove(10, 1); ndv.getters.outputTableRow(1).should('have.text', '1000'); ndv.getters @@ -155,7 +160,8 @@ describe('NDV', () => { .should('equal', 'hovering-item'); ndv.getters.outputTableRow(3).should('have.text', '2000'); - ndv.getters.outputTableRow(3).realHover(); + ndv.actions.dragMainPanelToLeft(); + ndv.getters.outputTableRow(3).realMouseMove(10, 1); ndv.getters.inputTableRow(3).should('have.text', '2000'); @@ -175,14 +181,15 @@ describe('NDV', () => { ndv.actions.switchOutputBranch('False Branch (2 items)'); ndv.getters.outputTableRow(1).should('have.text', '8888'); - ndv.getters.outputTableRow(1).realHover(); + ndv.actions.dragMainPanelToLeft(); + ndv.getters.outputTableRow(1).realMouseMove(10, 1); ndv.getters.inputTableRow(5).should('have.text', '8888'); ndv.getters.inputTableRow(5).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); ndv.getters.outputTableRow(2).should('have.text', '9999'); - ndv.getters.outputTableRow(2).realHover(); + ndv.getters.outputTableRow(2).realMouseMove(10, 1); ndv.getters.inputTableRow(6).should('have.text', '9999'); @@ -192,29 +199,35 @@ describe('NDV', () => { workflowPage.actions.openNode('Set5'); + ndv.actions.dragMainPanelToRight(); ndv.actions.switchInputBranch('True Branch'); + + ndv.actions.dragMainPanelToLeft(); ndv.actions.changeOutputRunSelector('1 of 2 (2 items)'); ndv.getters.outputTableRow(1).should('have.text', '8888'); - ndv.getters.outputTableRow(1).realHover(); - cy.wait(100); + ndv.getters.outputTableRow(1).realMouseMove(10, 1); ndv.getters.inputHoveringItem().should('not.exist'); ndv.getters.inputTableRow(1).should('have.text', '1111'); - ndv.getters.inputTableRow(1).realHover(); - cy.wait(100); + + ndv.actions.dragMainPanelToRight(); + ndv.getters.inputTableRow(1).realMouseMove(10, 1); ndv.getters.outputHoveringItem().should('not.exist'); ndv.actions.switchInputBranch('False Branch'); ndv.getters.inputTableRow(1).should('have.text', '8888'); - ndv.getters.inputTableRow(1).realHover(); + ndv.actions.dragMainPanelToRight(); + ndv.getters.inputTableRow(1).realMouseMove(10, 1); + ndv.actions.dragMainPanelToLeft(); ndv.actions.changeOutputRunSelector('2 of 2 (4 items)'); ndv.getters.outputTableRow(1).should('have.text', '1111'); - ndv.getters.outputTableRow(1).realHover(); + ndv.getters.outputTableRow(1).realMouseMove(10, 1); ndv.actions.changeOutputRunSelector('1 of 2 (2 items)'); ndv.getters.inputTableRow(1).should('have.text', '8888'); - ndv.getters.inputTableRow(1).realHover(); + ndv.actions.dragMainPanelToRight(); + ndv.getters.inputTableRow(1).realMouseMove(10, 1); ndv.getters.outputHoveringItem().should('have.text', '8888'); // todo there's a bug here need to fix ADO-534 // ndv.getters.outputHoveringItem().should('not.exist'); diff --git a/cypress/e2e/47-subworkflow-debugging.cy.ts b/cypress/e2e/47-subworkflow-debugging.cy.ts new file mode 100644 index 00000000000..f808bdd044e --- /dev/null +++ b/cypress/e2e/47-subworkflow-debugging.cy.ts @@ -0,0 +1,140 @@ +import { + getExecutionPreviewOutputPanelRelatedExecutionLink, + getExecutionsSidebar, + getWorkflowExecutionPreviewIframe, + openExecutionPreviewNode, +} from '../composables/executions'; +import { + changeOutputRunSelector, + getOutputPanelItemsCount, + getOutputPanelRelatedExecutionLink, + getOutputRunSelectorInput, + getOutputTableHeaders, + getOutputTableRows, + getOutputTbodyCell, +} from '../composables/ndv'; +import { + clickExecuteWorkflowButton, + clickZoomToFit, + getCanvasNodes, + navigateToNewWorkflowPage, + openNode, + pasteWorkflow, + saveWorkflowOnButtonClick, +} from '../composables/workflow'; +import SUBWORKFLOW_DEBUGGING_EXAMPLE from '../fixtures/Subworkflow-debugging-execute-workflow.json'; + +describe('Subworkflow debugging', () => { + beforeEach(() => { + navigateToNewWorkflowPage(); + pasteWorkflow(SUBWORKFLOW_DEBUGGING_EXAMPLE); + saveWorkflowOnButtonClick(); + getCanvasNodes().should('have.length', 11); + clickZoomToFit(); + + clickExecuteWorkflowButton(); + }); + + describe('can inspect sub executed workflow', () => { + it('(Run once with all items/ Wait for Sub-workflow completion) (default behavior)', () => { + openNode('Execute Workflow with param'); + + getOutputPanelItemsCount().should('contain.text', '2 items, 1 sub-execution'); + getOutputPanelRelatedExecutionLink().should('contain.text', 'Inspect Sub-Execution'); + getOutputPanelRelatedExecutionLink().should('have.attr', 'href'); + + // ensure workflow executed and waited on output + getOutputTableHeaders().should('have.length', 2); + getOutputTbodyCell(1, 0).should('have.text', 'world Natalie Moore'); + }); + + it('(Run once for each item/ Wait for Sub-workflow completion)', () => { + openNode('Execute Workflow with param1'); + + getOutputPanelItemsCount().should('contain.text', '2 items, 2 sub-execution'); + getOutputPanelRelatedExecutionLink().should('not.exist'); + + // ensure workflow executed and waited on output + getOutputTableHeaders().should('have.length', 3); + getOutputTbodyCell(1, 0).find('a').should('have.attr', 'href'); + getOutputTbodyCell(1, 1).should('have.text', 'world Natalie Moore'); + }); + + it('(Run once with all items/ Wait for Sub-workflow completion)', () => { + openNode('Execute Workflow with param2'); + + getOutputPanelItemsCount().should('not.exist'); + getOutputPanelRelatedExecutionLink().should('contain.text', 'Inspect Sub-Execution'); + getOutputPanelRelatedExecutionLink().should('have.attr', 'href'); + + // ensure workflow executed but returned same data as input + getOutputRunSelectorInput().should('have.value', '2 of 2 (3 items, 1 sub-execution)'); + getOutputTableHeaders().should('have.length', 6); + getOutputTableHeaders().eq(0).should('have.text', 'uid'); + getOutputTableRows().should('have.length', 4); + getOutputTbodyCell(1, 1).should('include.text', 'Jon_Ebert@yahoo.com'); + + changeOutputRunSelector('1 of 2 (2 items, 1 sub-execution)'); + getOutputRunSelectorInput().should('have.value', '1 of 2 (2 items, 1 sub-execution)'); + getOutputTableHeaders().should('have.length', 6); + getOutputTableHeaders().eq(0).should('have.text', 'uid'); + getOutputTableRows().should('have.length', 3); + getOutputTbodyCell(1, 1).should('include.text', 'Terry.Dach@hotmail.com'); + }); + + it('(Run once for each item/ Wait for Sub-workflow completion)', () => { + openNode('Execute Workflow with param3'); + + // ensure workflow executed but returned same data as input + getOutputRunSelectorInput().should('have.value', '2 of 2 (3 items, 3 sub-executions)'); + getOutputTableHeaders().should('have.length', 7); + getOutputTableHeaders().eq(1).should('have.text', 'uid'); + getOutputTableRows().should('have.length', 4); + getOutputTbodyCell(1, 0).find('a').should('have.attr', 'href'); + getOutputTbodyCell(1, 2).should('include.text', 'Jon_Ebert@yahoo.com'); + + changeOutputRunSelector('1 of 2 (2 items, 2 sub-executions)'); + getOutputRunSelectorInput().should('have.value', '1 of 2 (2 items, 2 sub-executions)'); + getOutputTableHeaders().should('have.length', 7); + getOutputTableHeaders().eq(1).should('have.text', 'uid'); + getOutputTableRows().should('have.length', 3); + + getOutputTbodyCell(1, 0).find('a').should('have.attr', 'href'); + getOutputTbodyCell(1, 2).should('include.text', 'Terry.Dach@hotmail.com'); + }); + }); + + it('can inspect parent executions', () => { + cy.url().then((workflowUrl) => { + openNode('Execute Workflow with param'); + + getOutputPanelItemsCount().should('contain.text', '2 items, 1 sub-execution'); + getOutputPanelRelatedExecutionLink().should('contain.text', 'Inspect Sub-Execution'); + getOutputPanelRelatedExecutionLink().should('have.attr', 'href'); + + // ensure workflow executed and waited on output + getOutputTableHeaders().should('have.length', 2); + getOutputTbodyCell(1, 0).should('have.text', 'world Natalie Moore'); + + // cypress cannot handle new tabs so removing it + getOutputPanelRelatedExecutionLink().invoke('removeAttr', 'target').click(); + + getExecutionsSidebar().should('be.visible'); + getWorkflowExecutionPreviewIframe().should('be.visible'); + openExecutionPreviewNode('Execute Workflow Trigger'); + + getExecutionPreviewOutputPanelRelatedExecutionLink().should( + 'include.text', + 'Inspect Parent Execution', + ); + + getExecutionPreviewOutputPanelRelatedExecutionLink() + .invoke('removeAttr', 'target') + .click({ force: true }); + + cy.url().then((currentUrl) => { + expect(currentUrl === workflowUrl); + }); + }); + }); +}); diff --git a/cypress/fixtures/Subworkflow-debugging-execute-workflow.json b/cypress/fixtures/Subworkflow-debugging-execute-workflow.json new file mode 100644 index 00000000000..c336a80b419 --- /dev/null +++ b/cypress/fixtures/Subworkflow-debugging-execute-workflow.json @@ -0,0 +1,354 @@ +{ + "meta": { + "instanceId": "08ce71ad998aeaade0abedb8dd96153d8eaa03fcb84cfccc1530095bf9ee478e" + }, + "nodes": [ + { + "parameters": {}, + "id": "4535ce3e-280e-49b0-8854-373472ec86d1", + "name": "When clicking ‘Test workflow’", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [80, 860] + }, + { + "parameters": { + "category": "randomData", + "randomDataSeed": "0", + "randomDataCount": 2 + }, + "id": "d7fba18a-d51f-4509-af45-68cd9425ac6b", + "name": "DebugHelper1", + "type": "n8n-nodes-base.debugHelper", + "typeVersion": 1, + "position": [280, 860] + }, + { + "parameters": { + "source": "parameter", + "workflowJson": "{\n \"meta\": {\n \"instanceId\": \"a786b722078489c1fa382391a9f3476c2784761624deb2dfb4634827256d51a0\"\n },\n \"nodes\": [\n {\n \"parameters\": {},\n \"type\": \"n8n-nodes-base.executeWorkflowTrigger\",\n \"typeVersion\": 1,\n \"position\": [\n 0,\n 0\n ],\n \"id\": \"00600a51-e63a-4b6e-93f5-f01d50a21e0c\",\n \"name\": \"Execute Workflow Trigger\"\n },\n {\n \"parameters\": {\n \"assignments\": {\n \"assignments\": [\n {\n \"id\": \"87ff01af-2e28-48da-ae6c-304040200b15\",\n \"name\": \"hello\",\n \"value\": \"=world {{ $json.firstname }} {{ $json.lastname }}\",\n \"type\": \"string\"\n }\n ]\n },\n \"includeOtherFields\": false,\n \"options\": {}\n },\n \"type\": \"n8n-nodes-base.set\",\n \"typeVersion\": 3.4,\n \"position\": [\n 280,\n 0\n ],\n \"id\": \"642219a1-d655-4a30-af5c-fcccbb690322\",\n \"name\": \"Edit Fields\"\n }\n ],\n \"connections\": {\n \"Execute Workflow Trigger\": {\n \"main\": [\n [\n {\n \"node\": \"Edit Fields\",\n \"type\": \"main\",\n \"index\": 0\n }\n ]\n ]\n }\n },\n \"pinData\": {}\n}", + "mode": "each", + "options": { + "waitForSubWorkflow": false + } + }, + "type": "n8n-nodes-base.executeWorkflow", + "typeVersion": 1.1, + "position": [680, 1540], + "id": "f90a25da-dd89-4bf8-8f5b-bf8ee1de0b70", + "name": "Execute Workflow with param3" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "c93f26bd-3489-467b-909e-6462e1463707", + "name": "uid", + "value": "={{ $json.uid }}", + "type": "string" + }, + { + "id": "3dd706ce-d925-4219-8531-ad12369972fe", + "name": "email", + "value": "={{ $json.email }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [900, 1540], + "id": "3be57648-3be8-4b0f-abfa-8fdcafee804d", + "name": "Edit Fields8" + }, + { + "parameters": { + "source": "parameter", + "workflowJson": "{\n \"meta\": {\n \"instanceId\": \"a786b722078489c1fa382391a9f3476c2784761624deb2dfb4634827256d51a0\"\n },\n \"nodes\": [\n {\n \"parameters\": {},\n \"type\": \"n8n-nodes-base.executeWorkflowTrigger\",\n \"typeVersion\": 1,\n \"position\": [\n 0,\n 0\n ],\n \"id\": \"00600a51-e63a-4b6e-93f5-f01d50a21e0c\",\n \"name\": \"Execute Workflow Trigger\"\n },\n {\n \"parameters\": {\n \"assignments\": {\n \"assignments\": [\n {\n \"id\": \"87ff01af-2e28-48da-ae6c-304040200b15\",\n \"name\": \"hello\",\n \"value\": \"=world {{ $json.firstname }} {{ $json.lastname }}\",\n \"type\": \"string\"\n }\n ]\n },\n \"includeOtherFields\": false,\n \"options\": {}\n },\n \"type\": \"n8n-nodes-base.set\",\n \"typeVersion\": 3.4,\n \"position\": [\n 280,\n 0\n ],\n \"id\": \"642219a1-d655-4a30-af5c-fcccbb690322\",\n \"name\": \"Edit Fields\"\n }\n ],\n \"connections\": {\n \"Execute Workflow Trigger\": {\n \"main\": [\n [\n {\n \"node\": \"Edit Fields\",\n \"type\": \"main\",\n \"index\": 0\n }\n ]\n ]\n }\n },\n \"pinData\": {}\n}", + "options": { + "waitForSubWorkflow": false + } + }, + "type": "n8n-nodes-base.executeWorkflow", + "typeVersion": 1.1, + "position": [620, 1220], + "id": "dabc2356-3660-4d17-b305-936a002029ba", + "name": "Execute Workflow with param2" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "c93f26bd-3489-467b-909e-6462e1463707", + "name": "uid", + "value": "={{ $json.uid }}", + "type": "string" + }, + { + "id": "3dd706ce-d925-4219-8531-ad12369972fe", + "name": "email", + "value": "={{ $json.email }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [840, 1220], + "id": "9d2a9dda-e2a1-43e8-a66f-a8a555692e5f", + "name": "Edit Fields7" + }, + { + "parameters": { + "source": "parameter", + "workflowJson": "{\n \"meta\": {\n \"instanceId\": \"a786b722078489c1fa382391a9f3476c2784761624deb2dfb4634827256d51a0\"\n },\n \"nodes\": [\n {\n \"parameters\": {},\n \"type\": \"n8n-nodes-base.executeWorkflowTrigger\",\n \"typeVersion\": 1,\n \"position\": [\n 0,\n 0\n ],\n \"id\": \"00600a51-e63a-4b6e-93f5-f01d50a21e0c\",\n \"name\": \"Execute Workflow Trigger\"\n },\n {\n \"parameters\": {\n \"assignments\": {\n \"assignments\": [\n {\n \"id\": \"87ff01af-2e28-48da-ae6c-304040200b15\",\n \"name\": \"hello\",\n \"value\": \"=world {{ $json.firstname }} {{ $json.lastname }}\",\n \"type\": \"string\"\n }\n ]\n },\n \"includeOtherFields\": false,\n \"options\": {}\n },\n \"type\": \"n8n-nodes-base.set\",\n \"typeVersion\": 3.4,\n \"position\": [\n 280,\n 0\n ],\n \"id\": \"642219a1-d655-4a30-af5c-fcccbb690322\",\n \"name\": \"Edit Fields\"\n }\n ],\n \"connections\": {\n \"Execute Workflow Trigger\": {\n \"main\": [\n [\n {\n \"node\": \"Edit Fields\",\n \"type\": \"main\",\n \"index\": 0\n }\n ]\n ]\n }\n },\n \"pinData\": {}\n}", + "mode": "each", + "options": { + "waitForSubWorkflow": true + } + }, + "type": "n8n-nodes-base.executeWorkflow", + "typeVersion": 1.1, + "position": [560, 900], + "id": "07e47f60-622a-484c-ab24-35f6f2280595", + "name": "Execute Workflow with param1" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "c93f26bd-3489-467b-909e-6462e1463707", + "name": "uid", + "value": "={{ $json.uid }}", + "type": "string" + }, + { + "id": "3dd706ce-d925-4219-8531-ad12369972fe", + "name": "email", + "value": "={{ $json.email }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [760, 900], + "id": "80563d0a-0bab-444f-a04c-4041a505d78b", + "name": "Edit Fields6" + }, + { + "parameters": { + "source": "parameter", + "workflowJson": "{\n \"meta\": {\n \"instanceId\": \"a786b722078489c1fa382391a9f3476c2784761624deb2dfb4634827256d51a0\"\n },\n \"nodes\": [\n {\n \"parameters\": {},\n \"type\": \"n8n-nodes-base.executeWorkflowTrigger\",\n \"typeVersion\": 1,\n \"position\": [\n 0,\n 0\n ],\n \"id\": \"00600a51-e63a-4b6e-93f5-f01d50a21e0c\",\n \"name\": \"Execute Workflow Trigger\"\n },\n {\n \"parameters\": {\n \"assignments\": {\n \"assignments\": [\n {\n \"id\": \"87ff01af-2e28-48da-ae6c-304040200b15\",\n \"name\": \"hello\",\n \"value\": \"=world {{ $json.firstname }} {{ $json.lastname }}\",\n \"type\": \"string\"\n }\n ]\n },\n \"includeOtherFields\": false,\n \"options\": {}\n },\n \"type\": \"n8n-nodes-base.set\",\n \"typeVersion\": 3.4,\n \"position\": [\n 280,\n 0\n ],\n \"id\": \"642219a1-d655-4a30-af5c-fcccbb690322\",\n \"name\": \"Edit Fields\"\n }\n ],\n \"connections\": {\n \"Execute Workflow Trigger\": {\n \"main\": [\n [\n {\n \"node\": \"Edit Fields\",\n \"type\": \"main\",\n \"index\": 0\n }\n ]\n ]\n }\n },\n \"pinData\": {}\n}", + "options": { + "waitForSubWorkflow": true + } + }, + "type": "n8n-nodes-base.executeWorkflow", + "typeVersion": 1.1, + "position": [560, 580], + "id": "f04af481-f4d9-4d91-a60a-a377580e8393", + "name": "Execute Workflow with param" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "c93f26bd-3489-467b-909e-6462e1463707", + "name": "uid", + "value": "={{ $json.uid }}", + "type": "string" + }, + { + "id": "3dd706ce-d925-4219-8531-ad12369972fe", + "name": "email", + "value": "={{ $json.email }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [760, 580], + "id": "80c10607-a0ac-4090-86a1-890da0a2aa52", + "name": "Edit Fields2" + }, + { + "parameters": { + "content": "## Execute Workflow (Run once with all items/ DONT Wait for Sub-workflow completion)", + "height": 254.84308966329985, + "width": 457.58120569815793 + }, + "id": "534ef523-3453-4a16-9ff0-8ac9f025d47d", + "name": "Sticky Note5", + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [500, 1080] + }, + { + "parameters": { + "content": "## Execute Workflow (Run once with for each item/ DONT Wait for Sub-workflow completion) ", + "height": 284.59778445962905, + "width": 457.58120569815793 + }, + "id": "838f0fa3-5ee4-4d1a-afb8-42e009f1aa9e", + "name": "Sticky Note4", + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [580, 1400] + }, + { + "parameters": { + "category": "randomData", + "randomDataSeed": "1", + "randomDataCount": 3 + }, + "id": "86699a49-2aa7-488e-8ea9-828404c98f08", + "name": "DebugHelper", + "type": "n8n-nodes-base.debugHelper", + "typeVersion": 1, + "position": [320, 1120] + }, + { + "parameters": { + "content": "## Execute Workflow (Run once with for each item/ Wait for Sub-workflow completion) ", + "height": 284.59778445962905, + "width": 457.58120569815793 + }, + "id": "885d35f0-8ae6-45ec-821b-a82c27e7577a", + "name": "Sticky Note3", + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [480, 760] + }, + { + "parameters": { + "content": "## Execute Workflow (Run once with all items/ Wait for Sub-workflow completion) (default behavior)", + "height": 254.84308966329985, + "width": 457.58120569815793 + }, + "id": "505bd7f2-767e-41b8-9325-77300aed5883", + "name": "Sticky Note2", + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [460, 460] + } + ], + "connections": { + "When clicking ‘Test workflow’": { + "main": [ + [ + { + "node": "DebugHelper1", + "type": "main", + "index": 0 + }, + { + "node": "DebugHelper", + "type": "main", + "index": 0 + } + ] + ] + }, + "DebugHelper1": { + "main": [ + [ + { + "node": "Execute Workflow with param3", + "type": "main", + "index": 0 + }, + { + "node": "Execute Workflow with param2", + "type": "main", + "index": 0 + }, + { + "node": "Execute Workflow with param1", + "type": "main", + "index": 0 + }, + { + "node": "Execute Workflow with param", + "type": "main", + "index": 0 + } + ] + ] + }, + "Execute Workflow with param3": { + "main": [ + [ + { + "node": "Edit Fields8", + "type": "main", + "index": 0 + } + ] + ] + }, + "Execute Workflow with param2": { + "main": [ + [ + { + "node": "Edit Fields7", + "type": "main", + "index": 0 + } + ] + ] + }, + "Execute Workflow with param1": { + "main": [ + [ + { + "node": "Edit Fields6", + "type": "main", + "index": 0 + } + ] + ] + }, + "Execute Workflow with param": { + "main": [ + [ + { + "node": "Edit Fields2", + "type": "main", + "index": 0 + } + ] + ] + }, + "DebugHelper": { + "main": [ + [ + { + "node": "Execute Workflow with param2", + "type": "main", + "index": 0 + }, + { + "node": "Execute Workflow with param3", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": {} +} diff --git a/cypress/pages/ndv.ts b/cypress/pages/ndv.ts index 4504552e262..516a0a1ea86 100644 --- a/cypress/pages/ndv.ts +++ b/cypress/pages/ndv.ts @@ -323,6 +323,12 @@ export class NDV extends BasePage { addItemToFixedCollection: (paramName: string) => { this.getters.fixedCollectionParameter(paramName).getByTestId('fixed-collection-add').click(); }, + dragMainPanelToLeft: () => { + cy.drag('[data-test-id=panel-drag-button]', [-1000, 0], { moveTwice: true }); + }, + dragMainPanelToRight: () => { + cy.drag('[data-test-id=panel-drag-button]', [1000, 0], { moveTwice: true }); + }, }; } diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 6cad68b34fd..bc5a18a34f6 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -177,6 +177,16 @@ Cypress.Commands.add('drag', (selector, pos, options) => { pageY: newPosition.y, force: true, }); + if (options?.moveTwice) { + // first move like hover to trigger object to be visible + // like in main panel in ndv + element.trigger('mousemove', { + which: 1, + pageX: newPosition.x, + pageY: newPosition.y, + force: true, + }); + } if (options?.clickToFinish) { // Click to finish the drag // For some reason, mouseup isn't working when moving nodes diff --git a/cypress/support/index.ts b/cypress/support/index.ts index 6be46315030..864e64f1c4d 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -59,7 +59,13 @@ declare global { drag( selector: string | Chainable>, target: [number, number], - options?: { abs?: boolean; index?: number; realMouse?: boolean; clickToFinish?: boolean }, + options?: { + abs?: boolean; + index?: number; + realMouse?: boolean; + clickToFinish?: boolean; + moveTwice?: boolean; + }, ): void; draganddrop( draggableSelector: string, diff --git a/packages/@n8n/nodes-langchain/nodes/retrievers/RetrieverWorkflow/RetrieverWorkflow.node.ts b/packages/@n8n/nodes-langchain/nodes/retrievers/RetrieverWorkflow/RetrieverWorkflow.node.ts index 0aafabf1d4c..bbee5539994 100644 --- a/packages/@n8n/nodes-langchain/nodes/retrievers/RetrieverWorkflow/RetrieverWorkflow.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/retrievers/RetrieverWorkflow/RetrieverWorkflow.node.ts @@ -10,6 +10,7 @@ import type { INodeTypeDescription, SupplyData, INodeParameterResourceLocator, + ExecuteWorkflowData, } from 'n8n-workflow'; import { BaseRetriever, type BaseRetrieverInput } from '@langchain/core/retrievers'; @@ -293,6 +294,8 @@ export class RetrieverWorkflow implements INodeType { }; async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise { + const workflowProxy = this.getWorkflowDataProxy(0); + class WorkflowRetriever extends BaseRetriever { lc_namespace = ['n8n-nodes-langchain', 'retrievers', 'workflow']; @@ -349,6 +352,9 @@ export class RetrieverWorkflow implements INodeType { }, ); } + + // same as current workflow + baseMetadata.workflowId = workflowProxy.$workflow.id; } const rawData: IDataObject = { query }; @@ -384,21 +390,29 @@ export class RetrieverWorkflow implements INodeType { const items = [newItem] as INodeExecutionData[]; - let receivedItems: INodeExecutionData[][]; + let receivedData: ExecuteWorkflowData; try { - receivedItems = (await this.executeFunctions.executeWorkflow( + receivedData = await this.executeFunctions.executeWorkflow( workflowInfo, items, config?.getChild(), - )) as INodeExecutionData[][]; + { + parentExecution: { + executionId: workflowProxy.$execution.id, + workflowId: workflowProxy.$workflow.id, + }, + }, + ); } catch (error) { // Make sure a valid error gets returned that can by json-serialized else it will // not show up in the frontend throw new NodeOperationError(this.executeFunctions.getNode(), error as Error); } + const receivedItems = receivedData.data?.[0] ?? []; + const returnData: Document[] = []; - for (const [index, itemData] of receivedItems[0].entries()) { + for (const [index, itemData] of receivedItems.entries()) { const pageContent = objectToString(itemData.json); returnData.push( new Document({ @@ -406,6 +420,7 @@ export class RetrieverWorkflow implements INodeType { metadata: { ...baseMetadata, itemIndex: index, + executionId: receivedData.executionId, }, }), ); diff --git a/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/ToolWorkflow.node.ts b/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/ToolWorkflow.node.ts index f912e162d9f..b76bcc8ec31 100644 --- a/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/ToolWorkflow.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/ToolWorkflow.node.ts @@ -14,8 +14,10 @@ import type { ISupplyDataFunctions, SupplyData, ExecutionError, + ExecuteWorkflowData, IDataObject, INodeParameterResourceLocator, + ITaskMetadata, } from 'n8n-workflow'; import { NodeConnectionType, NodeOperationError, jsonParse } from 'n8n-workflow'; @@ -358,9 +360,14 @@ export class ToolWorkflow implements INodeType { }; async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise { + const workflowProxy = this.getWorkflowDataProxy(0); + const name = this.getNodeParameter('name', itemIndex) as string; const description = this.getNodeParameter('description', itemIndex) as string; + let subExecutionId: string | undefined; + let subWorkflowId: string | undefined; + const useSchema = this.getNodeParameter('specifyInputSchema', itemIndex) as boolean; let tool: DynamicTool | DynamicStructuredTool | undefined = undefined; @@ -396,11 +403,16 @@ export class ToolWorkflow implements INodeType { ) as INodeParameterResourceLocator; workflowInfo.id = value as string; } + + subWorkflowId = workflowInfo.id; } else if (source === 'parameter') { // Read workflow from parameter const workflowJson = this.getNodeParameter('workflowJson', itemIndex) as string; try { workflowInfo.code = JSON.parse(workflowJson) as IWorkflowBase; + + // subworkflow is same as parent workflow + subWorkflowId = workflowProxy.$workflow.id; } catch (error) { throw new NodeOperationError( this.getNode(), @@ -440,13 +452,15 @@ export class ToolWorkflow implements INodeType { const items = [newItem] as INodeExecutionData[]; - let receivedData: INodeExecutionData; + let receivedData: ExecuteWorkflowData; try { - receivedData = (await this.executeWorkflow( - workflowInfo, - items, - runManager?.getChild(), - )) as INodeExecutionData; + receivedData = await this.executeWorkflow(workflowInfo, items, runManager?.getChild(), { + parentExecution: { + executionId: workflowProxy.$execution.id, + workflowId: workflowProxy.$workflow.id, + }, + }); + subExecutionId = receivedData.executionId; } catch (error) { // Make sure a valid error gets returned that can by json-serialized else it will // not show up in the frontend @@ -454,6 +468,7 @@ export class ToolWorkflow implements INodeType { } const response: string | undefined = get(receivedData, [ + 'data', 0, 0, 'json', @@ -503,10 +518,25 @@ export class ToolWorkflow implements INodeType { response = `There was an error: "${executionError.message}"`; } + let metadata: ITaskMetadata | undefined; + if (subExecutionId && subWorkflowId) { + metadata = { + subExecution: { + executionId: subExecutionId, + workflowId: subWorkflowId, + }, + }; + } + if (executionError) { - void this.addOutputData(NodeConnectionType.AiTool, index, executionError); + void this.addOutputData(NodeConnectionType.AiTool, index, executionError, metadata); } else { - void this.addOutputData(NodeConnectionType.AiTool, index, [[{ json: { response } }]]); + void this.addOutputData( + NodeConnectionType.AiTool, + index, + [[{ json: { response } }]], + metadata, + ); } return response; }; diff --git a/packages/@n8n/nodes-langchain/utils/logWrapper.ts b/packages/@n8n/nodes-langchain/utils/logWrapper.ts index 10e55ba6ef9..fa1a38b31a5 100644 --- a/packages/@n8n/nodes-langchain/utils/logWrapper.ts +++ b/packages/@n8n/nodes-langchain/utils/logWrapper.ts @@ -10,7 +10,12 @@ import type { Tool } from '@langchain/core/tools'; import { VectorStore } from '@langchain/core/vectorstores'; import { TextSplitter } from '@langchain/textsplitters'; import type { BaseDocumentLoader } from 'langchain/dist/document_loaders/base'; -import type { IExecuteFunctions, INodeExecutionData, ISupplyDataFunctions } from 'n8n-workflow'; +import type { + IExecuteFunctions, + INodeExecutionData, + ISupplyDataFunctions, + ITaskMetadata, +} from 'n8n-workflow'; import { NodeOperationError, NodeConnectionType } from 'n8n-workflow'; import { logAiEvent, isToolsInstance, isBaseChatMemory, isBaseChatMessageHistory } from './helpers'; @@ -220,8 +225,24 @@ export function logWrapper( arguments: [query, config], })) as Array>>; + const executionId: string | undefined = response[0]?.metadata?.executionId as string; + const workflowId: string | undefined = response[0]?.metadata?.workflowId as string; + + const metadata: ITaskMetadata = {}; + if (executionId && workflowId) { + metadata.subExecution = { + executionId, + workflowId, + }; + } + logAiEvent(executeFunctions, 'ai-documents-retrieved', { query }); - executeFunctions.addOutputData(connectionType, index, [[{ json: { response } }]]); + executeFunctions.addOutputData( + connectionType, + index, + [[{ json: { response } }]], + metadata, + ); return response; }; } diff --git a/packages/cli/package.json b/packages/cli/package.json index 3f2b0397ab2..db203199f7e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -25,6 +25,7 @@ "start:default": "cd bin && ./n8n", "start:windows": "cd bin && n8n", "test": "pnpm test:sqlite", + "test:dev": "N8N_LOG_LEVEL=silent DB_TYPE=sqlite jest --watch", "test:sqlite": "N8N_LOG_LEVEL=silent DB_TYPE=sqlite jest", "test:postgres": "N8N_LOG_LEVEL=silent DB_TYPE=postgresdb DB_POSTGRESDB_SCHEMA=alt_schema DB_TABLE_PREFIX=test_ jest --no-coverage", "test:mysql": "N8N_LOG_LEVEL=silent DB_TYPE=mysqldb DB_TABLE_PREFIX=test_ jest --no-coverage", diff --git a/packages/cli/src/__tests__/workflow-execute-additional-data.test.ts b/packages/cli/src/__tests__/workflow-execute-additional-data.test.ts index 88aee515406..d0aeb3111f6 100644 --- a/packages/cli/src/__tests__/workflow-execute-additional-data.test.ts +++ b/packages/cli/src/__tests__/workflow-execute-additional-data.test.ts @@ -1,9 +1,11 @@ import { mock } from 'jest-mock-extended'; -import type { - IExecuteWorkflowInfo, - IWorkflowExecuteAdditionalData, - ExecuteWorkflowOptions, - IRun, +import type { IWorkflowBase } from 'n8n-workflow'; +import { + type IExecuteWorkflowInfo, + type IWorkflowExecuteAdditionalData, + type ExecuteWorkflowOptions, + type IRun, + type INodeExecutionData, } from 'n8n-workflow'; import type PCancelable from 'p-cancelable'; import Container from 'typedi'; @@ -21,43 +23,59 @@ import { WorkflowStatisticsService } from '@/services/workflow-statistics.servic import { SubworkflowPolicyChecker } from '@/subworkflows/subworkflow-policy-checker.service'; import { Telemetry } from '@/telemetry'; import { PermissionChecker } from '@/user-management/permission-checker'; -import { executeWorkflow, getBase } from '@/workflow-execute-additional-data'; +import { executeWorkflow, getBase, getRunData } from '@/workflow-execute-additional-data'; import { mockInstance } from '@test/mocking'; -const run = mock({ - data: { resultData: {} }, - finished: true, - mode: 'manual', - startedAt: new Date(), - status: 'new', -}); +const EXECUTION_ID = '123'; +const LAST_NODE_EXECUTED = 'Last node executed'; -const cancelablePromise = mock>({ - then: jest - .fn() - .mockImplementation(async (onfulfilled) => await Promise.resolve(run).then(onfulfilled)), - catch: jest - .fn() - .mockImplementation(async (onrejected) => await Promise.resolve(run).catch(onrejected)), - finally: jest - .fn() - .mockImplementation(async (onfinally) => await Promise.resolve(run).finally(onfinally)), - [Symbol.toStringTag]: 'PCancelable', -}); +const getMockRun = ({ lastNodeOutput }: { lastNodeOutput: Array }) => + mock({ + data: { + resultData: { + runData: { + [LAST_NODE_EXECUTED]: [ + { + startTime: 100, + data: { + main: lastNodeOutput, + }, + }, + ], + }, + lastNodeExecuted: LAST_NODE_EXECUTED, + }, + }, + finished: true, + mode: 'manual', + startedAt: new Date(), + status: 'new', + }); + +const getCancelablePromise = async (run: IRun) => + await mock>({ + then: jest + .fn() + .mockImplementation(async (onfulfilled) => await Promise.resolve(run).then(onfulfilled)), + catch: jest + .fn() + .mockImplementation(async (onrejected) => await Promise.resolve(run).catch(onrejected)), + finally: jest + .fn() + .mockImplementation(async (onfinally) => await Promise.resolve(run).finally(onfinally)), + [Symbol.toStringTag]: 'PCancelable', + }); + +const processRunExecutionData = jest.fn(); jest.mock('n8n-core', () => ({ __esModule: true, ...jest.requireActual('n8n-core'), WorkflowExecute: jest.fn().mockImplementation(() => ({ - processRunExecutionData: jest.fn().mockReturnValue(cancelablePromise), + processRunExecutionData, })), })); -jest.mock('../workflow-helpers', () => ({ - ...jest.requireActual('../workflow-helpers'), - getDataLastExecutedNodeData: jest.fn().mockReturnValue({ data: { main: [] } }), -})); - describe('WorkflowExecuteAdditionalData', () => { const variablesService = mockInstance(VariablesService); variablesService.getAllCached.mockResolvedValue([]); @@ -95,17 +113,129 @@ describe('WorkflowExecuteAdditionalData', () => { expect(eventService.emit).toHaveBeenCalledWith(eventName, payload); }); - it('`executeWorkflow` should set subworkflow execution as running', async () => { - const executionId = '123'; - workflowRepository.get.mockResolvedValue(mock({ id: executionId, nodes: [] })); - activeExecutions.add.mockResolvedValue(executionId); + describe('executeWorkflow', () => { + const runWithData = getMockRun({ lastNodeOutput: [[{ json: { test: 1 } }]] }); - await executeWorkflow( - mock(), - mock(), - mock({ loadedWorkflowData: undefined }), - ); + beforeEach(() => { + workflowRepository.get.mockResolvedValue( + mock({ id: EXECUTION_ID, nodes: [] }), + ); + activeExecutions.add.mockResolvedValue(EXECUTION_ID); + processRunExecutionData.mockReturnValue(getCancelablePromise(runWithData)); + }); - expect(executionRepository.setRunning).toHaveBeenCalledWith(executionId); + it('should execute workflow, return data and execution id', async () => { + const response = await executeWorkflow( + mock(), + mock(), + mock({ loadedWorkflowData: undefined, doNotWaitToFinish: false }), + ); + + expect(response).toEqual({ + data: runWithData.data.resultData.runData[LAST_NODE_EXECUTED][0].data!.main, + executionId: EXECUTION_ID, + }); + }); + + it('should execute workflow, skip waiting', async () => { + const response = await executeWorkflow( + mock(), + mock(), + mock({ loadedWorkflowData: undefined, doNotWaitToFinish: true }), + ); + + expect(response).toEqual({ + data: [null], + executionId: EXECUTION_ID, + }); + }); + + it('should set sub workflow execution as running', async () => { + await executeWorkflow( + mock(), + mock(), + mock({ loadedWorkflowData: undefined }), + ); + + expect(executionRepository.setRunning).toHaveBeenCalledWith(EXECUTION_ID); + }); + }); + + describe('getRunData', () => { + it('should throw error to add trigger ndoe', async () => { + const workflow = mock({ + id: '1', + name: 'test', + nodes: [], + active: false, + }); + await expect(getRunData(workflow)).rejects.toThrowError('Missing node to start execution'); + }); + + const workflow = mock({ + id: '1', + name: 'test', + nodes: [ + { + type: 'n8n-nodes-base.executeWorkflowTrigger', + }, + ], + active: false, + }); + + it('should return default data', async () => { + expect(await getRunData(workflow)).toEqual({ + executionData: { + executionData: { + contextData: {}, + metadata: {}, + nodeExecutionStack: [ + { + data: { main: [[{ json: {} }]] }, + metadata: { parentExecution: undefined }, + node: workflow.nodes[0], + source: null, + }, + ], + waitingExecution: {}, + waitingExecutionSource: {}, + }, + resultData: { runData: {} }, + startData: {}, + }, + executionMode: 'integrated', + workflowData: workflow, + }); + }); + + it('should return run data with input data and metadata', async () => { + const data = [{ json: { test: 1 } }]; + const parentExecution = { + executionId: '123', + workflowId: '567', + }; + expect(await getRunData(workflow, data, parentExecution)).toEqual({ + executionData: { + executionData: { + contextData: {}, + metadata: {}, + nodeExecutionStack: [ + { + data: { main: [data] }, + metadata: { parentExecution }, + node: workflow.nodes[0], + source: null, + }, + ], + waitingExecution: {}, + waitingExecutionSource: {}, + }, + resultData: { runData: {} }, + startData: {}, + }, + executionMode: 'integrated', + workflowData: workflow, + }); + }); }); }); diff --git a/packages/cli/src/interfaces.ts b/packages/cli/src/interfaces.ts index c79f3d67e57..e50b96c3845 100644 --- a/packages/cli/src/interfaces.ts +++ b/packages/cli/src/interfaces.ts @@ -1,6 +1,5 @@ import type { Scope } from '@n8n/permissions'; import type { Application } from 'express'; -import type { WorkflowExecute } from 'n8n-core'; import type { ExecutionError, ICredentialDataDecryptedObject, @@ -14,7 +13,6 @@ import type { ITelemetryTrackProperties, IWorkflowBase, CredentialLoadingDetails, - Workflow, WorkflowExecuteMode, ExecutionStatus, ExecutionSummary, @@ -300,12 +298,6 @@ export interface IWorkflowErrorData { }; } -export interface IWorkflowExecuteProcess { - startedAt: Date; - workflow: Workflow; - workflowExecute: WorkflowExecute; -} - export interface IWorkflowStatisticsDataLoaded { dataLoaded: boolean; } diff --git a/packages/cli/src/workflow-execute-additional-data.ts b/packages/cli/src/workflow-execute-additional-data.ts index 78c21b44c07..08d6ba09e41 100644 --- a/packages/cli/src/workflow-execute-additional-data.ts +++ b/packages/cli/src/workflow-execute-additional-data.ts @@ -36,6 +36,8 @@ import type { ExecuteWorkflowOptions, IWorkflowExecutionDataProcess, EnvProviderState, + ExecuteWorkflowData, + RelatedExecution, } from 'n8n-workflow'; import { Container } from 'typedi'; @@ -45,11 +47,7 @@ import { CredentialsHelper } from '@/credentials-helper'; import { ExecutionRepository } from '@/databases/repositories/execution.repository'; import type { AiEventMap, AiEventPayload } from '@/events/maps/ai.event-map'; import { ExternalHooks } from '@/external-hooks'; -import type { - IWorkflowExecuteProcess, - IWorkflowErrorData, - UpdateExecutionPayload, -} from '@/interfaces'; +import type { IWorkflowErrorData, UpdateExecutionPayload } from '@/interfaces'; import { NodeTypes } from '@/node-types'; import { Push } from '@/push'; import { WorkflowStatisticsService } from '@/services/workflow-statistics.service'; @@ -650,6 +648,7 @@ function hookFunctionsSaveWorker(): IWorkflowExecuteHooks { export async function getRunData( workflowData: IWorkflowBase, inputData?: INodeExecutionData[], + parentExecution?: RelatedExecution, ): Promise { const mode = 'integrated'; @@ -669,6 +668,7 @@ export async function getRunData( data: { main: [inputData], }, + metadata: { parentExecution }, source: null, }); @@ -740,7 +740,41 @@ export async function executeWorkflow( workflowInfo: IExecuteWorkflowInfo, additionalData: IWorkflowExecuteAdditionalData, options: ExecuteWorkflowOptions, -): Promise | IWorkflowExecuteProcess> { +): Promise { + const activeExecutions = Container.get(ActiveExecutions); + + const workflowData = + options.loadedWorkflowData ?? + (await getWorkflowData(workflowInfo, options.parentWorkflowId, options.parentWorkflowSettings)); + + const runData = + options.loadedRunData ?? + (await getRunData(workflowData, options.inputData, options.parentExecution)); + + const executionId = await activeExecutions.add(runData); + + const executionPromise = startExecution( + additionalData, + options, + executionId, + runData, + workflowData, + ); + + if (options.doNotWaitToFinish) { + return { executionId, data: [null] }; + } + + return await executionPromise; +} + +async function startExecution( + additionalData: IWorkflowExecuteAdditionalData, + options: ExecuteWorkflowOptions, + executionId: string, + runData: IWorkflowExecutionDataProcess, + workflowData: IWorkflowBase, +): Promise { const externalHooks = Container.get(ExternalHooks); await externalHooks.init(); @@ -749,10 +783,6 @@ export async function executeWorkflow( const eventService = Container.get(EventService); const executionRepository = Container.get(ExecutionRepository); - const workflowData = - options.loadedWorkflowData ?? - (await getWorkflowData(workflowInfo, options.parentWorkflowId, options.parentWorkflowSettings)); - const workflowName = workflowData ? workflowData.name : undefined; const workflow = new Workflow({ id: workflowData.id, @@ -765,10 +795,6 @@ export async function executeWorkflow( settings: workflowData.settings, }); - const runData = options.loadedRunData ?? (await getRunData(workflowData, options.inputData)); - - const executionId = await activeExecutions.add(runData); - /** * A subworkflow execution in queue mode is not enqueued, but rather runs in the * same worker process as the parent execution. Hence ensure the subworkflow @@ -890,7 +916,10 @@ export async function executeWorkflow( activeExecutions.finalizeExecution(executionId, data); const returnData = WorkflowHelpers.getDataLastExecutedNodeData(data); - return returnData!.data!.main; + return { + executionId, + data: returnData!.data!.main, + }; } activeExecutions.finalizeExecution(executionId, data); diff --git a/packages/core/package.json b/packages/core/package.json index 5ad1ac4d5d2..446569e1c68 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -20,7 +20,8 @@ "lint": "eslint . --quiet", "lintfix": "eslint . --fix", "watch": "tsc-watch -p tsconfig.build.json --onCompilationComplete \"tsc-alias -p tsconfig.build.json\"", - "test": "jest" + "test": "jest", + "test:dev": "jest --watch" }, "files": [ "dist", diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index b3701c1ca38..29f60b89794 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -39,6 +39,7 @@ import type { BinaryHelperFunctions, CloseFunction, ContextType, + ExecuteWorkflowData, FieldType, FileSystemHelperFunctions, FunctionsBase, @@ -78,6 +79,7 @@ import type { IRunExecutionData, ITaskData, ITaskDataConnections, + ITaskMetadata, ITriggerFunctions, IWebhookData, IWebhookDescription, @@ -109,6 +111,7 @@ import type { ISupplyDataFunctions, WebhookType, SchedulingFunctions, + RelatedExecution, } from 'n8n-workflow'; import { NodeConnectionType, @@ -2721,6 +2724,7 @@ const addExecutionDataFunctions = async ( sourceNodeName: string, sourceNodeRunIndex: number, currentNodeRunIndex: number, + metadata?: ITaskMetadata, ): Promise => { if (connectionType === NodeConnectionType.Main) { throw new ApplicationError('Setting type is not supported for main connection', { @@ -2746,6 +2750,7 @@ const addExecutionDataFunctions = async ( if (taskData === undefined) { return; } + taskData.metadata = metadata; } taskData = taskData!; @@ -3622,6 +3627,12 @@ export function getExecuteFunctions( itemIndex, ), getExecuteData: () => executeData, + setMetadata: (metadata: ITaskMetadata): void => { + executeData.metadata = { + ...(executeData.metadata ?? {}), + ...metadata, + }; + }, continueOnFail: () => { return continueOnFail(node); }, @@ -3643,23 +3654,28 @@ export function getExecuteFunctions( workflowInfo: IExecuteWorkflowInfo, inputData?: INodeExecutionData[], parentCallbackManager?: CallbackManager, - ): Promise { + options?: { + doNotWaitToFinish?: boolean; + parentExecution?: RelatedExecution; + }, + ): Promise { return await additionalData .executeWorkflow(workflowInfo, additionalData, { + ...options, parentWorkflowId: workflow.id?.toString(), inputData, parentWorkflowSettings: workflow.settings, node, parentCallbackManager, }) - .then( - async (result) => - await Container.get(BinaryDataService).duplicateBinaryData( - workflow.id, - additionalData.executionId!, - result, - ), - ); + .then(async (result) => { + const data = await Container.get(BinaryDataService).duplicateBinaryData( + workflow.id, + additionalData.executionId!, + result.data, + ); + return { ...result, data }; + }); }, getContext(type: ContextType): IContextObject { return NodeHelpers.getContext(runExecutionData, type, node); @@ -3853,6 +3869,7 @@ export function getExecuteFunctions( connectionType: NodeConnectionType, currentNodeRunIndex: number, data: INodeExecutionData[][] | ExecutionBaseError, + metadata?: ITaskMetadata, ): void { addExecutionDataFunctions( 'output', @@ -3864,6 +3881,7 @@ export function getExecuteFunctions( node.name, runIndex, currentNodeRunIndex, + metadata, ).catch((error) => { Logger.warn( `There was a problem logging output data of node "${this.getNode().name}": ${ @@ -3972,7 +3990,11 @@ export function getSupplyDataFunctions( workflowInfo: IExecuteWorkflowInfo, inputData?: INodeExecutionData[], parentCallbackManager?: CallbackManager, - ) => + options?: { + doNotWaitToFinish?: boolean; + parentExecution?: RelatedExecution; + }, + ): Promise => await additionalData .executeWorkflow(workflowInfo, additionalData, { parentWorkflowId: workflow.id?.toString(), @@ -3980,15 +4002,16 @@ export function getSupplyDataFunctions( parentWorkflowSettings: workflow.settings, node, parentCallbackManager, + ...options, }) - .then( - async (result) => - await Container.get(BinaryDataService).duplicateBinaryData( - workflow.id, - additionalData.executionId!, - result, - ), - ), + .then(async (result) => { + const data = await Container.get(BinaryDataService).duplicateBinaryData( + workflow.id, + additionalData.executionId!, + result.data, + ); + return { ...result, data }; + }), getNodeOutputs() { const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion); return NodeHelpers.getNodeOutputs(workflow, node, nodeType.description).map((output) => { @@ -4143,6 +4166,7 @@ export function getSupplyDataFunctions( connectionType: NodeConnectionType, currentNodeRunIndex: number, data: INodeExecutionData[][], + metadata?: ITaskMetadata, ): void { addExecutionDataFunctions( 'output', @@ -4154,6 +4178,7 @@ export function getSupplyDataFunctions( node.name, runIndex, currentNodeRunIndex, + metadata, ).catch((error) => { Logger.warn( `There was a problem logging output data of node "${this.getNode().name}": ${ diff --git a/packages/core/src/WorkflowExecute.ts b/packages/core/src/WorkflowExecute.ts index bb1b1633df2..2ae12908aca 100644 --- a/packages/core/src/WorkflowExecute.ts +++ b/packages/core/src/WorkflowExecute.ts @@ -408,7 +408,10 @@ export class WorkflowExecute { let metaRunData: ITaskMetadata; for (const nodeName of Object.keys(metadata)) { for ([index, metaRunData] of metadata[nodeName].entries()) { - runData[nodeName][index].metadata = metaRunData; + runData[nodeName][index].metadata = { + ...(runData[nodeName][index].metadata ?? {}), + ...metaRunData, + }; } } } @@ -1448,6 +1451,7 @@ export class WorkflowExecute { startTime, executionTime: new Date().getTime() - startTime, source: !executionData.source ? [] : executionData.source.main, + metadata: executionData.metadata, executionStatus: 'success', }; diff --git a/packages/core/src/node-execution-context/__tests__/execute-single-context.test.ts b/packages/core/src/node-execution-context/__tests__/execute-single-context.test.ts index dcd8509c1d6..8a2fc72be36 100644 --- a/packages/core/src/node-execution-context/__tests__/execute-single-context.test.ts +++ b/packages/core/src/node-execution-context/__tests__/execute-single-context.test.ts @@ -17,6 +17,7 @@ import type { IContextObject, ICredentialDataDecryptedObject, ISourceData, + ITaskMetadata, } from 'n8n-workflow'; import { ApplicationError, NodeHelpers } from 'n8n-workflow'; @@ -298,4 +299,33 @@ describe('ExecuteSingleContext', () => { }); }); }); + + describe('setMetadata', () => { + it('sets metadata on execution data', () => { + const context = new ExecuteSingleContext( + workflow, + node, + additionalData, + mode, + runExecutionData, + runIndex, + connectionInputData, + inputData, + itemIndex, + executeData, + abortSignal, + ); + + const metadata: ITaskMetadata = { + subExecution: { + workflowId: '123', + executionId: 'xyz', + }, + }; + + expect(context.getExecuteData().metadata?.subExecution).toEqual(undefined); + context.setMetadata(metadata); + expect(context.getExecuteData().metadata?.subExecution).toEqual(metadata.subExecution); + }); + }); }); diff --git a/packages/core/src/node-execution-context/execute-single-context.ts b/packages/core/src/node-execution-context/execute-single-context.ts index 2b03a81974d..dd08aa0fc66 100644 --- a/packages/core/src/node-execution-context/execute-single-context.ts +++ b/packages/core/src/node-execution-context/execute-single-context.ts @@ -13,6 +13,7 @@ import type { ContextType, AiEvent, ISourceData, + ITaskMetadata, } from 'n8n-workflow'; import { ApplicationError, @@ -85,6 +86,13 @@ export class ExecuteSingleContext extends NodeExecutionContext implements IExecu this.abortSignal?.addEventListener('abort', fn); } + setMetadata(metadata: ITaskMetadata): void { + this.executeData.metadata = { + ...(this.executeData.metadata ?? {}), + ...metadata, + }; + } + continueOnFail() { return continueOnFail(this.node); } diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index 4bf5f02e84a..29d7d78e047 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -182,6 +182,10 @@ export interface IAiDataContent { metadata: { executionTime: number; startTime: number; + subExecution?: { + workflowId: string; + executionId: string; + }; }; } @@ -202,6 +206,10 @@ export interface ITableData { columns: string[]; data: GenericValue[][]; hasJson: { [key: string]: boolean }; + metadata: { + hasExecutionIds: boolean; + data: Array; + }; } // Simple version of n8n-workflow.Workflow diff --git a/packages/editor-ui/src/components/PanelDragButton.vue b/packages/editor-ui/src/components/PanelDragButton.vue index 929d5f9b184..c203e9e3352 100644 --- a/packages/editor-ui/src/components/PanelDragButton.vue +++ b/packages/editor-ui/src/components/PanelDragButton.vue @@ -35,7 +35,7 @@ const onDragStart = () => { @dragend="onDragEnd" > From f4f0b5110c2c87878b322db5d1cce848ef7baeb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Fri, 15 Nov 2024 10:28:21 +0100 Subject: [PATCH 08/18] refactor(core): Remove deprecated `TIME` constant (#11740) --- packages/cli/src/constants.ts | 21 ++++--------------- .../cli/src/scaling/multi-main-setup.ee.ts | 4 ++-- .../cli/src/services/cache/cache.service.ts | 4 ++-- .../workflows/workflow-history/constants.ts | 3 --- .../workflow-history-manager.ee.ts | 4 ++-- .../test/integration/pruning.service.test.ts | 4 ++-- 6 files changed, 12 insertions(+), 28 deletions(-) delete mode 100644 packages/cli/src/workflows/workflow-history/constants.ts diff --git a/packages/cli/src/constants.ts b/packages/cli/src/constants.ts index be26616fb64..df52a36b961 100644 --- a/packages/cli/src/constants.ts +++ b/packages/cli/src/constants.ts @@ -110,25 +110,12 @@ export const UM_FIX_INSTRUCTION = 'Please fix the database by running ./packages/cli/bin/n8n user-management:reset'; /** - * Units of time in milliseconds - * @deprecated Please use constants.Time instead. - */ -export const TIME = { - SECOND: 1000, - MINUTE: 60 * 1000, - HOUR: 60 * 60 * 1000, - DAY: 24 * 60 * 60 * 1000, -} as const; - -/** - * Convert time from any unit to any other unit - * - * Please amend conversions as necessary. - * Eventually this will superseed `TIME` above + * Convert time from any time unit to any other unit */ export const Time = { milliseconds: { toMinutes: 1 / (60 * 1000), + toSeconds: 1 / 1000, }, seconds: { toMilliseconds: 1000, @@ -150,9 +137,9 @@ export const MIN_PASSWORD_CHAR_LENGTH = 8; export const MAX_PASSWORD_CHAR_LENGTH = 64; -export const TEST_WEBHOOK_TIMEOUT = 2 * TIME.MINUTE; +export const TEST_WEBHOOK_TIMEOUT = 2 * Time.minutes.toMilliseconds; -export const TEST_WEBHOOK_TIMEOUT_BUFFER = 30 * TIME.SECOND; +export const TEST_WEBHOOK_TIMEOUT_BUFFER = 30 * Time.seconds.toMilliseconds; export const GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE = [ 'oAuth2Api', diff --git a/packages/cli/src/scaling/multi-main-setup.ee.ts b/packages/cli/src/scaling/multi-main-setup.ee.ts index 8be7f4ae51c..dab9f17cc6d 100644 --- a/packages/cli/src/scaling/multi-main-setup.ee.ts +++ b/packages/cli/src/scaling/multi-main-setup.ee.ts @@ -3,7 +3,7 @@ import { InstanceSettings } from 'n8n-core'; import { Service } from 'typedi'; import config from '@/config'; -import { TIME } from '@/constants'; +import { Time } from '@/constants'; import { Logger } from '@/logging/logger.service'; import { Publisher } from '@/scaling/pubsub/publisher.service'; import { RedisClientService } from '@/services/redis-client.service'; @@ -54,7 +54,7 @@ export class MultiMainSetup extends TypedEmitter { this.leaderCheckInterval = setInterval(async () => { await this.checkLeader(); - }, this.globalConfig.multiMainSetup.interval * TIME.SECOND); + }, this.globalConfig.multiMainSetup.interval * Time.seconds.toMilliseconds); } async shutdown() { diff --git a/packages/cli/src/services/cache/cache.service.ts b/packages/cli/src/services/cache/cache.service.ts index aefe9310fc2..f82bac3d02e 100644 --- a/packages/cli/src/services/cache/cache.service.ts +++ b/packages/cli/src/services/cache/cache.service.ts @@ -4,7 +4,7 @@ import { ApplicationError, jsonStringify } from 'n8n-workflow'; import Container, { Service } from 'typedi'; import config from '@/config'; -import { TIME } from '@/constants'; +import { Time } from '@/constants'; import { MalformedRefreshValueError } from '@/errors/cache-errors/malformed-refresh-value.error'; import { UncacheableValueError } from '@/errors/cache-errors/uncacheable-value.error'; import type { @@ -160,7 +160,7 @@ export class CacheService extends TypedEmitter { }); } - await this.cache.store.expire(key, ttlMs / TIME.SECOND); + await this.cache.store.expire(key, ttlMs * Time.milliseconds.toSeconds); } // ---------------------------------- diff --git a/packages/cli/src/workflows/workflow-history/constants.ts b/packages/cli/src/workflows/workflow-history/constants.ts deleted file mode 100644 index dc4f7c78678..00000000000 --- a/packages/cli/src/workflows/workflow-history/constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { TIME } from '@/constants'; - -export const WORKFLOW_HISTORY_PRUNE_INTERVAL = 1 * TIME.HOUR; diff --git a/packages/cli/src/workflows/workflow-history/workflow-history-manager.ee.ts b/packages/cli/src/workflows/workflow-history/workflow-history-manager.ee.ts index e710637441b..f3a25bfb16f 100644 --- a/packages/cli/src/workflows/workflow-history/workflow-history-manager.ee.ts +++ b/packages/cli/src/workflows/workflow-history/workflow-history-manager.ee.ts @@ -1,9 +1,9 @@ import { DateTime } from 'luxon'; import { Service } from 'typedi'; +import { Time } from '@/constants'; import { WorkflowHistoryRepository } from '@/databases/repositories/workflow-history.repository'; -import { WORKFLOW_HISTORY_PRUNE_INTERVAL } from './constants'; import { getWorkflowHistoryPruneTime, isWorkflowHistoryEnabled, @@ -20,7 +20,7 @@ export class WorkflowHistoryManager { clearInterval(this.pruneTimer); } - this.pruneTimer = setInterval(async () => await this.prune(), WORKFLOW_HISTORY_PRUNE_INTERVAL); + this.pruneTimer = setInterval(async () => await this.prune(), 1 * Time.hours.toMilliseconds); } shutdown() { diff --git a/packages/cli/test/integration/pruning.service.test.ts b/packages/cli/test/integration/pruning.service.test.ts index 4ea8455b944..b4300c77a5a 100644 --- a/packages/cli/test/integration/pruning.service.test.ts +++ b/packages/cli/test/integration/pruning.service.test.ts @@ -4,7 +4,7 @@ import { BinaryDataService, InstanceSettings } from 'n8n-core'; import type { ExecutionStatus } from 'n8n-workflow'; import Container from 'typedi'; -import { TIME } from '@/constants'; +import { Time } from '@/constants'; import type { ExecutionEntity } from '@/databases/entities/execution-entity'; import type { WorkflowEntity } from '@/databases/entities/workflow-entity'; import { ExecutionRepository } from '@/databases/repositories/execution.repository'; @@ -25,7 +25,7 @@ describe('softDeleteOnPruningCycle()', () => { instanceSettings.markAsLeader(); const now = new Date(); - const yesterday = new Date(Date.now() - TIME.DAY); + const yesterday = new Date(Date.now() - 1 * Time.days.toMilliseconds); let workflow: WorkflowEntity; let pruningConfig: PruningConfig; From f1ca8c128ba6ca561792d78adb6cf1717b942433 Mon Sep 17 00:00:00 2001 From: Tomi Turtiainen <10324676+tomi@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:35:02 +0200 Subject: [PATCH 09/18] feat(core): Add sentry for task runner (no-changelog) (#11683) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Iván Ovejero --- docker/images/n8n/n8n-task-runners.json | 6 +- packages/@n8n/task-runner/package.json | 2 + .../src/__tests__/error-reporter.test.ts | 31 +++++++ .../task-runner/src/config/main-config.ts | 4 + .../task-runner/src/config/sentry-config.ts | 21 +++++ .../@n8n/task-runner/src/error-reporter.ts | 93 +++++++++++++++++++ .../__tests__/js-task-runner.test.ts | 6 ++ packages/@n8n/task-runner/src/start.ts | 14 +++ packages/cli/package.json | 4 +- .../__tests__/task-runner-process.test.ts | 35 ++++--- .../cli/src/runners/task-runner-process.ts | 5 + pnpm-lock.yaml | 62 +++++++------ pnpm-workspace.yaml | 2 + 13 files changed, 238 insertions(+), 47 deletions(-) create mode 100644 packages/@n8n/task-runner/src/__tests__/error-reporter.test.ts create mode 100644 packages/@n8n/task-runner/src/config/sentry-config.ts create mode 100644 packages/@n8n/task-runner/src/error-reporter.ts diff --git a/docker/images/n8n/n8n-task-runners.json b/docker/images/n8n/n8n-task-runners.json index 699794d5041..9eab58d91b3 100644 --- a/docker/images/n8n/n8n-task-runners.json +++ b/docker/images/n8n/n8n-task-runners.json @@ -13,7 +13,11 @@ "N8N_RUNNERS_MAX_CONCURRENCY", "NODE_FUNCTION_ALLOW_BUILTIN", "NODE_FUNCTION_ALLOW_EXTERNAL", - "NODE_OPTIONS" + "NODE_OPTIONS", + "N8N_SENTRY_DSN", + "N8N_VERSION", + "ENVIRONMENT", + "DEPLOYMENT_NAME" ], "uid": 2000, "gid": 2000 diff --git a/packages/@n8n/task-runner/package.json b/packages/@n8n/task-runner/package.json index b595fefe6e4..dd9ee6ae176 100644 --- a/packages/@n8n/task-runner/package.json +++ b/packages/@n8n/task-runner/package.json @@ -35,6 +35,8 @@ }, "dependencies": { "@n8n/config": "workspace:*", + "@sentry/integrations": "catalog:", + "@sentry/node": "catalog:", "acorn": "8.14.0", "acorn-walk": "8.3.4", "n8n-core": "workspace:*", diff --git a/packages/@n8n/task-runner/src/__tests__/error-reporter.test.ts b/packages/@n8n/task-runner/src/__tests__/error-reporter.test.ts new file mode 100644 index 00000000000..9345819329b --- /dev/null +++ b/packages/@n8n/task-runner/src/__tests__/error-reporter.test.ts @@ -0,0 +1,31 @@ +import { mock } from 'jest-mock-extended'; +import { ApplicationError } from 'n8n-workflow'; + +import { ErrorReporter } from '../error-reporter'; + +describe('ErrorReporter', () => { + const errorReporting = new ErrorReporter(mock()); + + describe('beforeSend', () => { + it('should return null if originalException is an ApplicationError with level warning', () => { + const hint = { originalException: new ApplicationError('Test error', { level: 'warning' }) }; + expect(errorReporting.beforeSend(mock(), hint)).toBeNull(); + }); + + it('should return event if originalException is an ApplicationError with level error', () => { + const hint = { originalException: new ApplicationError('Test error', { level: 'error' }) }; + expect(errorReporting.beforeSend(mock(), hint)).not.toBeNull(); + }); + + it('should return null if originalException is an Error with a non-unique stack', () => { + const hint = { originalException: new Error('Test error') }; + errorReporting.beforeSend(mock(), hint); + expect(errorReporting.beforeSend(mock(), hint)).toBeNull(); + }); + + it('should return event if originalException is an Error with a unique stack', () => { + const hint = { originalException: new Error('Test error') }; + expect(errorReporting.beforeSend(mock(), hint)).not.toBeNull(); + }); + }); +}); diff --git a/packages/@n8n/task-runner/src/config/main-config.ts b/packages/@n8n/task-runner/src/config/main-config.ts index a290c0c3803..10b504f1d6f 100644 --- a/packages/@n8n/task-runner/src/config/main-config.ts +++ b/packages/@n8n/task-runner/src/config/main-config.ts @@ -2,6 +2,7 @@ import { Config, Nested } from '@n8n/config'; import { BaseRunnerConfig } from './base-runner-config'; import { JsRunnerConfig } from './js-runner-config'; +import { SentryConfig } from './sentry-config'; @Config export class MainConfig { @@ -10,4 +11,7 @@ export class MainConfig { @Nested jsRunnerConfig!: JsRunnerConfig; + + @Nested + sentryConfig!: SentryConfig; } diff --git a/packages/@n8n/task-runner/src/config/sentry-config.ts b/packages/@n8n/task-runner/src/config/sentry-config.ts new file mode 100644 index 00000000000..691f64244f0 --- /dev/null +++ b/packages/@n8n/task-runner/src/config/sentry-config.ts @@ -0,0 +1,21 @@ +import { Config, Env } from '@n8n/config'; + +@Config +export class SentryConfig { + /** Sentry DSN */ + @Env('N8N_SENTRY_DSN') + sentryDsn: string = ''; + + //#region Metadata about the environment + + @Env('N8N_VERSION') + n8nVersion: string = ''; + + @Env('ENVIRONMENT') + environment: string = ''; + + @Env('DEPLOYMENT_NAME') + deploymentName: string = ''; + + //#endregion +} diff --git a/packages/@n8n/task-runner/src/error-reporter.ts b/packages/@n8n/task-runner/src/error-reporter.ts new file mode 100644 index 00000000000..167cc37c924 --- /dev/null +++ b/packages/@n8n/task-runner/src/error-reporter.ts @@ -0,0 +1,93 @@ +import { RewriteFrames } from '@sentry/integrations'; +import { init, setTag, captureException, close } from '@sentry/node'; +import type { ErrorEvent, EventHint } from '@sentry/types'; +import * as a from 'assert/strict'; +import { createHash } from 'crypto'; +import { ApplicationError } from 'n8n-workflow'; + +import type { SentryConfig } from '@/config/sentry-config'; + +/** + * Handles error reporting using Sentry + */ +export class ErrorReporter { + private isInitialized = false; + + /** Hashes of error stack traces, to deduplicate error reports. */ + private readonly seenErrors = new Set(); + + private get dsn() { + return this.sentryConfig.sentryDsn; + } + + constructor(private readonly sentryConfig: SentryConfig) { + a.ok(this.dsn, 'Sentry DSN is required to initialize Sentry'); + } + + async start() { + if (this.isInitialized) return; + + // Collect longer stacktraces + Error.stackTraceLimit = 50; + + process.on('uncaughtException', captureException); + + const ENABLED_INTEGRATIONS = [ + 'InboundFilters', + 'FunctionToString', + 'LinkedErrors', + 'OnUnhandledRejection', + 'ContextLines', + ]; + + setTag('server_type', 'task_runner'); + + init({ + dsn: this.dsn, + release: this.sentryConfig.n8nVersion, + environment: this.sentryConfig.environment, + enableTracing: false, + serverName: this.sentryConfig.deploymentName, + beforeBreadcrumb: () => null, + beforeSend: async (event, hint) => await this.beforeSend(event, hint), + integrations: (integrations) => [ + ...integrations.filter(({ name }) => ENABLED_INTEGRATIONS.includes(name)), + new RewriteFrames({ root: process.cwd() }), + ], + }); + + this.isInitialized = true; + } + + async stop() { + if (!this.isInitialized) { + return; + } + + await close(1000); + } + + async beforeSend(event: ErrorEvent, { originalException }: EventHint) { + if (!originalException) return null; + + if (originalException instanceof Promise) { + originalException = await originalException.catch((error) => error as Error); + } + + if (originalException instanceof ApplicationError) { + const { level, extra, tags } = originalException; + if (level === 'warning') return null; + event.level = level; + if (extra) event.extra = { ...event.extra, ...extra }; + if (tags) event.tags = { ...event.tags, ...tags }; + } + + if (originalException instanceof Error && originalException.stack) { + const eventHash = createHash('sha1').update(originalException.stack).digest('base64'); + if (this.seenErrors.has(eventHash)) return null; + this.seenErrors.add(eventHash); + } + + return event; + } +} diff --git a/packages/@n8n/task-runner/src/js-task-runner/__tests__/js-task-runner.test.ts b/packages/@n8n/task-runner/src/js-task-runner/__tests__/js-task-runner.test.ts index 621a9c81a7f..cd966ef8ac4 100644 --- a/packages/@n8n/task-runner/src/js-task-runner/__tests__/js-task-runner.test.ts +++ b/packages/@n8n/task-runner/src/js-task-runner/__tests__/js-task-runner.test.ts @@ -36,6 +36,12 @@ describe('JsTaskRunner', () => { ...defaultConfig.jsRunnerConfig, ...opts, }, + sentryConfig: { + sentryDsn: '', + deploymentName: '', + environment: '', + n8nVersion: '', + }, }); const defaultTaskRunner = createRunnerWithOpts(); diff --git a/packages/@n8n/task-runner/src/start.ts b/packages/@n8n/task-runner/src/start.ts index fcaab84d51d..c6e8cb314cd 100644 --- a/packages/@n8n/task-runner/src/start.ts +++ b/packages/@n8n/task-runner/src/start.ts @@ -2,10 +2,12 @@ import { ensureError } from 'n8n-workflow'; import Container from 'typedi'; import { MainConfig } from './config/main-config'; +import type { ErrorReporter } from './error-reporter'; import { JsTaskRunner } from './js-task-runner/js-task-runner'; let runner: JsTaskRunner | undefined; let isShuttingDown = false; +let errorReporter: ErrorReporter | undefined; function createSignalHandler(signal: string) { return async function onSignal() { @@ -21,10 +23,16 @@ function createSignalHandler(signal: string) { await runner.stop(); runner = undefined; } + + if (errorReporter) { + await errorReporter.stop(); + errorReporter = undefined; + } } catch (e) { const error = ensureError(e); console.error('Error stopping task runner', { error }); } finally { + console.log('Task runner stopped'); process.exit(0); } }; @@ -33,6 +41,12 @@ function createSignalHandler(signal: string) { void (async function start() { const config = Container.get(MainConfig); + if (config.sentryConfig.sentryDsn) { + const { ErrorReporter } = await import('@/error-reporter'); + errorReporter = new ErrorReporter(config.sentryConfig); + await errorReporter.start(); + } + runner = new JsTaskRunner(config); process.on('SIGINT', createSignalHandler('SIGINT')); diff --git a/packages/cli/package.json b/packages/cli/package.json index db203199f7e..1dcbbebbd12 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -98,8 +98,8 @@ "@n8n_io/license-sdk": "2.13.1", "@oclif/core": "4.0.7", "@rudderstack/rudder-sdk-node": "2.0.9", - "@sentry/integrations": "7.87.0", - "@sentry/node": "7.87.0", + "@sentry/integrations": "catalog:", + "@sentry/node": "catalog:", "aws4": "1.11.0", "axios": "catalog:", "bcryptjs": "2.4.3", diff --git a/packages/cli/src/runners/__tests__/task-runner-process.test.ts b/packages/cli/src/runners/__tests__/task-runner-process.test.ts index fbab9ee1e3f..92e8483d03e 100644 --- a/packages/cli/src/runners/__tests__/task-runner-process.test.ts +++ b/packages/cli/src/runners/__tests__/task-runner-process.test.ts @@ -46,23 +46,28 @@ describe('TaskRunnerProcess', () => { taskRunnerProcess = new TaskRunnerProcess(logger, runnerConfig, authService); }); - test.each(['PATH', 'NODE_FUNCTION_ALLOW_BUILTIN', 'NODE_FUNCTION_ALLOW_EXTERNAL'])( - 'should propagate %s from env as is', - async (envVar) => { - jest.spyOn(authService, 'createGrantToken').mockResolvedValue('grantToken'); - process.env[envVar] = 'custom value'; + test.each([ + 'PATH', + 'NODE_FUNCTION_ALLOW_BUILTIN', + 'NODE_FUNCTION_ALLOW_EXTERNAL', + 'N8N_SENTRY_DSN', + 'N8N_VERSION', + 'ENVIRONMENT', + 'DEPLOYMENT_NAME', + ])('should propagate %s from env as is', async (envVar) => { + jest.spyOn(authService, 'createGrantToken').mockResolvedValue('grantToken'); + process.env[envVar] = 'custom value'; - await taskRunnerProcess.start(); + await taskRunnerProcess.start(); - // @ts-expect-error The type is not correct - const options = spawnMock.mock.calls[0][2] as SpawnOptions; - expect(options.env).toEqual( - expect.objectContaining({ - [envVar]: 'custom value', - }), - ); - }, - ); + // @ts-expect-error The type is not correct + const options = spawnMock.mock.calls[0][2] as SpawnOptions; + expect(options.env).toEqual( + expect.objectContaining({ + [envVar]: 'custom value', + }), + ); + }); it('should pass NODE_OPTIONS env if maxOldSpaceSize is configured', async () => { jest.spyOn(authService, 'createGrantToken').mockResolvedValue('grantToken'); diff --git a/packages/cli/src/runners/task-runner-process.ts b/packages/cli/src/runners/task-runner-process.ts index 9e731a99c5d..ba63cbe9e76 100644 --- a/packages/cli/src/runners/task-runner-process.ts +++ b/packages/cli/src/runners/task-runner-process.ts @@ -59,6 +59,11 @@ export class TaskRunnerProcess extends TypedEmitter { 'PATH', 'NODE_FUNCTION_ALLOW_BUILTIN', 'NODE_FUNCTION_ALLOW_EXTERNAL', + 'N8N_SENTRY_DSN', + // Metadata about the environment + 'N8N_VERSION', + 'ENVIRONMENT', + 'DEPLOYMENT_NAME', ] as const; constructor( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1a43366f16a..e0568d9dc3e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,6 +9,12 @@ catalogs: '@langchain/core': specifier: 0.3.15 version: 0.3.15 + '@sentry/integrations': + specifier: 7.87.0 + version: 7.87.0 + '@sentry/node': + specifier: 7.87.0 + version: 7.87.0 '@types/basic-auth': specifier: ^1.1.3 version: 1.1.3 @@ -271,7 +277,7 @@ importers: version: 4.0.7 axios: specifier: 'catalog:' - version: 1.7.4 + version: 1.7.4(debug@4.3.7) dotenv: specifier: 8.6.0 version: 8.6.0 @@ -339,7 +345,7 @@ importers: dependencies: axios: specifier: 'catalog:' - version: 1.7.4 + version: 1.7.4(debug@4.3.7) packages/@n8n/codemirror-lang: dependencies: @@ -413,7 +419,7 @@ importers: version: 3.666.0(@aws-sdk/client-sts@3.666.0) '@getzep/zep-cloud': specifier: 1.0.12 - version: 1.0.12(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.5(7umjwzmwnymi4lyinuvazmp6ki)) + version: 1.0.12(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.5(4ubssgvn2k3t3hxnzmxuoc2aja)) '@getzep/zep-js': specifier: 0.9.0 version: 0.9.0 @@ -440,7 +446,7 @@ importers: version: 0.3.1(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0))(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13) '@langchain/community': specifier: 0.3.11 - version: 0.3.11(simkpjwqw7qnwbripe37u5qu7a) + version: 0.3.11(tzffvezibmkr4px5bpuitcp7xu) '@langchain/core': specifier: 'catalog:' version: 0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)) @@ -527,7 +533,7 @@ importers: version: 23.0.1 langchain: specifier: 0.3.5 - version: 0.3.5(7umjwzmwnymi4lyinuvazmp6ki) + version: 0.3.5(4ubssgvn2k3t3hxnzmxuoc2aja) lodash: specifier: 'catalog:' version: 4.17.21 @@ -648,6 +654,12 @@ importers: '@n8n/config': specifier: workspace:* version: link:../config + '@sentry/integrations': + specifier: 'catalog:' + version: 7.87.0 + '@sentry/node': + specifier: 'catalog:' + version: 7.87.0 acorn: specifier: 8.14.0 version: 8.14.0 @@ -770,17 +782,17 @@ importers: specifier: 2.0.9 version: 2.0.9(tslib@2.6.2) '@sentry/integrations': - specifier: 7.87.0 + specifier: 'catalog:' version: 7.87.0 '@sentry/node': - specifier: 7.87.0 + specifier: 'catalog:' version: 7.87.0 aws4: specifier: 1.11.0 version: 1.11.0 axios: specifier: 'catalog:' - version: 1.7.4 + version: 1.7.4(debug@4.3.7) bcryptjs: specifier: 2.4.3 version: 2.4.3 @@ -1111,7 +1123,7 @@ importers: version: 1.11.0 axios: specifier: 'catalog:' - version: 1.7.4 + version: 1.7.4(debug@4.3.7) concat-stream: specifier: 2.0.0 version: 2.0.0 @@ -1401,7 +1413,7 @@ importers: version: 10.11.0(vue@3.5.11(typescript@5.6.2)) axios: specifier: 'catalog:' - version: 1.7.4 + version: 1.7.4(debug@4.3.7) bowser: specifier: 2.11.0 version: 2.11.0 @@ -1881,7 +1893,7 @@ importers: version: 0.15.2 axios: specifier: 'catalog:' - version: 1.7.4 + version: 1.7.4(debug@4.3.7) callsites: specifier: 3.1.0 version: 3.1.0 @@ -14082,7 +14094,7 @@ snapshots: '@gar/promisify@1.1.3': optional: true - '@getzep/zep-cloud@1.0.12(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.5(7umjwzmwnymi4lyinuvazmp6ki))': + '@getzep/zep-cloud@1.0.12(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.5(4ubssgvn2k3t3hxnzmxuoc2aja))': dependencies: form-data: 4.0.0 node-fetch: 2.7.0(encoding@0.1.13) @@ -14091,7 +14103,7 @@ snapshots: zod: 3.23.8 optionalDependencies: '@langchain/core': 0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)) - langchain: 0.3.5(7umjwzmwnymi4lyinuvazmp6ki) + langchain: 0.3.5(4ubssgvn2k3t3hxnzmxuoc2aja) transitivePeerDependencies: - encoding @@ -14558,7 +14570,7 @@ snapshots: - aws-crt - encoding - '@langchain/community@0.3.11(simkpjwqw7qnwbripe37u5qu7a)': + '@langchain/community@0.3.11(tzffvezibmkr4px5bpuitcp7xu)': dependencies: '@ibm-cloud/watsonx-ai': 1.1.2 '@langchain/core': 0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)) @@ -14568,7 +14580,7 @@ snapshots: flat: 5.0.2 ibm-cloud-sdk-core: 5.1.0 js-yaml: 4.1.0 - langchain: 0.3.5(7umjwzmwnymi4lyinuvazmp6ki) + langchain: 0.3.5(4ubssgvn2k3t3hxnzmxuoc2aja) langsmith: 0.2.3(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)) uuid: 10.0.0 zod: 3.23.8 @@ -14581,7 +14593,7 @@ snapshots: '@aws-sdk/client-s3': 3.666.0 '@aws-sdk/credential-provider-node': 3.666.0(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0))(@aws-sdk/client-sts@3.666.0) '@azure/storage-blob': 12.18.0(encoding@0.1.13) - '@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.5(7umjwzmwnymi4lyinuvazmp6ki)) + '@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.5(4ubssgvn2k3t3hxnzmxuoc2aja)) '@getzep/zep-js': 0.9.0 '@google-ai/generativelanguage': 2.6.0(encoding@0.1.13) '@google-cloud/storage': 7.12.1(encoding@0.1.13) @@ -15294,7 +15306,7 @@ snapshots: '@rudderstack/rudder-sdk-node@2.0.9(tslib@2.6.2)': dependencies: - axios: 1.7.4 + axios: 1.7.4(debug@4.3.7) axios-retry: 3.7.0 component-type: 1.2.1 join-component: 1.1.0 @@ -17550,17 +17562,9 @@ snapshots: '@babel/runtime': 7.24.7 is-retry-allowed: 2.2.0 - axios@1.7.4: - dependencies: - follow-redirects: 1.15.6(debug@4.3.6) - form-data: 4.0.0 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - axios@1.7.4(debug@4.3.7): dependencies: - follow-redirects: 1.15.6(debug@4.3.7) + follow-redirects: 1.15.6(debug@4.3.6) form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -19915,7 +19919,7 @@ snapshots: gaxios@6.6.0(encoding@0.1.13): dependencies: extend: 3.0.2 - https-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.5 is-stream: 2.0.1 node-fetch: 2.7.0(encoding@0.1.13) uuid: 9.0.1 @@ -21396,7 +21400,7 @@ snapshots: kuler@2.0.0: {} - langchain@0.3.5(7umjwzmwnymi4lyinuvazmp6ki): + langchain@0.3.5(4ubssgvn2k3t3hxnzmxuoc2aja): dependencies: '@langchain/core': 0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)) '@langchain/openai': 0.3.11(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13) @@ -21420,7 +21424,7 @@ snapshots: '@langchain/groq': 0.1.2(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13) '@langchain/mistralai': 0.1.1(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13) '@langchain/ollama': 0.1.1(@langchain/core@0.3.15(openai@4.69.0(encoding@0.1.13)(zod@3.23.8))) - axios: 1.7.4 + axios: 1.7.4(debug@4.3.7) cheerio: 1.0.0 handlebars: 4.7.8 transitivePeerDependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 52e1230ca38..8b692007af8 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,6 +5,8 @@ packages: - cypress catalog: + '@sentry/integrations': 7.87.0 + '@sentry/node': 7.87.0 '@types/basic-auth': ^1.1.3 '@types/express': ^4.17.21 '@types/lodash': ^4.14.195 From bd924c7194aeaab5f15fe6d07e68ad28e73e44f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Fri, 15 Nov 2024 14:00:21 +0100 Subject: [PATCH 10/18] chore(editor): Stop reporting `ResponseError: Unauthorized` error to Sentry (no-changelog) (#11744) --- packages/editor-ui/src/plugins/sentry.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/editor-ui/src/plugins/sentry.ts b/packages/editor-ui/src/plugins/sentry.ts index c00826a0921..7347459fb51 100644 --- a/packages/editor-ui/src/plugins/sentry.ts +++ b/packages/editor-ui/src/plugins/sentry.ts @@ -7,6 +7,7 @@ const ignoredErrors = [ { instanceof: AxiosError }, { instanceof: ResponseError, message: /ECONNREFUSED/ }, { instanceof: ResponseError, message: "Can't connect to n8n." }, + { instanceof: ResponseError, message: 'Unauthorized' }, { instanceof: Error, message: /ResizeObserver/ }, ] as const; From 54e1f62535c0211ddf5055f2039337b4e262b860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Fri, 15 Nov 2024 18:26:35 +0100 Subject: [PATCH 11/18] ci: Fix flaky 34-template-credentials-setup.cy.ts (no-changelog) (#11763) --- .../e2e/34-template-credentials-setup.cy.ts | 6 +- ...erce_starter_pack_template_collection.json | 1556 ++++++++++++++++- cypress/fixtures/Test_Template_1.json | 2 +- .../sales_templates_search_response.json | 2 +- 4 files changed, 1560 insertions(+), 6 deletions(-) diff --git a/cypress/e2e/34-template-credentials-setup.cy.ts b/cypress/e2e/34-template-credentials-setup.cy.ts index 815f4b1cebf..386c83eb0ad 100644 --- a/cypress/e2e/34-template-credentials-setup.cy.ts +++ b/cypress/e2e/34-template-credentials-setup.cy.ts @@ -56,10 +56,10 @@ describe('Template credentials setup', () => { it('can be opened from template collection page', () => { visitTemplateCollectionPage(testData.ecommerceStarterPack); templateCredentialsSetupPage.enableTemplateCredentialSetupFeatureFlag(); - clickUseWorkflowButtonByTitle('Promote new Shopify products on Twitter and Telegram'); + clickUseWorkflowButtonByTitle('Promote new Shopify products'); templateCredentialsSetupPage.getters - .title("Set up 'Promote new Shopify products on Twitter and Telegram' template") + .title("Set up 'Promote new Shopify products' template") .should('be.visible'); }); @@ -67,7 +67,7 @@ describe('Template credentials setup', () => { templateCredentialsSetupPage.visitTemplateCredentialSetupPage(testTemplate.id); templateCredentialsSetupPage.getters - .title("Set up 'Promote new Shopify products on Twitter and Telegram' template") + .title("Set up 'Promote new Shopify products' template") .should('be.visible'); templateCredentialsSetupPage.getters diff --git a/cypress/fixtures/Ecommerce_starter_pack_template_collection.json b/cypress/fixtures/Ecommerce_starter_pack_template_collection.json index 1f908c587ba..d6235b358c9 100644 --- a/cypress/fixtures/Ecommerce_starter_pack_template_collection.json +++ b/cypress/fixtures/Ecommerce_starter_pack_template_collection.json @@ -1 +1,1555 @@ -{"collection":{"id":1,"name":"eCommerce Starter Pack","description":"eCommerce operations are complex — but there are many things that you can automate to make your life easier. This collection provides a few ideas to get started.\n\nReduce manual work and the risk of human error by automating processes such as social media promotion of products, updating customer databases, and get notifications for important events.","totalViews":0,"createdAt":"2022-02-17T12:40:50.498Z","nodes":[{"id":20,"name":"n8n-nodes-base.if","defaults":{"name":"IF","color":"#408000"},"displayName":"IF","icon":"fa:map-signs","iconData":{"icon":"map-signs","type":"icon"},"typeVersion":1,"categories":[{"id":9,"name":"Core Nodes"}]},{"id":49,"name":"n8n-nodes-base.telegram","defaults":{"name":"Telegram"},"displayName":"Telegram","icon":"file:telegram.svg","iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNjYgNjYiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48ZyBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZD0iTTAgMzJjMCAxNy42NzMgMTQuMzI3IDMyIDMyIDMyczMyLTE0LjMyNyAzMi0zMlM0OS42NzMgMCAzMiAwIDAgMTQuMzI3IDAgMzIiIGZpbGw9IiMzN2FlZTIiLz48cGF0aCBkPSJNMjEuNjYxIDM0LjMzOGwzLjc5NyAxMC41MDhzLjQ3NS45ODMuOTgzLjk4MyA4LjA2OC03Ljg2NCA4LjA2OC03Ljg2NGw4LjQwNy0xNi4yMzctMjEuMTE5IDkuODk4eiIgZmlsbD0iI2M4ZGFlYSIvPjxwYXRoIGQ9Ik0yNi42OTUgMzcuMDM0bC0uNzI5IDcuNzQ2cy0uMzA1IDIuMzczIDIuMDY4IDBsNC42NDQtNC4yMDMiIGZpbGw9IiNhOWM2ZDgiLz48cGF0aCBkPSJNMjEuNzMgMzQuNzEybC03LjgwOS0yLjU0NXMtLjkzMi0uMzc4LS42MzMtMS4yMzdjLjA2Mi0uMTc3LjE4Ni0uMzI4LjU1OS0uNTg4IDEuNzMxLTEuMjA2IDMyLjAyOC0xMi4wOTYgMzIuMDI4LTEyLjA5NnMuODU2LS4yODggMS4zNjEtLjA5N2MuMjMxLjA4OC4zNzguMTg3LjUwMy41NDguMDQ1LjEzMi4wNzEuNDExLjA2OC42ODktLjAwMy4yMDEtLjAyNy4zODYtLjA0NS42NzgtLjE4NCAyLjk3OC01LjcwNiAyNS4xOTgtNS43MDYgMjUuMTk4cy0uMzMgMS4zLTEuNTE0IDEuMzQ1Yy0uNDMyLjAxNi0uOTU2LS4wNzEtMS41ODItLjYxLTIuMzIzLTEuOTk4LTEwLjM1Mi03LjM5NC0xMi4xMjYtOC41OGEuMzQuMzQgMCAwMS0uMTQ2LS4yMzljLS4wMjUtLjEyNS4xMDgtLjI4LjEwOC0uMjhzMTMuOTgtMTIuNDI3IDE0LjM1Mi0xMy43MzFjLjAyOS0uMTAxLS4wNzktLjE1MS0uMjI2LS4xMDctLjkyOS4zNDItMTcuMDI1IDEwLjUwNi0xOC44MDEgMTEuNjI5LS4xMDQuMDY2LS4zOTUuMDIzLS4zOTUuMDIzIi8+PC9nPjwvc3ltYm9sPjwvc3ZnPg=="},"typeVersion":1,"categories":[{"id":6,"name":"Communication"}]},{"id":107,"name":"n8n-nodes-base.shopifyTrigger","defaults":{"name":"Shopify Trigger"},"displayName":"Shopify Trigger","icon":"file:shopify.svg","iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNTggNjYiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48ZyBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZD0iTTQ5LjI1NSAxMi40ODRhLjYzMy42MzMgMCAwMC0uNTY0LS41MjdjLS4yMjUtLjAzNy01LjE3LS4zNzYtNS4xNy0uMzc2bC0zLjc3LTMuNzdjLS4zNC0uMzc2LTEuMDkyLS4yNjYtMS4zNzYtLjE4OC0uMDM3IDAtLjc1Mi4yMjUtMS45MjIuNjA1LTEuMTM3LTMuMy0zLjE1LTYuMzA2LTYuNjk2LTYuMzA2aC0uMzAzQzI4LjQzOC42MDUgMjcuMTk0IDAgMjYuMTQ0IDBjLTguMjU2LjAzNy0xMi4yIDEwLjMzMy0xMy40MzQgMTUuNTk0bC01Ljc3IDEuNzdjLTEuNzcuNTY0LTEuODM1LjYwNS0yLjA3MyAyLjI5M0wwIDU3LjE3NSAzNi40NjggNjRsMTkuNzYzLTQuMjZjMC0uMDM3LTYuOTQtNDYuODk3LTYuOTc2LTQ3LjI1NXpNMzQuNDMxIDguODZjLS45MTcuMzAzLTEuOTYzLjYwNS0zLjEuOTQ1di0uNjhhMTUuMDMgMTUuMDMgMCAwMC0uNzUyLTQuOTk5YzEuODQ4LjI4NCAzLjEgMi4zNTcgMy44NDMgNC43MzN6bS02LjA2OC00LjI5OGMuNjAzIDEuNzc4Ljg4MyAzLjY1LjgyNiA1LjUyN3YuMzRsLTYuMzc1IDEuOTYzYzEuMjQ4LTQuNjYgMy41NS02Ljk2MiA1LjU1LTcuODN6bS0yLjQ1LTIuMjkzYTEuOTQgMS45NCAwIDAxMS4wNTUuMzM5Yy0yLjY2IDEuMjM4LTUuNDcyIDQuMzY2LTYuNjc4IDEwLjYyN2wtNS4wNDUgMS41NDZDMTYuNjY4IDEwLjAzIDE5Ljk4OCAyLjI2IDI1LjkxIDIuMjZ6IiBmaWxsPSIjOTViZjQ3Ii8+PHBhdGggZD0iTTQ4LjY5MSAxMS45NTdjLS4yMjUtLjAzNy01LjE3LS4zNzYtNS4xNy0uMzc2bC0zLjc3LTMuNzdhLjc1My43NTMgMCAwMC0uNTI3LS4yMjVMMzYuNDcyIDY0bDE5Ljc2My00LjI2LTYuOTgtNDcuMjE4YS42OC42OCAwIDAwLS41NjQtLjU2NHoiIGZpbGw9IiM1ZThlM2UiLz48cGF0aCBkPSJNMjkuNzU4IDIyLjlsLTIuNDU0IDcuMjQyYTExLjM2IDExLjM2IDAgMDAtNC43NTItMS4xMzNjLTMuODQ4IDAtNC4wMzYgMi40MTItNC4wMzYgMy4wMTggMCAzLjI5OCA4LjYzNiA0LjU2NCA4LjYzNiAxMi4zMzMgMCA2LjEtMy44ODUgMTAuMDMtOS4xIDEwLjAzLTYuMjYgMC05LjQ2Ny0zLjg4NS05LjQ2Ny0zLjg4NWwxLjY2NS01LjU1czMuMjggMi44MyA2LjA3MyAyLjgzYTIuNDcgMi40NyAwIDAwMi41NjQtMi40OWMwLTQuMzQtNy4xLTQuNTI3LTcuMS0xMS42MTggMC01Ljk2MiA0LjI5OC0xMS43NyAxMi45MzQtMTEuNzcgMy4zOTQuMDUgNS4wMTggMSA1LjAxOCAxeiIvPjwvZz48L3N5bWJvbD48L3N2Zz4="},"typeVersion":1,"categories":[{"id":2,"name":"Sales"}]},{"id":126,"name":"n8n-nodes-base.mautic","defaults":{"name":"Mautic"},"displayName":"Mautic","icon":"file:mautic.svg","iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNi4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iMzQ5Ljc3OXB4IiBoZWlnaHQ9IjM0OS43NzlweCIgdmlld0JveD0iMCAwIDM0OS43NzkgMzQ5Ljc3OSIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMzQ5Ljc3OSAzNDkuNzc5Ig0KCSB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxwYXRoIGZpbGw9IiNGRkZGRkYiIGQ9Ik0xNzQuODksMzQ5Ljc3OUM3OC42MTIsMzQ5Ljc3OSwwLDI3MS40NjIsMCwxNzQuODlTNzguNjEyLDAsMTc0Ljg5LDBjMjMuMjYsMCw0NS45MzEsNC40MTcsNjcuMTI5LDEzLjU0Mw0KCWM1Ljg4OSwyLjY1LDguODMzLDkuNDIyLDYuNDc4LDE1LjYwNWMtMi42NDksNS44ODgtOS40MjEsOC44MzMtMTUuNjA0LDYuNDc3Yy0xOC41NDktNy42NTUtMzcuOTgtMTEuNDgyLTU4LjAwMi0xMS40ODINCgljLTgzLjMyMywwLTE1MS4wNDEsNjcuNzE4LTE1MS4wNDEsMTUxLjA0MVM5MS41NjcsMzI2LjIyNSwxNzQuODksMzI2LjIyNWM4My4zMjMsMCwxNTEuMDQxLTY3LjcxOCwxNTEuMDQxLTE1MS4wNDENCgljMC0xNy45Ni0yLjk0NC0zNS4zMzItOS4xMjctNTEuODE5Yy0yLjM1NS02LjE4MywwLjg4My0xMi45NTUsNy4wNjYtMTUuMzFjNi4xODMtMi4zNTUsMTIuOTU0LDAuODgzLDE1LjMxLDcuMDY2DQoJYzcuMDY2LDE5LjEzOCwxMC42LDM5LjQ1MywxMC42LDYwLjA2M0MzNDkuNzc5LDI3MS4xNjcsMjcxLjQ2MiwzNDkuNzc5LDE3NC44OSwzNDkuNzc5Ii8+DQo8Zz4NCgk8cG9seWdvbiBmaWxsPSIjRkRCOTMzIiBwb2ludHM9IjI1MS40NCwxNTYuOTMgMjI0LjM1NCwxODUuMTk0IDIzOS4zNjksMjQ4LjQ5NiAyNzMuNTIyLDI0OC40OTYgCSIvPg0KPC9nPg0KPHBvbHlnb24gZmlsbD0iI0ZEQjkzMyIgcG9pbnRzPSIyNDAuMjUzLDczLjMxMiAyNDkuNjc0LDgyLjczNCAxNzQuODksMTYxLjkzNSAxMTAuOTk5LDk2LjI3NyA3NC4xOTYsMjQ4LjQ5NiAxMDguMzUsMjQ4LjQ5NiANCgkxMjguNjY1LDE2My45OTYgMTc0Ljg5LDIxNC4zNDMgMjczLjgxNywxMDYuNTgzIDI4My4yMzksMTE2LjI5OSAyOTIuNjYsNjMuMDA3ICIvPg0KPC9zdmc+DQo="},"typeVersion":1,"categories":[{"id":1,"name":"Marketing & Content"},{"id":6,"name":"Communication"}]},{"id":235,"name":"n8n-nodes-base.wooCommerceTrigger","defaults":{"name":"WooCommerce Trigger"},"displayName":"WooCommerce Trigger","icon":"file:wooCommerce.svg","iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIC01MCAyNTYgMjUzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KPHRpdGxlPldvb0NvbW1lcmNlIExvZ288L3RpdGxlPgo8bWV0YWRhdGE+CjxyZGY6UkRGPgo8Y2M6V29yayByZGY6YWJvdXQ9IiI+CjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0Pgo8ZGM6dHlwZSByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIi8+CjxkYzp0aXRsZS8+CjwvY2M6V29yaz4KPC9yZGY6UkRGPgo8L21ldGFkYXRhPgo8cGF0aCBkPSJtMjMuNzU5IDBoMjA4LjM4YzEzLjE4NyAwIDIzLjg2MyAxMC42NzUgMjMuODYzIDIzLjg2M3Y3OS41NDJjMCAxMy4xODctMTAuNjc1IDIzLjg2My0yMy44NjMgMjMuODYzaC03NC43MjdsMTAuMjU3IDI1LjExOC00NS4xMDktMjUuMTE4aC05OC42OTVjLTEzLjE4NyAwLTIzLjg2My0xMC42NzUtMjMuODYzLTIzLjg2M3YtNzkuNTQyYy0wLjEwNDY2LTEzLjA4MyAxMC41NzEtMjMuODYzIDIzLjc1OC0yMy44NjN6IiBmaWxsPSIjN2Y1NGIzIi8+CjxwYXRoIGQ9Im0xNC41NzggMjEuNzVjMS40NTY5LTEuOTc3MiAzLjY0MjMtMy4wMTc5IDYuNTU2MS0zLjIyNiA1LjMwNzMtMC40MTYyNiA4LjMyNTIgMi4wODEzIDkuMDUzNyA3LjQ5MjcgMy4yMjYgMjEuNzUgNi43NjQyIDQwLjE2OSAxMC41MTEgNTUuMjU5bDIyLjc5LTQzLjM5NWMyLjA4MTMtMy45NTQ1IDQuNjgyOS02LjAzNTggNy44MDQ5LTYuMjQzOSA0LjU3ODktMC4zMTIyIDcuMzg4NiAyLjYwMTYgOC41MzMzIDguNzQxNSAyLjYwMTYgMTMuODQxIDUuOTMxNyAyNS42IDkuODg2MiAzNS41OSAyLjcwNTctMjYuNDMzIDcuMjg0Ni00NS40NzYgMTMuNzM3LTU3LjIzNiAxLjU2MS0yLjkxMzggMy44NTA0LTQuMzcwNyA2Ljg2ODMtNC41Nzg5IDIuMzkzNS0wLjIwODEzIDQuNTc4OSAwLjUyMDMzIDYuNTU2MSAyLjA4MTMgMS45NzcyIDEuNTYxIDMuMDE3OSAzLjUzODIgMy4yMjYgNS45MzE3IDAuMTA0MDYgMS44NzMyLTAuMjA4MTMgMy40MzQxLTEuMDQwNyA0Ljk5NTEtNC4wNTg1IDcuNDkyNy03LjM4ODYgMjAuMDg1LTEwLjA5NCAzNy41NjctMi42MDE2IDE2Ljk2My0zLjUzODIgMzAuMTc5LTIuOTEzOCAzOS42NDkgMC4yMDgxMyAyLjYwMTYtMC4yMDgxMyA0Ljg5MTEtMS4yNDg4IDYuODY4My0xLjI0ODggMi4yODk0LTMuMTIyIDMuNTM4Mi01LjUxNTQgMy43NDYzLTIuNzA1NyAwLjIwODEzLTUuNTE1NC0xLjA0MDYtOC4yMjExLTMuODUwNC05LjY3OC05Ljg4NjItMTcuMzc5LTI0LjY2My0yMi45OTgtNDQuMzMyLTYuNzY0MiAxMy4zMi0xMS43NTkgMjMuMzExLTE0Ljk4NSAyOS45NzEtNi4xMzk4IDExLjc1OS0xMS4zNDMgMTcuNzk1LTE1LjcxNCAxOC4xMDctMi44MDk4IDAuMjA4MTMtNS4yMDMzLTIuMTg1NC03LjI4NDYtNy4xODA1LTUuMzA3My0xMy42MzMtMTEuMDMxLTM5Ljk2MS0xNy4xNzEtNzguOTg1LTAuNDE2MjYtMi43MDU3IDAuMjA4MTMtNS4wOTkyIDEuNjY1LTYuOTcyNHptMjIzLjY0IDE2LjMzOGMtMy43NDYzLTYuNTU2MS05LjI2MTgtMTAuNTExLTE2LjY1LTEyLjA3Mi0xLjk3NzItMC40MTYyNi0zLjg1MDQtMC42MjQzOS01LjYxOTUtMC42MjQzOS05Ljk5MDIgMC0xOC4xMDcgNS4yMDMzLTI0LjQ1NSAxNS42MS01LjQxMTQgOC44NDU1LTguMTE3MSAxOC42MjgtOC4xMTcxIDI5LjM0NiAwIDguMDEzIDEuNjY1IDE0Ljg4MSA0Ljk5NTEgMjAuNjA1IDMuNzQ2MyA2LjU1NjEgOS4yNjE4IDEwLjUxMSAxNi42NSAxMi4wNzIgMS45NzcyIDAuNDE2MjYgMy44NTA0IDAuNjI0MzkgNS42MTk1IDAuNjI0MzkgMTAuMDk0IDAgMTguMjExLTUuMjAzMyAyNC40NTUtMTUuNjEgNS40MTE0LTguOTQ5NiA4LjExNzEtMTguNzMyIDguMTE3MS0yOS40NSAwLjEwNDA2LTguMTE3MS0xLjY2NS0xNC44ODEtNC45OTUxLTIwLjUwMXptLTEzLjExMiAyOC44MjZjLTEuNDU2OSA2Ljg2ODMtNC4wNTg1IDExLjk2Ny03LjkwODkgMTUuNDAyLTMuMDE3OSAyLjcwNTctNS44Mjc2IDMuODUwNC04LjQyOTMgMy4zMzAxLTIuNDk3Ni0wLjUyMDMzLTQuNTc4OS0yLjcwNTctNi4xMzk4LTYuNzY0Mi0xLjI0ODgtMy4yMjYtMS44NzMyLTYuNDUyLTEuODczMi05LjQ2OTkgMC0yLjYwMTYgMC4yMDgxMy01LjIwMzMgMC43Mjg0Ni03LjU5NjcgMC45MzY1OS00LjI2NjcgMi43MDU3LTguNDI5MyA1LjUxNTQtMTIuMzg0IDMuNDM0MS01LjA5OTIgNy4wNzY0LTcuMTgwNSAxMC44MjMtNi40NTIgMi40OTc2IDAuNTIwMzMgNC41Nzg5IDIuNzA1NyA2LjEzOTggNi43NjQyIDEuMjQ4OCAzLjIyNiAxLjg3MzIgNi40NTIgMS44NzMyIDkuNDY5OSAwIDIuNzA1Ny0wLjIwODEzIDUuMzA3My0wLjcyODQ2IDcuNzAwOHptLTUyLjAzMy0yOC44MjZjLTMuNzQ2My02LjU1NjEtOS4zNjU5LTEwLjUxMS0xNi42NS0xMi4wNzItMS45NzcyLTAuNDE2MjYtMy44NTA0LTAuNjI0MzktNS42MTk1LTAuNjI0MzktOS45OTAyIDAtMTguMTA3IDUuMjAzMy0yNC40NTUgMTUuNjEtNS40MTE0IDguODQ1NS04LjExNzEgMTguNjI4LTguMTE3MSAyOS4zNDYgMCA4LjAxMyAxLjY2NSAxNC44ODEgNC45OTUxIDIwLjYwNSAzLjc0NjMgNi41NTYxIDkuMjYxOCAxMC41MTEgMTYuNjUgMTIuMDcyIDEuOTc3MiAwLjQxNjI2IDMuODUwNCAwLjYyNDM5IDUuNjE5NSAwLjYyNDM5IDEwLjA5NCAwIDE4LjIxMS01LjIwMzMgMjQuNDU1LTE1LjYxIDUuNDExNC04Ljk0OTYgOC4xMTcxLTE4LjczMiA4LjExNzEtMjkuNDUgMC04LjExNzEtMS42NjUtMTQuODgxLTQuOTk1MS0yMC41MDF6bS0xMy4yMTYgMjguODI2Yy0xLjQ1NjkgNi44NjgzLTQuMDU4NSAxMS45NjctNy45MDg5IDE1LjQwMi0zLjAxNzkgMi43MDU3LTUuODI3NiAzLjg1MDQtOC40MjkzIDMuMzMwMS0yLjQ5NzYtMC41MjAzMy00LjU3ODktMi43MDU3LTYuMTM5OC02Ljc2NDItMS4yNDg4LTMuMjI2LTEuODczMi02LjQ1Mi0xLjg3MzItOS40Njk5IDAtMi42MDE2IDAuMjA4MTMtNS4yMDMzIDAuNzI4NDYtNy41OTY3IDAuOTM2NTgtNC4yNjY3IDIuNzA1Ny04LjQyOTMgNS41MTU0LTEyLjM4NCAzLjQzNDEtNS4wOTkyIDcuMDc2NC03LjE4MDUgMTAuODIzLTYuNDUyIDIuNDk3NiAwLjUyMDMzIDQuNTc4OSAyLjcwNTcgNi4xMzk4IDYuNzY0MiAxLjI0ODggMy4yMjYgMS44NzMyIDYuNDUyIDEuODczMiA5LjQ2OTkgMC4xMDQwNiAyLjcwNTctMC4yMDgxMyA1LjMwNzMtMC43Mjg0NiA3LjcwMDh6IiBmaWxsPSIjZmZmIi8+Cjwvc3ZnPgo="},"typeVersion":1,"categories":[{"id":2,"name":"Sales"}]},{"id":325,"name":"n8n-nodes-base.twitter","defaults":{"name":"X"},"displayName":"X (Formerly Twitter)","icon":"file:x.svg","iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE4LjI0NCAyLjI1aDMuMzA4bC03LjIyNyA4LjI2IDguNTAyIDExLjI0SDE2LjE3bC01LjIxNC02LjgxN0w0Ljk5IDIxLjc1SDEuNjhsNy43My04LjgzNUwxLjI1NCAyLjI1SDguMDhsNC43MTMgNi4yMzF6bS0xLjE2MSAxNy41MmgxLjgzM0w3LjA4NCA0LjEyNkg1LjExN3oiPjwvcGF0aD48L3N2Zz4K"},"typeVersion":2,"categories":[{"id":1,"name":"Marketing & Content"}]}],"categories":[{"id":2,"name":"Sales"}],"workflows":[{"id":1205,"name":"Promote new Shopify products on Twitter and Telegram","views":485,"recentViews":9850,"totalViews":485,"createdAt":"2021-08-24T10:40:50.007Z","description":"This workflow automatically promotes your new Shopify products on Twitter and Telegram. This workflow is also featured in the blog post [*6 e-commerce workflows to power up your Shopify store*](https://n8n.io/blog/no-code-ecommerce-workflow-automations/#promote-your-new-products-on-social-media).\n\n## Prerequisites\n\n- A Shopify account and [credentials](https://docs.n8n.io/integrations/credentials/shopify/)\n- A Twitter account and [credentials](https://docs.n8n.io/integrations/credentials/twitter/)\n- A Telegram account and [credentials](https://docs.n8n.io/integrations/credentials/telegram/) for the channel you want to send messages to.\n\n## Nodes\n\n- [Shopify Trigger node](https://docs.n8n.io/integrations/trigger-nodes/n8n-nodes-base.shopifytrigger/) triggers the workflow when you create a new product in Shopify.\n- [Twitter node](https://docs.n8n.io/integrations/nodes/n8n-nodes-base.twitter/) posts a tweet with the text \"Hey there, my design is now on a new product! Visit my {shop name} to get this cool {product title} (and check out more {product type})\".\n- [Telegram node](https://docs.n8n.io/integrations/nodes/n8n-nodes-base.telegram/) posts a message with the same text as above in a Telegram channel.","workflow":{"nodes":[{"name":"Twitter","type":"n8n-nodes-base.twitter","position":[720,-220],"parameters":{"text":"=Hey there, my design is now on a new product ✨\nVisit my {{$json[\"vendor\"]}} shop to get this cool{{$json[\"title\"]}} (and check out more {{$json[\"product_type\"]}}) 🛍️","additionalFields":{}},"credentials":{"twitterOAuth1Api":"twitter"},"typeVersion":1},{"name":"Telegram","type":"n8n-nodes-base.telegram","position":[720,-20],"parameters":{"text":"=Hey there, my design is now on a new product!\nVisit my {{$json[\"vendor\"]}} shop to get this cool{{$json[\"title\"]}} (and check out more {{$json[\"product_type\"]}})","chatId":"123456","additionalFields":{}},"credentials":{"telegramApi":"telegram_habot"},"typeVersion":1},{"name":"product created","type":"n8n-nodes-base.shopifyTrigger","position":[540,-110],"webhookId":"2a7e0e50-8f09-4a2b-bf54-a849a6ac4fe0","parameters":{"topic":"products/create"},"credentials":{"shopifyApi":"shopify_nodeqa"},"typeVersion":1}],"connections":{"product created":{"main":[[{"node":"Twitter","type":"main","index":0},{"node":"Telegram","type":"main","index":0}]]}}},"workflowInfo":{"nodeCount":3,"nodeTypes":{"n8n-nodes-base.twitter":{"count":1},"n8n-nodes-base.telegram":{"count":1},"n8n-nodes-base.shopifyTrigger":{"count":1}}},"user":{"username":"lorenanda"},"nodes":[{"id":49,"icon":"file:telegram.svg","name":"n8n-nodes-base.telegram","defaults":{"name":"Telegram"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNjYgNjYiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48ZyBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZD0iTTAgMzJjMCAxNy42NzMgMTQuMzI3IDMyIDMyIDMyczMyLTE0LjMyNyAzMi0zMlM0OS42NzMgMCAzMiAwIDAgMTQuMzI3IDAgMzIiIGZpbGw9IiMzN2FlZTIiLz48cGF0aCBkPSJNMjEuNjYxIDM0LjMzOGwzLjc5NyAxMC41MDhzLjQ3NS45ODMuOTgzLjk4MyA4LjA2OC03Ljg2NCA4LjA2OC03Ljg2NGw4LjQwNy0xNi4yMzctMjEuMTE5IDkuODk4eiIgZmlsbD0iI2M4ZGFlYSIvPjxwYXRoIGQ9Ik0yNi42OTUgMzcuMDM0bC0uNzI5IDcuNzQ2cy0uMzA1IDIuMzczIDIuMDY4IDBsNC42NDQtNC4yMDMiIGZpbGw9IiNhOWM2ZDgiLz48cGF0aCBkPSJNMjEuNzMgMzQuNzEybC03LjgwOS0yLjU0NXMtLjkzMi0uMzc4LS42MzMtMS4yMzdjLjA2Mi0uMTc3LjE4Ni0uMzI4LjU1OS0uNTg4IDEuNzMxLTEuMjA2IDMyLjAyOC0xMi4wOTYgMzIuMDI4LTEyLjA5NnMuODU2LS4yODggMS4zNjEtLjA5N2MuMjMxLjA4OC4zNzguMTg3LjUwMy41NDguMDQ1LjEzMi4wNzEuNDExLjA2OC42ODktLjAwMy4yMDEtLjAyNy4zODYtLjA0NS42NzgtLjE4NCAyLjk3OC01LjcwNiAyNS4xOTgtNS43MDYgMjUuMTk4cy0uMzMgMS4zLTEuNTE0IDEuMzQ1Yy0uNDMyLjAxNi0uOTU2LS4wNzEtMS41ODItLjYxLTIuMzIzLTEuOTk4LTEwLjM1Mi03LjM5NC0xMi4xMjYtOC41OGEuMzQuMzQgMCAwMS0uMTQ2LS4yMzljLS4wMjUtLjEyNS4xMDgtLjI4LjEwOC0uMjhzMTMuOTgtMTIuNDI3IDE0LjM1Mi0xMy43MzFjLjAyOS0uMTAxLS4wNzktLjE1MS0uMjI2LS4xMDctLjkyOS4zNDItMTcuMDI1IDEwLjUwNi0xOC44MDEgMTEuNjI5LS4xMDQuMDY2LS4zOTUuMDIzLS4zOTUuMDIzIi8+PC9nPjwvc3ltYm9sPjwvc3ZnPg=="},"categories":[{"id":6,"name":"Communication"}],"displayName":"Telegram","typeVersion":1},{"id":107,"icon":"file:shopify.svg","name":"n8n-nodes-base.shopifyTrigger","defaults":{"name":"Shopify Trigger"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNTggNjYiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48ZyBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZD0iTTQ5LjI1NSAxMi40ODRhLjYzMy42MzMgMCAwMC0uNTY0LS41MjdjLS4yMjUtLjAzNy01LjE3LS4zNzYtNS4xNy0uMzc2bC0zLjc3LTMuNzdjLS4zNC0uMzc2LTEuMDkyLS4yNjYtMS4zNzYtLjE4OC0uMDM3IDAtLjc1Mi4yMjUtMS45MjIuNjA1LTEuMTM3LTMuMy0zLjE1LTYuMzA2LTYuNjk2LTYuMzA2aC0uMzAzQzI4LjQzOC42MDUgMjcuMTk0IDAgMjYuMTQ0IDBjLTguMjU2LjAzNy0xMi4yIDEwLjMzMy0xMy40MzQgMTUuNTk0bC01Ljc3IDEuNzdjLTEuNzcuNTY0LTEuODM1LjYwNS0yLjA3MyAyLjI5M0wwIDU3LjE3NSAzNi40NjggNjRsMTkuNzYzLTQuMjZjMC0uMDM3LTYuOTQtNDYuODk3LTYuOTc2LTQ3LjI1NXpNMzQuNDMxIDguODZjLS45MTcuMzAzLTEuOTYzLjYwNS0zLjEuOTQ1di0uNjhhMTUuMDMgMTUuMDMgMCAwMC0uNzUyLTQuOTk5YzEuODQ4LjI4NCAzLjEgMi4zNTcgMy44NDMgNC43MzN6bS02LjA2OC00LjI5OGMuNjAzIDEuNzc4Ljg4MyAzLjY1LjgyNiA1LjUyN3YuMzRsLTYuMzc1IDEuOTYzYzEuMjQ4LTQuNjYgMy41NS02Ljk2MiA1LjU1LTcuODN6bS0yLjQ1LTIuMjkzYTEuOTQgMS45NCAwIDAxMS4wNTUuMzM5Yy0yLjY2IDEuMjM4LTUuNDcyIDQuMzY2LTYuNjc4IDEwLjYyN2wtNS4wNDUgMS41NDZDMTYuNjY4IDEwLjAzIDE5Ljk4OCAyLjI2IDI1LjkxIDIuMjZ6IiBmaWxsPSIjOTViZjQ3Ii8+PHBhdGggZD0iTTQ4LjY5MSAxMS45NTdjLS4yMjUtLjAzNy01LjE3LS4zNzYtNS4xNy0uMzc2bC0zLjc3LTMuNzdhLjc1My43NTMgMCAwMC0uNTI3LS4yMjVMMzYuNDcyIDY0bDE5Ljc2My00LjI2LTYuOTgtNDcuMjE4YS42OC42OCAwIDAwLS41NjQtLjU2NHoiIGZpbGw9IiM1ZThlM2UiLz48cGF0aCBkPSJNMjkuNzU4IDIyLjlsLTIuNDU0IDcuMjQyYTExLjM2IDExLjM2IDAgMDAtNC43NTItMS4xMzNjLTMuODQ4IDAtNC4wMzYgMi40MTItNC4wMzYgMy4wMTggMCAzLjI5OCA4LjYzNiA0LjU2NCA4LjYzNiAxMi4zMzMgMCA2LjEtMy44ODUgMTAuMDMtOS4xIDEwLjAzLTYuMjYgMC05LjQ2Ny0zLjg4NS05LjQ2Ny0zLjg4NWwxLjY2NS01LjU1czMuMjggMi44MyA2LjA3MyAyLjgzYTIuNDcgMi40NyAwIDAwMi41NjQtMi40OWMwLTQuMzQtNy4xLTQuNTI3LTcuMS0xMS42MTggMC01Ljk2MiA0LjI5OC0xMS43NyAxMi45MzQtMTEuNzcgMy4zOTQuMDUgNS4wMTggMSA1LjAxOCAxeiIvPjwvZz48L3N5bWJvbD48L3N2Zz4="},"categories":[{"id":2,"name":"Sales"}],"displayName":"Shopify Trigger","typeVersion":1},{"id":325,"icon":"file:x.svg","name":"n8n-nodes-base.twitter","defaults":{"name":"X"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE4LjI0NCAyLjI1aDMuMzA4bC03LjIyNyA4LjI2IDguNTAyIDExLjI0SDE2LjE3bC01LjIxNC02LjgxN0w0Ljk5IDIxLjc1SDEuNjhsNy43My04LjgzNUwxLjI1NCAyLjI1SDguMDhsNC43MTMgNi4yMzF6bS0xLjE2MSAxNy41MmgxLjgzM0w3LjA4NCA0LjEyNkg1LjExN3oiPjwvcGF0aD48L3N2Zz4K"},"categories":[{"id":1,"name":"Marketing & Content"}],"displayName":"X (Formerly Twitter)","typeVersion":2}],"categories":[{"id":2,"name":"Sales"},{"id":19,"name":"Marketing & Growth"}],"image":[{"id":527,"url":"https://n8niostorageaccount.blob.core.windows.net/n8nio-strapi-blobs-prod/assets/89a078b208fe4c6181902608b1cd1332.png"}]},{"id":1456,"name":"Add new customers from WooCommerce to Mautic","views":333,"recentViews":9833,"totalViews":333,"createdAt":"2022-02-17T15:00:40.748Z","description":"This workflow uses a WooCommerce trigger that will run when a new customer has been added, It will then add the customer to Mautic.\n\nTo use this workflow you will need to set the credentials to use for the WooCommerce and Mautic nodes.","workflow":{"id":83,"name":"New WooCommerce Customer to Mautic","nodes":[{"name":"Check for Existing","type":"n8n-nodes-base.mautic","position":[280,480],"parameters":{"options":{"search":"={{$json[\"email\"]}}"},"operation":"getAll","authentication":"oAuth2"},"credentials":{"mauticOAuth2Api":{"id":"54","name":"Mautic account"}},"typeVersion":1,"alwaysOutputData":true},{"name":"If New","type":"n8n-nodes-base.if","position":[460,480],"parameters":{"conditions":{"string":[{"value1":"={{$json[\"id\"]}}","operation":"isEmpty"}]}},"typeVersion":1},{"name":"Create Contact","type":"n8n-nodes-base.mautic","position":[680,320],"parameters":{"email":"={{$node[\"Customer Created\"].json[\"email\"]}}","company":"={{$node[\"Customer Created\"].json[\"billing\"][\"company\"]}}","options":{},"lastName":"={{$node[\"Customer Created\"].json[\"last_name\"]}}","firstName":"={{$node[\"Customer Created\"].json[\"first_name\"]}}","authentication":"oAuth2","additionalFields":{}},"credentials":{"mauticOAuth2Api":{"id":"54","name":"Mautic account"}},"typeVersion":1},{"name":"Update Contact","type":"n8n-nodes-base.mautic","position":[680,580],"parameters":{"options":{},"contactId":"={{$json[\"id\"]}}","operation":"update","updateFields":{"lastName":"={{$node[\"Customer Created or Updated\"].json[\"last_name\"]}}","firstName":"={{$node[\"Customer Created or Updated\"].json[\"first_name\"]}}"},"authentication":"oAuth2"},"credentials":{"mauticOAuth2Api":{"id":"54","name":"Mautic account"}},"typeVersion":1},{"name":"Customer Created or Updated","type":"n8n-nodes-base.wooCommerceTrigger","position":[100,480],"webhookId":"5d89e322-a5e0-4cce-9eab-185e8375175b","parameters":{"event":"customer.updated"},"credentials":{"wooCommerceApi":{"id":"48","name":"WooCommerce account"}},"typeVersion":1}],"active":false,"settings":{"saveManualExecutions":true,"saveExecutionProgress":true,"saveDataSuccessExecution":"all"},"connections":{"If New":{"main":[[{"node":"Create Contact","type":"main","index":0}],[{"node":"Update Contact","type":"main","index":0}]]},"Check for Existing":{"main":[[{"node":"If New","type":"main","index":0}]]},"Customer Created or Updated":{"main":[[{"node":"Check for Existing","type":"main","index":0}]]}}},"workflowInfo":{"nodeCount":6,"nodeTypes":{"n8n-nodes-base.if":{"count":1},"n8n-nodes-base.mautic":{"count":3},"n8n-nodes-base.wooCommerceTrigger":{"count":1}}},"user":{"username":"jon-n8n"},"nodes":[{"id":20,"icon":"fa:map-signs","name":"n8n-nodes-base.if","defaults":{"name":"IF","color":"#408000"},"iconData":{"icon":"map-signs","type":"icon"},"categories":[{"id":9,"name":"Core Nodes"}],"displayName":"IF","typeVersion":1},{"id":42,"icon":"fa:play","name":"n8n-nodes-base.start","defaults":{"name":"Start","color":"#00e000"},"iconData":{"icon":"play","type":"icon"},"categories":[{"id":9,"name":"Core Nodes"}],"displayName":"Start","typeVersion":1},{"id":126,"icon":"file:mautic.svg","name":"n8n-nodes-base.mautic","defaults":{"name":"Mautic"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNi4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iMzQ5Ljc3OXB4IiBoZWlnaHQ9IjM0OS43NzlweCIgdmlld0JveD0iMCAwIDM0OS43NzkgMzQ5Ljc3OSIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMzQ5Ljc3OSAzNDkuNzc5Ig0KCSB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxwYXRoIGZpbGw9IiNGRkZGRkYiIGQ9Ik0xNzQuODksMzQ5Ljc3OUM3OC42MTIsMzQ5Ljc3OSwwLDI3MS40NjIsMCwxNzQuODlTNzguNjEyLDAsMTc0Ljg5LDBjMjMuMjYsMCw0NS45MzEsNC40MTcsNjcuMTI5LDEzLjU0Mw0KCWM1Ljg4OSwyLjY1LDguODMzLDkuNDIyLDYuNDc4LDE1LjYwNWMtMi42NDksNS44ODgtOS40MjEsOC44MzMtMTUuNjA0LDYuNDc3Yy0xOC41NDktNy42NTUtMzcuOTgtMTEuNDgyLTU4LjAwMi0xMS40ODINCgljLTgzLjMyMywwLTE1MS4wNDEsNjcuNzE4LTE1MS4wNDEsMTUxLjA0MVM5MS41NjcsMzI2LjIyNSwxNzQuODksMzI2LjIyNWM4My4zMjMsMCwxNTEuMDQxLTY3LjcxOCwxNTEuMDQxLTE1MS4wNDENCgljMC0xNy45Ni0yLjk0NC0zNS4zMzItOS4xMjctNTEuODE5Yy0yLjM1NS02LjE4MywwLjg4My0xMi45NTUsNy4wNjYtMTUuMzFjNi4xODMtMi4zNTUsMTIuOTU0LDAuODgzLDE1LjMxLDcuMDY2DQoJYzcuMDY2LDE5LjEzOCwxMC42LDM5LjQ1MywxMC42LDYwLjA2M0MzNDkuNzc5LDI3MS4xNjcsMjcxLjQ2MiwzNDkuNzc5LDE3NC44OSwzNDkuNzc5Ii8+DQo8Zz4NCgk8cG9seWdvbiBmaWxsPSIjRkRCOTMzIiBwb2ludHM9IjI1MS40NCwxNTYuOTMgMjI0LjM1NCwxODUuMTk0IDIzOS4zNjksMjQ4LjQ5NiAyNzMuNTIyLDI0OC40OTYgCSIvPg0KPC9nPg0KPHBvbHlnb24gZmlsbD0iI0ZEQjkzMyIgcG9pbnRzPSIyNDAuMjUzLDczLjMxMiAyNDkuNjc0LDgyLjczNCAxNzQuODksMTYxLjkzNSAxMTAuOTk5LDk2LjI3NyA3NC4xOTYsMjQ4LjQ5NiAxMDguMzUsMjQ4LjQ5NiANCgkxMjguNjY1LDE2My45OTYgMTc0Ljg5LDIxNC4zNDMgMjczLjgxNywxMDYuNTgzIDI4My4yMzksMTE2LjI5OSAyOTIuNjYsNjMuMDA3ICIvPg0KPC9zdmc+DQo="},"categories":[{"id":1,"name":"Marketing & Content"},{"id":6,"name":"Communication"}],"displayName":"Mautic","typeVersion":1},{"id":235,"icon":"file:wooCommerce.svg","name":"n8n-nodes-base.wooCommerceTrigger","defaults":{"name":"WooCommerce Trigger"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIC01MCAyNTYgMjUzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KPHRpdGxlPldvb0NvbW1lcmNlIExvZ288L3RpdGxlPgo8bWV0YWRhdGE+CjxyZGY6UkRGPgo8Y2M6V29yayByZGY6YWJvdXQ9IiI+CjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0Pgo8ZGM6dHlwZSByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIi8+CjxkYzp0aXRsZS8+CjwvY2M6V29yaz4KPC9yZGY6UkRGPgo8L21ldGFkYXRhPgo8cGF0aCBkPSJtMjMuNzU5IDBoMjA4LjM4YzEzLjE4NyAwIDIzLjg2MyAxMC42NzUgMjMuODYzIDIzLjg2M3Y3OS41NDJjMCAxMy4xODctMTAuNjc1IDIzLjg2My0yMy44NjMgMjMuODYzaC03NC43MjdsMTAuMjU3IDI1LjExOC00NS4xMDktMjUuMTE4aC05OC42OTVjLTEzLjE4NyAwLTIzLjg2My0xMC42NzUtMjMuODYzLTIzLjg2M3YtNzkuNTQyYy0wLjEwNDY2LTEzLjA4MyAxMC41NzEtMjMuODYzIDIzLjc1OC0yMy44NjN6IiBmaWxsPSIjN2Y1NGIzIi8+CjxwYXRoIGQ9Im0xNC41NzggMjEuNzVjMS40NTY5LTEuOTc3MiAzLjY0MjMtMy4wMTc5IDYuNTU2MS0zLjIyNiA1LjMwNzMtMC40MTYyNiA4LjMyNTIgMi4wODEzIDkuMDUzNyA3LjQ5MjcgMy4yMjYgMjEuNzUgNi43NjQyIDQwLjE2OSAxMC41MTEgNTUuMjU5bDIyLjc5LTQzLjM5NWMyLjA4MTMtMy45NTQ1IDQuNjgyOS02LjAzNTggNy44MDQ5LTYuMjQzOSA0LjU3ODktMC4zMTIyIDcuMzg4NiAyLjYwMTYgOC41MzMzIDguNzQxNSAyLjYwMTYgMTMuODQxIDUuOTMxNyAyNS42IDkuODg2MiAzNS41OSAyLjcwNTctMjYuNDMzIDcuMjg0Ni00NS40NzYgMTMuNzM3LTU3LjIzNiAxLjU2MS0yLjkxMzggMy44NTA0LTQuMzcwNyA2Ljg2ODMtNC41Nzg5IDIuMzkzNS0wLjIwODEzIDQuNTc4OSAwLjUyMDMzIDYuNTU2MSAyLjA4MTMgMS45NzcyIDEuNTYxIDMuMDE3OSAzLjUzODIgMy4yMjYgNS45MzE3IDAuMTA0MDYgMS44NzMyLTAuMjA4MTMgMy40MzQxLTEuMDQwNyA0Ljk5NTEtNC4wNTg1IDcuNDkyNy03LjM4ODYgMjAuMDg1LTEwLjA5NCAzNy41NjctMi42MDE2IDE2Ljk2My0zLjUzODIgMzAuMTc5LTIuOTEzOCAzOS42NDkgMC4yMDgxMyAyLjYwMTYtMC4yMDgxMyA0Ljg5MTEtMS4yNDg4IDYuODY4My0xLjI0ODggMi4yODk0LTMuMTIyIDMuNTM4Mi01LjUxNTQgMy43NDYzLTIuNzA1NyAwLjIwODEzLTUuNTE1NC0xLjA0MDYtOC4yMjExLTMuODUwNC05LjY3OC05Ljg4NjItMTcuMzc5LTI0LjY2My0yMi45OTgtNDQuMzMyLTYuNzY0MiAxMy4zMi0xMS43NTkgMjMuMzExLTE0Ljk4NSAyOS45NzEtNi4xMzk4IDExLjc1OS0xMS4zNDMgMTcuNzk1LTE1LjcxNCAxOC4xMDctMi44MDk4IDAuMjA4MTMtNS4yMDMzLTIuMTg1NC03LjI4NDYtNy4xODA1LTUuMzA3My0xMy42MzMtMTEuMDMxLTM5Ljk2MS0xNy4xNzEtNzguOTg1LTAuNDE2MjYtMi43MDU3IDAuMjA4MTMtNS4wOTkyIDEuNjY1LTYuOTcyNHptMjIzLjY0IDE2LjMzOGMtMy43NDYzLTYuNTU2MS05LjI2MTgtMTAuNTExLTE2LjY1LTEyLjA3Mi0xLjk3NzItMC40MTYyNi0zLjg1MDQtMC42MjQzOS01LjYxOTUtMC42MjQzOS05Ljk5MDIgMC0xOC4xMDcgNS4yMDMzLTI0LjQ1NSAxNS42MS01LjQxMTQgOC44NDU1LTguMTE3MSAxOC42MjgtOC4xMTcxIDI5LjM0NiAwIDguMDEzIDEuNjY1IDE0Ljg4MSA0Ljk5NTEgMjAuNjA1IDMuNzQ2MyA2LjU1NjEgOS4yNjE4IDEwLjUxMSAxNi42NSAxMi4wNzIgMS45NzcyIDAuNDE2MjYgMy44NTA0IDAuNjI0MzkgNS42MTk1IDAuNjI0MzkgMTAuMDk0IDAgMTguMjExLTUuMjAzMyAyNC40NTUtMTUuNjEgNS40MTE0LTguOTQ5NiA4LjExNzEtMTguNzMyIDguMTE3MS0yOS40NSAwLjEwNDA2LTguMTE3MS0xLjY2NS0xNC44ODEtNC45OTUxLTIwLjUwMXptLTEzLjExMiAyOC44MjZjLTEuNDU2OSA2Ljg2ODMtNC4wNTg1IDExLjk2Ny03LjkwODkgMTUuNDAyLTMuMDE3OSAyLjcwNTctNS44Mjc2IDMuODUwNC04LjQyOTMgMy4zMzAxLTIuNDk3Ni0wLjUyMDMzLTQuNTc4OS0yLjcwNTctNi4xMzk4LTYuNzY0Mi0xLjI0ODgtMy4yMjYtMS44NzMyLTYuNDUyLTEuODczMi05LjQ2OTkgMC0yLjYwMTYgMC4yMDgxMy01LjIwMzMgMC43Mjg0Ni03LjU5NjcgMC45MzY1OS00LjI2NjcgMi43MDU3LTguNDI5MyA1LjUxNTQtMTIuMzg0IDMuNDM0MS01LjA5OTIgNy4wNzY0LTcuMTgwNSAxMC44MjMtNi40NTIgMi40OTc2IDAuNTIwMzMgNC41Nzg5IDIuNzA1NyA2LjEzOTggNi43NjQyIDEuMjQ4OCAzLjIyNiAxLjg3MzIgNi40NTIgMS44NzMyIDkuNDY5OSAwIDIuNzA1Ny0wLjIwODEzIDUuMzA3My0wLjcyODQ2IDcuNzAwOHptLTUyLjAzMy0yOC44MjZjLTMuNzQ2My02LjU1NjEtOS4zNjU5LTEwLjUxMS0xNi42NS0xMi4wNzItMS45NzcyLTAuNDE2MjYtMy44NTA0LTAuNjI0MzktNS42MTk1LTAuNjI0MzktOS45OTAyIDAtMTguMTA3IDUuMjAzMy0yNC40NTUgMTUuNjEtNS40MTE0IDguODQ1NS04LjExNzEgMTguNjI4LTguMTE3MSAyOS4zNDYgMCA4LjAxMyAxLjY2NSAxNC44ODEgNC45OTUxIDIwLjYwNSAzLjc0NjMgNi41NTYxIDkuMjYxOCAxMC41MTEgMTYuNjUgMTIuMDcyIDEuOTc3MiAwLjQxNjI2IDMuODUwNCAwLjYyNDM5IDUuNjE5NSAwLjYyNDM5IDEwLjA5NCAwIDE4LjIxMS01LjIwMzMgMjQuNDU1LTE1LjYxIDUuNDExNC04Ljk0OTYgOC4xMTcxLTE4LjczMiA4LjExNzEtMjkuNDUgMC04LjExNzEtMS42NjUtMTQuODgxLTQuOTk1MS0yMC41MDF6bS0xMy4yMTYgMjguODI2Yy0xLjQ1NjkgNi44NjgzLTQuMDU4NSAxMS45NjctNy45MDg5IDE1LjQwMi0zLjAxNzkgMi43MDU3LTUuODI3NiAzLjg1MDQtOC40MjkzIDMuMzMwMS0yLjQ5NzYtMC41MjAzMy00LjU3ODktMi43MDU3LTYuMTM5OC02Ljc2NDItMS4yNDg4LTMuMjI2LTEuODczMi02LjQ1Mi0xLjg3MzItOS40Njk5IDAtMi42MDE2IDAuMjA4MTMtNS4yMDMzIDAuNzI4NDYtNy41OTY3IDAuOTM2NTgtNC4yNjY3IDIuNzA1Ny04LjQyOTMgNS41MTU0LTEyLjM4NCAzLjQzNDEtNS4wOTkyIDcuMDc2NC03LjE4MDUgMTAuODIzLTYuNDUyIDIuNDk3NiAwLjUyMDMzIDQuNTc4OSAyLjcwNTcgNi4xMzk4IDYuNzY0MiAxLjI0ODggMy4yMjYgMS44NzMyIDYuNDUyIDEuODczMiA5LjQ2OTkgMC4xMDQwNiAyLjcwNTctMC4yMDgxMyA1LjMwNzMtMC43Mjg0NiA3LjcwMDh6IiBmaWxsPSIjZmZmIi8+Cjwvc3ZnPgo="},"categories":[{"id":2,"name":"Sales"}],"displayName":"WooCommerce Trigger","typeVersion":1}],"categories":[{"id":2,"name":"Sales"}],"image":[]},{"id":1459,"name":"Notify on Telegram and Twitter when new order is added in WooCommerce","views":620,"recentViews":9823,"totalViews":620,"createdAt":"2022-02-17T15:02:14.961Z","description":"This workflow uses a WooCommerce trigger that will run a new product has been added, It will then post the product to Telegram and Twitter.\n\nTo use this workflow you will need to set the credentials to use for the WooCommerce, Twitter and Telegram nodes.","workflow":{"id":85,"name":"New WooCommerce Product to Twitter and Telegram","nodes":[{"name":"Twitter","type":"n8n-nodes-base.twitter","position":[720,300],"parameters":{"text":"=✨ New Product Announcement ✨\nWe have just added {{$json[\"name\"]}}, Head to {{$json[\"permalink\"]}} to find out more.","additionalFields":{}},"credentials":{"twitterOAuth1Api":{"id":"37","name":"joffcom"}},"typeVersion":1},{"name":"Telegram","type":"n8n-nodes-base.telegram","position":[720,500],"parameters":{"text":"=✨ New Product Announcement ✨\nWe have just added {{$json[\"name\"]}}, Head to {{$json[\"permalink\"]}} to find out more.","chatId":"123456","additionalFields":{}},"credentials":{"telegramApi":{"id":"56","name":"Telegram account"}},"typeVersion":1},{"name":"WooCommerce Trigger","type":"n8n-nodes-base.wooCommerceTrigger","position":[540,400],"webhookId":"ab7b134b-9b2d-4e0d-b496-1aee30db0808","parameters":{"event":"product.created"},"credentials":{"wooCommerceApi":{"id":"48","name":"WooCommerce account"}},"typeVersion":1}],"active":false,"settings":{},"connections":{"WooCommerce Trigger":{"main":[[{"node":"Twitter","type":"main","index":0},{"node":"Telegram","type":"main","index":0}]]}}},"workflowInfo":{"nodeCount":4,"nodeTypes":{"n8n-nodes-base.twitter":{"count":1},"n8n-nodes-base.telegram":{"count":1},"n8n-nodes-base.wooCommerceTrigger":{"count":1}}},"user":{"username":"jon-n8n"},"nodes":[{"id":42,"icon":"fa:play","name":"n8n-nodes-base.start","defaults":{"name":"Start","color":"#00e000"},"iconData":{"icon":"play","type":"icon"},"categories":[{"id":9,"name":"Core Nodes"}],"displayName":"Start","typeVersion":1},{"id":49,"icon":"file:telegram.svg","name":"n8n-nodes-base.telegram","defaults":{"name":"Telegram"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNjYgNjYiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48ZyBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZD0iTTAgMzJjMCAxNy42NzMgMTQuMzI3IDMyIDMyIDMyczMyLTE0LjMyNyAzMi0zMlM0OS42NzMgMCAzMiAwIDAgMTQuMzI3IDAgMzIiIGZpbGw9IiMzN2FlZTIiLz48cGF0aCBkPSJNMjEuNjYxIDM0LjMzOGwzLjc5NyAxMC41MDhzLjQ3NS45ODMuOTgzLjk4MyA4LjA2OC03Ljg2NCA4LjA2OC03Ljg2NGw4LjQwNy0xNi4yMzctMjEuMTE5IDkuODk4eiIgZmlsbD0iI2M4ZGFlYSIvPjxwYXRoIGQ9Ik0yNi42OTUgMzcuMDM0bC0uNzI5IDcuNzQ2cy0uMzA1IDIuMzczIDIuMDY4IDBsNC42NDQtNC4yMDMiIGZpbGw9IiNhOWM2ZDgiLz48cGF0aCBkPSJNMjEuNzMgMzQuNzEybC03LjgwOS0yLjU0NXMtLjkzMi0uMzc4LS42MzMtMS4yMzdjLjA2Mi0uMTc3LjE4Ni0uMzI4LjU1OS0uNTg4IDEuNzMxLTEuMjA2IDMyLjAyOC0xMi4wOTYgMzIuMDI4LTEyLjA5NnMuODU2LS4yODggMS4zNjEtLjA5N2MuMjMxLjA4OC4zNzguMTg3LjUwMy41NDguMDQ1LjEzMi4wNzEuNDExLjA2OC42ODktLjAwMy4yMDEtLjAyNy4zODYtLjA0NS42NzgtLjE4NCAyLjk3OC01LjcwNiAyNS4xOTgtNS43MDYgMjUuMTk4cy0uMzMgMS4zLTEuNTE0IDEuMzQ1Yy0uNDMyLjAxNi0uOTU2LS4wNzEtMS41ODItLjYxLTIuMzIzLTEuOTk4LTEwLjM1Mi03LjM5NC0xMi4xMjYtOC41OGEuMzQuMzQgMCAwMS0uMTQ2LS4yMzljLS4wMjUtLjEyNS4xMDgtLjI4LjEwOC0uMjhzMTMuOTgtMTIuNDI3IDE0LjM1Mi0xMy43MzFjLjAyOS0uMTAxLS4wNzktLjE1MS0uMjI2LS4xMDctLjkyOS4zNDItMTcuMDI1IDEwLjUwNi0xOC44MDEgMTEuNjI5LS4xMDQuMDY2LS4zOTUuMDIzLS4zOTUuMDIzIi8+PC9nPjwvc3ltYm9sPjwvc3ZnPg=="},"categories":[{"id":6,"name":"Communication"}],"displayName":"Telegram","typeVersion":1},{"id":235,"icon":"file:wooCommerce.svg","name":"n8n-nodes-base.wooCommerceTrigger","defaults":{"name":"WooCommerce Trigger"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIC01MCAyNTYgMjUzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KPHRpdGxlPldvb0NvbW1lcmNlIExvZ288L3RpdGxlPgo8bWV0YWRhdGE+CjxyZGY6UkRGPgo8Y2M6V29yayByZGY6YWJvdXQ9IiI+CjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0Pgo8ZGM6dHlwZSByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIi8+CjxkYzp0aXRsZS8+CjwvY2M6V29yaz4KPC9yZGY6UkRGPgo8L21ldGFkYXRhPgo8cGF0aCBkPSJtMjMuNzU5IDBoMjA4LjM4YzEzLjE4NyAwIDIzLjg2MyAxMC42NzUgMjMuODYzIDIzLjg2M3Y3OS41NDJjMCAxMy4xODctMTAuNjc1IDIzLjg2My0yMy44NjMgMjMuODYzaC03NC43MjdsMTAuMjU3IDI1LjExOC00NS4xMDktMjUuMTE4aC05OC42OTVjLTEzLjE4NyAwLTIzLjg2My0xMC42NzUtMjMuODYzLTIzLjg2M3YtNzkuNTQyYy0wLjEwNDY2LTEzLjA4MyAxMC41NzEtMjMuODYzIDIzLjc1OC0yMy44NjN6IiBmaWxsPSIjN2Y1NGIzIi8+CjxwYXRoIGQ9Im0xNC41NzggMjEuNzVjMS40NTY5LTEuOTc3MiAzLjY0MjMtMy4wMTc5IDYuNTU2MS0zLjIyNiA1LjMwNzMtMC40MTYyNiA4LjMyNTIgMi4wODEzIDkuMDUzNyA3LjQ5MjcgMy4yMjYgMjEuNzUgNi43NjQyIDQwLjE2OSAxMC41MTEgNTUuMjU5bDIyLjc5LTQzLjM5NWMyLjA4MTMtMy45NTQ1IDQuNjgyOS02LjAzNTggNy44MDQ5LTYuMjQzOSA0LjU3ODktMC4zMTIyIDcuMzg4NiAyLjYwMTYgOC41MzMzIDguNzQxNSAyLjYwMTYgMTMuODQxIDUuOTMxNyAyNS42IDkuODg2MiAzNS41OSAyLjcwNTctMjYuNDMzIDcuMjg0Ni00NS40NzYgMTMuNzM3LTU3LjIzNiAxLjU2MS0yLjkxMzggMy44NTA0LTQuMzcwNyA2Ljg2ODMtNC41Nzg5IDIuMzkzNS0wLjIwODEzIDQuNTc4OSAwLjUyMDMzIDYuNTU2MSAyLjA4MTMgMS45NzcyIDEuNTYxIDMuMDE3OSAzLjUzODIgMy4yMjYgNS45MzE3IDAuMTA0MDYgMS44NzMyLTAuMjA4MTMgMy40MzQxLTEuMDQwNyA0Ljk5NTEtNC4wNTg1IDcuNDkyNy03LjM4ODYgMjAuMDg1LTEwLjA5NCAzNy41NjctMi42MDE2IDE2Ljk2My0zLjUzODIgMzAuMTc5LTIuOTEzOCAzOS42NDkgMC4yMDgxMyAyLjYwMTYtMC4yMDgxMyA0Ljg5MTEtMS4yNDg4IDYuODY4My0xLjI0ODggMi4yODk0LTMuMTIyIDMuNTM4Mi01LjUxNTQgMy43NDYzLTIuNzA1NyAwLjIwODEzLTUuNTE1NC0xLjA0MDYtOC4yMjExLTMuODUwNC05LjY3OC05Ljg4NjItMTcuMzc5LTI0LjY2My0yMi45OTgtNDQuMzMyLTYuNzY0MiAxMy4zMi0xMS43NTkgMjMuMzExLTE0Ljk4NSAyOS45NzEtNi4xMzk4IDExLjc1OS0xMS4zNDMgMTcuNzk1LTE1LjcxNCAxOC4xMDctMi44MDk4IDAuMjA4MTMtNS4yMDMzLTIuMTg1NC03LjI4NDYtNy4xODA1LTUuMzA3My0xMy42MzMtMTEuMDMxLTM5Ljk2MS0xNy4xNzEtNzguOTg1LTAuNDE2MjYtMi43MDU3IDAuMjA4MTMtNS4wOTkyIDEuNjY1LTYuOTcyNHptMjIzLjY0IDE2LjMzOGMtMy43NDYzLTYuNTU2MS05LjI2MTgtMTAuNTExLTE2LjY1LTEyLjA3Mi0xLjk3NzItMC40MTYyNi0zLjg1MDQtMC42MjQzOS01LjYxOTUtMC42MjQzOS05Ljk5MDIgMC0xOC4xMDcgNS4yMDMzLTI0LjQ1NSAxNS42MS01LjQxMTQgOC44NDU1LTguMTE3MSAxOC42MjgtOC4xMTcxIDI5LjM0NiAwIDguMDEzIDEuNjY1IDE0Ljg4MSA0Ljk5NTEgMjAuNjA1IDMuNzQ2MyA2LjU1NjEgOS4yNjE4IDEwLjUxMSAxNi42NSAxMi4wNzIgMS45NzcyIDAuNDE2MjYgMy44NTA0IDAuNjI0MzkgNS42MTk1IDAuNjI0MzkgMTAuMDk0IDAgMTguMjExLTUuMjAzMyAyNC40NTUtMTUuNjEgNS40MTE0LTguOTQ5NiA4LjExNzEtMTguNzMyIDguMTE3MS0yOS40NSAwLjEwNDA2LTguMTE3MS0xLjY2NS0xNC44ODEtNC45OTUxLTIwLjUwMXptLTEzLjExMiAyOC44MjZjLTEuNDU2OSA2Ljg2ODMtNC4wNTg1IDExLjk2Ny03LjkwODkgMTUuNDAyLTMuMDE3OSAyLjcwNTctNS44Mjc2IDMuODUwNC04LjQyOTMgMy4zMzAxLTIuNDk3Ni0wLjUyMDMzLTQuNTc4OS0yLjcwNTctNi4xMzk4LTYuNzY0Mi0xLjI0ODgtMy4yMjYtMS44NzMyLTYuNDUyLTEuODczMi05LjQ2OTkgMC0yLjYwMTYgMC4yMDgxMy01LjIwMzMgMC43Mjg0Ni03LjU5NjcgMC45MzY1OS00LjI2NjcgMi43MDU3LTguNDI5MyA1LjUxNTQtMTIuMzg0IDMuNDM0MS01LjA5OTIgNy4wNzY0LTcuMTgwNSAxMC44MjMtNi40NTIgMi40OTc2IDAuNTIwMzMgNC41Nzg5IDIuNzA1NyA2LjEzOTggNi43NjQyIDEuMjQ4OCAzLjIyNiAxLjg3MzIgNi40NTIgMS44NzMyIDkuNDY5OSAwIDIuNzA1Ny0wLjIwODEzIDUuMzA3My0wLjcyODQ2IDcuNzAwOHptLTUyLjAzMy0yOC44MjZjLTMuNzQ2My02LjU1NjEtOS4zNjU5LTEwLjUxMS0xNi42NS0xMi4wNzItMS45NzcyLTAuNDE2MjYtMy44NTA0LTAuNjI0MzktNS42MTk1LTAuNjI0MzktOS45OTAyIDAtMTguMTA3IDUuMjAzMy0yNC40NTUgMTUuNjEtNS40MTE0IDguODQ1NS04LjExNzEgMTguNjI4LTguMTE3MSAyOS4zNDYgMCA4LjAxMyAxLjY2NSAxNC44ODEgNC45OTUxIDIwLjYwNSAzLjc0NjMgNi41NTYxIDkuMjYxOCAxMC41MTEgMTYuNjUgMTIuMDcyIDEuOTc3MiAwLjQxNjI2IDMuODUwNCAwLjYyNDM5IDUuNjE5NSAwLjYyNDM5IDEwLjA5NCAwIDE4LjIxMS01LjIwMzMgMjQuNDU1LTE1LjYxIDUuNDExNC04Ljk0OTYgOC4xMTcxLTE4LjczMiA4LjExNzEtMjkuNDUgMC04LjExNzEtMS42NjUtMTQuODgxLTQuOTk1MS0yMC41MDF6bS0xMy4yMTYgMjguODI2Yy0xLjQ1NjkgNi44NjgzLTQuMDU4NSAxMS45NjctNy45MDg5IDE1LjQwMi0zLjAxNzkgMi43MDU3LTUuODI3NiAzLjg1MDQtOC40MjkzIDMuMzMwMS0yLjQ5NzYtMC41MjAzMy00LjU3ODktMi43MDU3LTYuMTM5OC02Ljc2NDItMS4yNDg4LTMuMjI2LTEuODczMi02LjQ1Mi0xLjg3MzItOS40Njk5IDAtMi42MDE2IDAuMjA4MTMtNS4yMDMzIDAuNzI4NDYtNy41OTY3IDAuOTM2NTgtNC4yNjY3IDIuNzA1Ny04LjQyOTMgNS41MTU0LTEyLjM4NCAzLjQzNDEtNS4wOTkyIDcuMDc2NC03LjE4MDUgMTAuODIzLTYuNDUyIDIuNDk3NiAwLjUyMDMzIDQuNTc4OSAyLjcwNTcgNi4xMzk4IDYuNzY0MiAxLjI0ODggMy4yMjYgMS44NzMyIDYuNDUyIDEuODczMiA5LjQ2OTkgMC4xMDQwNiAyLjcwNTctMC4yMDgxMyA1LjMwNzMtMC43Mjg0NiA3LjcwMDh6IiBmaWxsPSIjZmZmIi8+Cjwvc3ZnPgo="},"categories":[{"id":2,"name":"Sales"}],"displayName":"WooCommerce Trigger","typeVersion":1},{"id":325,"icon":"file:x.svg","name":"n8n-nodes-base.twitter","defaults":{"name":"X"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE4LjI0NCAyLjI1aDMuMzA4bC03LjIyNyA4LjI2IDguNTAyIDExLjI0SDE2LjE3bC01LjIxNC02LjgxN0w0Ljk5IDIxLjc1SDEuNjhsNy43My04LjgzNUwxLjI1NCAyLjI1SDguMDhsNC43MTMgNi4yMzF6bS0xLjE2MSAxNy41MmgxLjgzM0w3LjA4NCA0LjEyNkg1LjExN3oiPjwvcGF0aD48L3N2Zz4K"},"categories":[{"id":1,"name":"Marketing & Content"}],"displayName":"X (Formerly Twitter)","typeVersion":2}],"categories":[{"id":2,"name":"Sales"},{"id":19,"name":"Marketing & Growth"}],"image":[]},{"id":1457,"name":"Notify on Slack when new order is registered in WooCommerce","views":178,"recentViews":9787,"totalViews":178,"createdAt":"2022-02-17T15:01:13.489Z","description":"This workflow uses a WooCommerce trigger that will run when an order has been placed.\n\nIf the value of this is over 100 it will post it to a Slack channel.\n\nTo use this workflow you will need to set the credentials to use for the WooCommerce and Slack nodes, You will also need to pick a channel to post the message to.","workflow":{"id":81,"name":"New WooCommerce order to Slack","nodes":[{"name":"Order Created","type":"n8n-nodes-base.wooCommerceTrigger","position":[340,500],"webhookId":"287b4bf4-67ec-4c97-85d9-c0d3e6f59e6b","parameters":{"event":"order.created"},"credentials":{"wooCommerceApi":{"id":"48","name":"WooCommerce account"}},"typeVersion":1},{"name":"Send to Slack","type":"n8n-nodes-base.slack","position":[780,480],"parameters":{"text":":sparkles: There is a new order :sparkles:","channel":"woo-commerce","blocksUi":{"blocksValues":[]},"attachments":[{"color":"#66FF00","fields":{"item":[{"short":true,"title":"Order ID","value":"={{$json[\"id\"]}}"},{"short":true,"title":"Status","value":"={{$json[\"status\"]}}"},{"short":true,"title":"Total","value":"={{$json[\"currency_symbol\"]}}{{$json[\"total\"]}}"},{"short":false,"title":"Link","value":"={{$node[\"Order Created\"].json[\"_links\"][\"self\"][0][\"href\"]}}"}]},"footer":"=*Ordered:* {{$json[\"date_created\"]}} | *Transaction ID:* {{$json[\"transaction_id\"]}}"}],"otherOptions":{}},"credentials":{"slackApi":{"id":"53","name":"Slack Access Token"}},"typeVersion":1},{"name":"Price over 100","type":"n8n-nodes-base.if","position":[540,500],"parameters":{"conditions":{"number":[{"value1":"={{$json[\"total\"]}}","value2":100,"operation":"largerEqual"}]}},"typeVersion":1}],"active":false,"settings":{"saveManualExecutions":true,"saveExecutionProgress":true,"saveDataSuccessExecution":"all"},"connections":{"Order Created":{"main":[[{"node":"Price over 100","type":"main","index":0}]]},"Price over 100":{"main":[[{"node":"Send to Slack","type":"main","index":0}],[]]}}},"workflowInfo":{"nodeCount":4,"nodeTypes":{"n8n-nodes-base.if":{"count":1},"n8n-nodes-base.slack":{"count":1},"n8n-nodes-base.wooCommerceTrigger":{"count":1}}},"user":{"username":"jon-n8n"},"nodes":[{"id":20,"icon":"fa:map-signs","name":"n8n-nodes-base.if","defaults":{"name":"IF","color":"#408000"},"iconData":{"icon":"map-signs","type":"icon"},"categories":[{"id":9,"name":"Core Nodes"}],"displayName":"IF","typeVersion":1},{"id":40,"icon":"file:slack.svg","name":"n8n-nodes-base.slack","defaults":{"name":"Slack"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTUwLjg1MiAxNTAuODUyIiBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48dXNlIHhsaW5rOmhyZWY9IiNhIiB4PSIuOTI2IiB5PSIuOTI2Ii8+PHN5bWJvbCBpZD0iYSIgb3ZlcmZsb3c9InZpc2libGUiPjxnIHN0cm9rZS13aWR0aD0iMS44NTIiPjxwYXRoIGQ9Ik00MC43NDEgOTMuNTVjMC04LjczNSA2LjYwNy0xNS43NzIgMTQuODE1LTE1Ljc3MnMxNC44MTUgNy4wMzcgMTQuODE1IDE1Ljc3MnYzOC44MjRjMCA4LjczNy02LjYwNyAxNS43NzQtMTQuODE1IDE1Ljc3NHMtMTQuODE1LTcuMDM3LTE0LjgxNS0xNS43NzJ6IiBmaWxsPSIjZTAxZTVhIiBzdHJva2U9IiNlMDFlNWEiLz48cGF0aCBkPSJNOTMuNTUgMTA3LjQwOGMtOC43MzUgMC0xNS43NzItNi42MDctMTUuNzcyLTE0LjgxNXM3LjAzNy0xNC44MTUgMTUuNzcyLTE0LjgxNWgzOC44MjZjOC43MzUgMCAxNS43NzIgNi42MDcgMTUuNzcyIDE0LjgxNXMtNy4wMzcgMTQuODE1LTE1Ljc3MiAxNC44MTV6IiBmaWxsPSIjZWNiMjJkIiBzdHJva2U9IiNlY2IyMmQiLz48cGF0aCBkPSJNNzcuNzc4IDE1Ljc3MkM3Ny43NzggNy4wMzcgODQuMzg1IDAgOTIuNTkzIDBzMTQuODE1IDcuMDM3IDE0LjgxNSAxNS43NzJ2MzguODI2YzAgOC43MzUtNi42MDcgMTUuNzcyLTE0LjgxNSAxNS43NzJzLTE0LjgxNS03LjAzNy0xNC44MTUtMTUuNzcyeiIgZmlsbD0iIzJmYjY3YyIgc3Ryb2tlPSIjMmZiNjdjIi8+PHBhdGggZD0iTTE1Ljc3MiA3MC4zNzFDNy4wMzcgNzAuMzcxIDAgNjMuNzYzIDAgNTUuNTU2czcuMDM3LTE0LjgxNSAxNS43NzItMTQuODE1aDM4LjgyNmM4LjczNSAwIDE1Ljc3MiA2LjYwNyAxNS43NzIgMTQuODE1cy03LjAzNyAxNC44MTUtMTUuNzcyIDE0LjgxNXoiIGZpbGw9IiMzNmM1ZjEiIHN0cm9rZT0iIzM2YzVmMSIvPjxnIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiPjxwYXRoIGQ9Ik03Ny43NzggMTMzLjMzM2MwIDguMjA4IDYuNjA3IDE0LjgxNSAxNC44MTUgMTQuODE1czE0LjgxNS02LjYwNyAxNC44MTUtMTQuODE1LTYuNjA3LTE0LjgxNS0xNC44MTUtMTQuODE1SDc3Ljc3OHoiIGZpbGw9IiNlY2IyMmQiIHN0cm9rZT0iI2VjYjIyZCIvPjxwYXRoIGQ9Ik0xMzMuMzM0IDcwLjM3MWgtMTQuODE1VjU1LjU1NmMwLTguMjA3IDYuNjA3LTE0LjgxNSAxNC44MTUtMTQuODE1czE0LjgxNSA2LjYwNyAxNC44MTUgMTQuODE1LTYuNjA3IDE0LjgxNS0xNC44MTUgMTQuODE1eiIgZmlsbD0iIzJmYjY3YyIgc3Ryb2tlPSIjMmZiNjdjIi8+PHBhdGggZD0iTTE0LjgxNSA3Ny43NzhIMjkuNjN2MTQuODE1YzAgOC4yMDctNi42MDcgMTQuODE1LTE0LjgxNSAxNC44MTVTMCAxMDAuOCAwIDkyLjU5M3M2LjYwNy0xNC44MTUgMTQuODE1LTE0LjgxNXoiIGZpbGw9IiNlMDFlNWEiIHN0cm9rZT0iI2UwMWU1YSIvPjxwYXRoIGQ9Ik03MC4zNzEgMTQuODE1VjI5LjYzSDU1LjU1NmMtOC4yMDcgMC0xNC44MTUtNi42MDctMTQuODE1LTE0LjgxNVM0Ny4zNDggMCA1NS41NTYgMHMxNC44MTUgNi42MDcgMTQuODE1IDE0LjgxNXoiIGZpbGw9IiMzNmM1ZjEiIHN0cm9rZT0iIzM2YzVmMSIvPjwvZz48L2c+PC9zeW1ib2w+PC9zdmc+"},"categories":[{"id":6,"name":"Communication"}],"displayName":"Slack","typeVersion":2},{"id":42,"icon":"fa:play","name":"n8n-nodes-base.start","defaults":{"name":"Start","color":"#00e000"},"iconData":{"icon":"play","type":"icon"},"categories":[{"id":9,"name":"Core Nodes"}],"displayName":"Start","typeVersion":1},{"id":235,"icon":"file:wooCommerce.svg","name":"n8n-nodes-base.wooCommerceTrigger","defaults":{"name":"WooCommerce Trigger"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIC01MCAyNTYgMjUzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KPHRpdGxlPldvb0NvbW1lcmNlIExvZ288L3RpdGxlPgo8bWV0YWRhdGE+CjxyZGY6UkRGPgo8Y2M6V29yayByZGY6YWJvdXQ9IiI+CjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0Pgo8ZGM6dHlwZSByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIi8+CjxkYzp0aXRsZS8+CjwvY2M6V29yaz4KPC9yZGY6UkRGPgo8L21ldGFkYXRhPgo8cGF0aCBkPSJtMjMuNzU5IDBoMjA4LjM4YzEzLjE4NyAwIDIzLjg2MyAxMC42NzUgMjMuODYzIDIzLjg2M3Y3OS41NDJjMCAxMy4xODctMTAuNjc1IDIzLjg2My0yMy44NjMgMjMuODYzaC03NC43MjdsMTAuMjU3IDI1LjExOC00NS4xMDktMjUuMTE4aC05OC42OTVjLTEzLjE4NyAwLTIzLjg2My0xMC42NzUtMjMuODYzLTIzLjg2M3YtNzkuNTQyYy0wLjEwNDY2LTEzLjA4MyAxMC41NzEtMjMuODYzIDIzLjc1OC0yMy44NjN6IiBmaWxsPSIjN2Y1NGIzIi8+CjxwYXRoIGQ9Im0xNC41NzggMjEuNzVjMS40NTY5LTEuOTc3MiAzLjY0MjMtMy4wMTc5IDYuNTU2MS0zLjIyNiA1LjMwNzMtMC40MTYyNiA4LjMyNTIgMi4wODEzIDkuMDUzNyA3LjQ5MjcgMy4yMjYgMjEuNzUgNi43NjQyIDQwLjE2OSAxMC41MTEgNTUuMjU5bDIyLjc5LTQzLjM5NWMyLjA4MTMtMy45NTQ1IDQuNjgyOS02LjAzNTggNy44MDQ5LTYuMjQzOSA0LjU3ODktMC4zMTIyIDcuMzg4NiAyLjYwMTYgOC41MzMzIDguNzQxNSAyLjYwMTYgMTMuODQxIDUuOTMxNyAyNS42IDkuODg2MiAzNS41OSAyLjcwNTctMjYuNDMzIDcuMjg0Ni00NS40NzYgMTMuNzM3LTU3LjIzNiAxLjU2MS0yLjkxMzggMy44NTA0LTQuMzcwNyA2Ljg2ODMtNC41Nzg5IDIuMzkzNS0wLjIwODEzIDQuNTc4OSAwLjUyMDMzIDYuNTU2MSAyLjA4MTMgMS45NzcyIDEuNTYxIDMuMDE3OSAzLjUzODIgMy4yMjYgNS45MzE3IDAuMTA0MDYgMS44NzMyLTAuMjA4MTMgMy40MzQxLTEuMDQwNyA0Ljk5NTEtNC4wNTg1IDcuNDkyNy03LjM4ODYgMjAuMDg1LTEwLjA5NCAzNy41NjctMi42MDE2IDE2Ljk2My0zLjUzODIgMzAuMTc5LTIuOTEzOCAzOS42NDkgMC4yMDgxMyAyLjYwMTYtMC4yMDgxMyA0Ljg5MTEtMS4yNDg4IDYuODY4My0xLjI0ODggMi4yODk0LTMuMTIyIDMuNTM4Mi01LjUxNTQgMy43NDYzLTIuNzA1NyAwLjIwODEzLTUuNTE1NC0xLjA0MDYtOC4yMjExLTMuODUwNC05LjY3OC05Ljg4NjItMTcuMzc5LTI0LjY2My0yMi45OTgtNDQuMzMyLTYuNzY0MiAxMy4zMi0xMS43NTkgMjMuMzExLTE0Ljk4NSAyOS45NzEtNi4xMzk4IDExLjc1OS0xMS4zNDMgMTcuNzk1LTE1LjcxNCAxOC4xMDctMi44MDk4IDAuMjA4MTMtNS4yMDMzLTIuMTg1NC03LjI4NDYtNy4xODA1LTUuMzA3My0xMy42MzMtMTEuMDMxLTM5Ljk2MS0xNy4xNzEtNzguOTg1LTAuNDE2MjYtMi43MDU3IDAuMjA4MTMtNS4wOTkyIDEuNjY1LTYuOTcyNHptMjIzLjY0IDE2LjMzOGMtMy43NDYzLTYuNTU2MS05LjI2MTgtMTAuNTExLTE2LjY1LTEyLjA3Mi0xLjk3NzItMC40MTYyNi0zLjg1MDQtMC42MjQzOS01LjYxOTUtMC42MjQzOS05Ljk5MDIgMC0xOC4xMDcgNS4yMDMzLTI0LjQ1NSAxNS42MS01LjQxMTQgOC44NDU1LTguMTE3MSAxOC42MjgtOC4xMTcxIDI5LjM0NiAwIDguMDEzIDEuNjY1IDE0Ljg4MSA0Ljk5NTEgMjAuNjA1IDMuNzQ2MyA2LjU1NjEgOS4yNjE4IDEwLjUxMSAxNi42NSAxMi4wNzIgMS45NzcyIDAuNDE2MjYgMy44NTA0IDAuNjI0MzkgNS42MTk1IDAuNjI0MzkgMTAuMDk0IDAgMTguMjExLTUuMjAzMyAyNC40NTUtMTUuNjEgNS40MTE0LTguOTQ5NiA4LjExNzEtMTguNzMyIDguMTE3MS0yOS40NSAwLjEwNDA2LTguMTE3MS0xLjY2NS0xNC44ODEtNC45OTUxLTIwLjUwMXptLTEzLjExMiAyOC44MjZjLTEuNDU2OSA2Ljg2ODMtNC4wNTg1IDExLjk2Ny03LjkwODkgMTUuNDAyLTMuMDE3OSAyLjcwNTctNS44Mjc2IDMuODUwNC04LjQyOTMgMy4zMzAxLTIuNDk3Ni0wLjUyMDMzLTQuNTc4OS0yLjcwNTctNi4xMzk4LTYuNzY0Mi0xLjI0ODgtMy4yMjYtMS44NzMyLTYuNDUyLTEuODczMi05LjQ2OTkgMC0yLjYwMTYgMC4yMDgxMy01LjIwMzMgMC43Mjg0Ni03LjU5NjcgMC45MzY1OS00LjI2NjcgMi43MDU3LTguNDI5MyA1LjUxNTQtMTIuMzg0IDMuNDM0MS01LjA5OTIgNy4wNzY0LTcuMTgwNSAxMC44MjMtNi40NTIgMi40OTc2IDAuNTIwMzMgNC41Nzg5IDIuNzA1NyA2LjEzOTggNi43NjQyIDEuMjQ4OCAzLjIyNiAxLjg3MzIgNi40NTIgMS44NzMyIDkuNDY5OSAwIDIuNzA1Ny0wLjIwODEzIDUuMzA3My0wLjcyODQ2IDcuNzAwOHptLTUyLjAzMy0yOC44MjZjLTMuNzQ2My02LjU1NjEtOS4zNjU5LTEwLjUxMS0xNi42NS0xMi4wNzItMS45NzcyLTAuNDE2MjYtMy44NTA0LTAuNjI0MzktNS42MTk1LTAuNjI0MzktOS45OTAyIDAtMTguMTA3IDUuMjAzMy0yNC40NTUgMTUuNjEtNS40MTE0IDguODQ1NS04LjExNzEgMTguNjI4LTguMTE3MSAyOS4zNDYgMCA4LjAxMyAxLjY2NSAxNC44ODEgNC45OTUxIDIwLjYwNSAzLjc0NjMgNi41NTYxIDkuMjYxOCAxMC41MTEgMTYuNjUgMTIuMDcyIDEuOTc3MiAwLjQxNjI2IDMuODUwNCAwLjYyNDM5IDUuNjE5NSAwLjYyNDM5IDEwLjA5NCAwIDE4LjIxMS01LjIwMzMgMjQuNDU1LTE1LjYxIDUuNDExNC04Ljk0OTYgOC4xMTcxLTE4LjczMiA4LjExNzEtMjkuNDUgMC04LjExNzEtMS42NjUtMTQuODgxLTQuOTk1MS0yMC41MDF6bS0xMy4yMTYgMjguODI2Yy0xLjQ1NjkgNi44NjgzLTQuMDU4NSAxMS45NjctNy45MDg5IDE1LjQwMi0zLjAxNzkgMi43MDU3LTUuODI3NiAzLjg1MDQtOC40MjkzIDMuMzMwMS0yLjQ5NzYtMC41MjAzMy00LjU3ODktMi43MDU3LTYuMTM5OC02Ljc2NDItMS4yNDg4LTMuMjI2LTEuODczMi02LjQ1Mi0xLjg3MzItOS40Njk5IDAtMi42MDE2IDAuMjA4MTMtNS4yMDMzIDAuNzI4NDYtNy41OTY3IDAuOTM2NTgtNC4yNjY3IDIuNzA1Ny04LjQyOTMgNS41MTU0LTEyLjM4NCAzLjQzNDEtNS4wOTkyIDcuMDc2NC03LjE4MDUgMTAuODIzLTYuNDUyIDIuNDk3NiAwLjUyMDMzIDQuNTc4OSAyLjcwNTcgNi4xMzk4IDYuNzY0MiAxLjI0ODggMy4yMjYgMS44NzMyIDYuNDUyIDEuODczMiA5LjQ2OTkgMC4xMDQwNiAyLjcwNTctMC4yMDgxMyA1LjMwNzMtMC43Mjg0NiA3LjcwMDh6IiBmaWxsPSIjZmZmIi8+Cjwvc3ZnPgo="},"categories":[{"id":2,"name":"Sales"}],"displayName":"WooCommerce Trigger","typeVersion":1}],"categories":[{"id":2,"name":"Sales"}],"image":[]},{"id":1765,"name":"Get Slack notifications when new product published on WooCommerce","views":79,"recentViews":9577,"totalViews":79,"createdAt":"2022-08-12T12:36:53.409Z","description":"This workflow let's a bot in Slack notify a specific channel when a new product in WooCommerce is published and live on the site. \n\n## Prerequisites\n\n[WooCommerce](https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.woocommercetrigger/) account\n[Slack](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.slack/) and a [Slack bot](https://slack.com/help/articles/115005265703-Create-a-bot-for-your-workspace)\n\n## How it works\n\n1. Listen for WooCommerce product creation\n2. If permalink starts with https://[your-url-here].com/product/\n3. Slack bot notifies channel that a new product has been added. \n\nPlease note, you must update the URL in the IF node to match your url. If your WooCommerce doesn't use the slug /product/, that will need to be updated too. \n","workflow":{"id":1016,"name":"Woocommerce to slack: notify new product created","tags":[{"id":"5","name":"FVF","createdAt":"2022-07-30T07:43:44.795Z","updatedAt":"2022-07-30T07:43:44.795Z"}],"nodes":[{"name":"If URL has /product/","type":"n8n-nodes-base.if","position":[640,300],"parameters":{"conditions":{"string":[{"value1":"={{$json[\"permalink\"]}}","value2":"https://[add-your-url-here]/product/","operation":"startsWith"}]}},"typeVersion":1},{"name":"Send message to slack","type":"n8n-nodes-base.slack","position":[920,260],"parameters":{"text":":new: A new product has been added! :new:","channel":"newproducts","blocksUi":{"blocksValues":[]},"attachments":[{"color":"#66FF00","fields":{"item":[{"short":false,"title":"Name","value":"={{$json[\"name\"]}}"},{"short":true,"title":"Price","value":"={{$json[\"regular_price\"]}}"},{"short":true,"title":"Sale Price","value":"={{$json[\"sale_price\"]}}"},{"short":false,"title":"Link","value":"={{$json[\"permalink\"]}}"}]},"footer":"=Added: {{$json[\"date_created\"]}}"}],"otherOptions":{}},"credentials":{"slackApi":{"id":"21","name":"FVF bot"}},"typeVersion":1},{"name":"On product creation","type":"n8n-nodes-base.wooCommerceTrigger","position":[460,300],"webhookId":"267c4855-6227-4d33-867e-74600097473e","parameters":{"event":"product.created"},"credentials":{"wooCommerceApi":{"id":"20","name":"WooCommerce account FVF"}},"typeVersion":1}],"active":true,"settings":{},"connections":{"On product creation":{"main":[[{"node":"If URL has /product/","type":"main","index":0}]]},"If URL has /product/":{"main":[[{"node":"Send message to slack","type":"main","index":0}]]}}},"workflowInfo":{"nodeCount":4,"nodeTypes":{"n8n-nodes-base.if":{"count":1},"n8n-nodes-base.slack":{"count":1},"n8n-nodes-base.wooCommerceTrigger":{"count":1}}},"user":{"username":"n8n-team"},"nodes":[{"id":20,"icon":"fa:map-signs","name":"n8n-nodes-base.if","defaults":{"name":"IF","color":"#408000"},"iconData":{"icon":"map-signs","type":"icon"},"categories":[{"id":9,"name":"Core Nodes"}],"displayName":"IF","typeVersion":1},{"id":40,"icon":"file:slack.svg","name":"n8n-nodes-base.slack","defaults":{"name":"Slack"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTUwLjg1MiAxNTAuODUyIiBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48dXNlIHhsaW5rOmhyZWY9IiNhIiB4PSIuOTI2IiB5PSIuOTI2Ii8+PHN5bWJvbCBpZD0iYSIgb3ZlcmZsb3c9InZpc2libGUiPjxnIHN0cm9rZS13aWR0aD0iMS44NTIiPjxwYXRoIGQ9Ik00MC43NDEgOTMuNTVjMC04LjczNSA2LjYwNy0xNS43NzIgMTQuODE1LTE1Ljc3MnMxNC44MTUgNy4wMzcgMTQuODE1IDE1Ljc3MnYzOC44MjRjMCA4LjczNy02LjYwNyAxNS43NzQtMTQuODE1IDE1Ljc3NHMtMTQuODE1LTcuMDM3LTE0LjgxNS0xNS43NzJ6IiBmaWxsPSIjZTAxZTVhIiBzdHJva2U9IiNlMDFlNWEiLz48cGF0aCBkPSJNOTMuNTUgMTA3LjQwOGMtOC43MzUgMC0xNS43NzItNi42MDctMTUuNzcyLTE0LjgxNXM3LjAzNy0xNC44MTUgMTUuNzcyLTE0LjgxNWgzOC44MjZjOC43MzUgMCAxNS43NzIgNi42MDcgMTUuNzcyIDE0LjgxNXMtNy4wMzcgMTQuODE1LTE1Ljc3MiAxNC44MTV6IiBmaWxsPSIjZWNiMjJkIiBzdHJva2U9IiNlY2IyMmQiLz48cGF0aCBkPSJNNzcuNzc4IDE1Ljc3MkM3Ny43NzggNy4wMzcgODQuMzg1IDAgOTIuNTkzIDBzMTQuODE1IDcuMDM3IDE0LjgxNSAxNS43NzJ2MzguODI2YzAgOC43MzUtNi42MDcgMTUuNzcyLTE0LjgxNSAxNS43NzJzLTE0LjgxNS03LjAzNy0xNC44MTUtMTUuNzcyeiIgZmlsbD0iIzJmYjY3YyIgc3Ryb2tlPSIjMmZiNjdjIi8+PHBhdGggZD0iTTE1Ljc3MiA3MC4zNzFDNy4wMzcgNzAuMzcxIDAgNjMuNzYzIDAgNTUuNTU2czcuMDM3LTE0LjgxNSAxNS43NzItMTQuODE1aDM4LjgyNmM4LjczNSAwIDE1Ljc3MiA2LjYwNyAxNS43NzIgMTQuODE1cy03LjAzNyAxNC44MTUtMTUuNzcyIDE0LjgxNXoiIGZpbGw9IiMzNmM1ZjEiIHN0cm9rZT0iIzM2YzVmMSIvPjxnIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiPjxwYXRoIGQ9Ik03Ny43NzggMTMzLjMzM2MwIDguMjA4IDYuNjA3IDE0LjgxNSAxNC44MTUgMTQuODE1czE0LjgxNS02LjYwNyAxNC44MTUtMTQuODE1LTYuNjA3LTE0LjgxNS0xNC44MTUtMTQuODE1SDc3Ljc3OHoiIGZpbGw9IiNlY2IyMmQiIHN0cm9rZT0iI2VjYjIyZCIvPjxwYXRoIGQ9Ik0xMzMuMzM0IDcwLjM3MWgtMTQuODE1VjU1LjU1NmMwLTguMjA3IDYuNjA3LTE0LjgxNSAxNC44MTUtMTQuODE1czE0LjgxNSA2LjYwNyAxNC44MTUgMTQuODE1LTYuNjA3IDE0LjgxNS0xNC44MTUgMTQuODE1eiIgZmlsbD0iIzJmYjY3YyIgc3Ryb2tlPSIjMmZiNjdjIi8+PHBhdGggZD0iTTE0LjgxNSA3Ny43NzhIMjkuNjN2MTQuODE1YzAgOC4yMDctNi42MDcgMTQuODE1LTE0LjgxNSAxNC44MTVTMCAxMDAuOCAwIDkyLjU5M3M2LjYwNy0xNC44MTUgMTQuODE1LTE0LjgxNXoiIGZpbGw9IiNlMDFlNWEiIHN0cm9rZT0iI2UwMWU1YSIvPjxwYXRoIGQ9Ik03MC4zNzEgMTQuODE1VjI5LjYzSDU1LjU1NmMtOC4yMDcgMC0xNC44MTUtNi42MDctMTQuODE1LTE0LjgxNVM0Ny4zNDggMCA1NS41NTYgMHMxNC44MTUgNi42MDcgMTQuODE1IDE0LjgxNXoiIGZpbGw9IiMzNmM1ZjEiIHN0cm9rZT0iIzM2YzVmMSIvPjwvZz48L2c+PC9zeW1ib2w+PC9zdmc+"},"categories":[{"id":6,"name":"Communication"}],"displayName":"Slack","typeVersion":2},{"id":42,"icon":"fa:play","name":"n8n-nodes-base.start","defaults":{"name":"Start","color":"#00e000"},"iconData":{"icon":"play","type":"icon"},"categories":[{"id":9,"name":"Core Nodes"}],"displayName":"Start","typeVersion":1},{"id":235,"icon":"file:wooCommerce.svg","name":"n8n-nodes-base.wooCommerceTrigger","defaults":{"name":"WooCommerce Trigger"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIC01MCAyNTYgMjUzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KPHRpdGxlPldvb0NvbW1lcmNlIExvZ288L3RpdGxlPgo8bWV0YWRhdGE+CjxyZGY6UkRGPgo8Y2M6V29yayByZGY6YWJvdXQ9IiI+CjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0Pgo8ZGM6dHlwZSByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIi8+CjxkYzp0aXRsZS8+CjwvY2M6V29yaz4KPC9yZGY6UkRGPgo8L21ldGFkYXRhPgo8cGF0aCBkPSJtMjMuNzU5IDBoMjA4LjM4YzEzLjE4NyAwIDIzLjg2MyAxMC42NzUgMjMuODYzIDIzLjg2M3Y3OS41NDJjMCAxMy4xODctMTAuNjc1IDIzLjg2My0yMy44NjMgMjMuODYzaC03NC43MjdsMTAuMjU3IDI1LjExOC00NS4xMDktMjUuMTE4aC05OC42OTVjLTEzLjE4NyAwLTIzLjg2My0xMC42NzUtMjMuODYzLTIzLjg2M3YtNzkuNTQyYy0wLjEwNDY2LTEzLjA4MyAxMC41NzEtMjMuODYzIDIzLjc1OC0yMy44NjN6IiBmaWxsPSIjN2Y1NGIzIi8+CjxwYXRoIGQ9Im0xNC41NzggMjEuNzVjMS40NTY5LTEuOTc3MiAzLjY0MjMtMy4wMTc5IDYuNTU2MS0zLjIyNiA1LjMwNzMtMC40MTYyNiA4LjMyNTIgMi4wODEzIDkuMDUzNyA3LjQ5MjcgMy4yMjYgMjEuNzUgNi43NjQyIDQwLjE2OSAxMC41MTEgNTUuMjU5bDIyLjc5LTQzLjM5NWMyLjA4MTMtMy45NTQ1IDQuNjgyOS02LjAzNTggNy44MDQ5LTYuMjQzOSA0LjU3ODktMC4zMTIyIDcuMzg4NiAyLjYwMTYgOC41MzMzIDguNzQxNSAyLjYwMTYgMTMuODQxIDUuOTMxNyAyNS42IDkuODg2MiAzNS41OSAyLjcwNTctMjYuNDMzIDcuMjg0Ni00NS40NzYgMTMuNzM3LTU3LjIzNiAxLjU2MS0yLjkxMzggMy44NTA0LTQuMzcwNyA2Ljg2ODMtNC41Nzg5IDIuMzkzNS0wLjIwODEzIDQuNTc4OSAwLjUyMDMzIDYuNTU2MSAyLjA4MTMgMS45NzcyIDEuNTYxIDMuMDE3OSAzLjUzODIgMy4yMjYgNS45MzE3IDAuMTA0MDYgMS44NzMyLTAuMjA4MTMgMy40MzQxLTEuMDQwNyA0Ljk5NTEtNC4wNTg1IDcuNDkyNy03LjM4ODYgMjAuMDg1LTEwLjA5NCAzNy41NjctMi42MDE2IDE2Ljk2My0zLjUzODIgMzAuMTc5LTIuOTEzOCAzOS42NDkgMC4yMDgxMyAyLjYwMTYtMC4yMDgxMyA0Ljg5MTEtMS4yNDg4IDYuODY4My0xLjI0ODggMi4yODk0LTMuMTIyIDMuNTM4Mi01LjUxNTQgMy43NDYzLTIuNzA1NyAwLjIwODEzLTUuNTE1NC0xLjA0MDYtOC4yMjExLTMuODUwNC05LjY3OC05Ljg4NjItMTcuMzc5LTI0LjY2My0yMi45OTgtNDQuMzMyLTYuNzY0MiAxMy4zMi0xMS43NTkgMjMuMzExLTE0Ljk4NSAyOS45NzEtNi4xMzk4IDExLjc1OS0xMS4zNDMgMTcuNzk1LTE1LjcxNCAxOC4xMDctMi44MDk4IDAuMjA4MTMtNS4yMDMzLTIuMTg1NC03LjI4NDYtNy4xODA1LTUuMzA3My0xMy42MzMtMTEuMDMxLTM5Ljk2MS0xNy4xNzEtNzguOTg1LTAuNDE2MjYtMi43MDU3IDAuMjA4MTMtNS4wOTkyIDEuNjY1LTYuOTcyNHptMjIzLjY0IDE2LjMzOGMtMy43NDYzLTYuNTU2MS05LjI2MTgtMTAuNTExLTE2LjY1LTEyLjA3Mi0xLjk3NzItMC40MTYyNi0zLjg1MDQtMC42MjQzOS01LjYxOTUtMC42MjQzOS05Ljk5MDIgMC0xOC4xMDcgNS4yMDMzLTI0LjQ1NSAxNS42MS01LjQxMTQgOC44NDU1LTguMTE3MSAxOC42MjgtOC4xMTcxIDI5LjM0NiAwIDguMDEzIDEuNjY1IDE0Ljg4MSA0Ljk5NTEgMjAuNjA1IDMuNzQ2MyA2LjU1NjEgOS4yNjE4IDEwLjUxMSAxNi42NSAxMi4wNzIgMS45NzcyIDAuNDE2MjYgMy44NTA0IDAuNjI0MzkgNS42MTk1IDAuNjI0MzkgMTAuMDk0IDAgMTguMjExLTUuMjAzMyAyNC40NTUtMTUuNjEgNS40MTE0LTguOTQ5NiA4LjExNzEtMTguNzMyIDguMTE3MS0yOS40NSAwLjEwNDA2LTguMTE3MS0xLjY2NS0xNC44ODEtNC45OTUxLTIwLjUwMXptLTEzLjExMiAyOC44MjZjLTEuNDU2OSA2Ljg2ODMtNC4wNTg1IDExLjk2Ny03LjkwODkgMTUuNDAyLTMuMDE3OSAyLjcwNTctNS44Mjc2IDMuODUwNC04LjQyOTMgMy4zMzAxLTIuNDk3Ni0wLjUyMDMzLTQuNTc4OS0yLjcwNTctNi4xMzk4LTYuNzY0Mi0xLjI0ODgtMy4yMjYtMS44NzMyLTYuNDUyLTEuODczMi05LjQ2OTkgMC0yLjYwMTYgMC4yMDgxMy01LjIwMzMgMC43Mjg0Ni03LjU5NjcgMC45MzY1OS00LjI2NjcgMi43MDU3LTguNDI5MyA1LjUxNTQtMTIuMzg0IDMuNDM0MS01LjA5OTIgNy4wNzY0LTcuMTgwNSAxMC44MjMtNi40NTIgMi40OTc2IDAuNTIwMzMgNC41Nzg5IDIuNzA1NyA2LjEzOTggNi43NjQyIDEuMjQ4OCAzLjIyNiAxLjg3MzIgNi40NTIgMS44NzMyIDkuNDY5OSAwIDIuNzA1Ny0wLjIwODEzIDUuMzA3My0wLjcyODQ2IDcuNzAwOHptLTUyLjAzMy0yOC44MjZjLTMuNzQ2My02LjU1NjEtOS4zNjU5LTEwLjUxMS0xNi42NS0xMi4wNzItMS45NzcyLTAuNDE2MjYtMy44NTA0LTAuNjI0MzktNS42MTk1LTAuNjI0MzktOS45OTAyIDAtMTguMTA3IDUuMjAzMy0yNC40NTUgMTUuNjEtNS40MTE0IDguODQ1NS04LjExNzEgMTguNjI4LTguMTE3MSAyOS4zNDYgMCA4LjAxMyAxLjY2NSAxNC44ODEgNC45OTUxIDIwLjYwNSAzLjc0NjMgNi41NTYxIDkuMjYxOCAxMC41MTEgMTYuNjUgMTIuMDcyIDEuOTc3MiAwLjQxNjI2IDMuODUwNCAwLjYyNDM5IDUuNjE5NSAwLjYyNDM5IDEwLjA5NCAwIDE4LjIxMS01LjIwMzMgMjQuNDU1LTE1LjYxIDUuNDExNC04Ljk0OTYgOC4xMTcxLTE4LjczMiA4LjExNzEtMjkuNDUgMC04LjExNzEtMS42NjUtMTQuODgxLTQuOTk1MS0yMC41MDF6bS0xMy4yMTYgMjguODI2Yy0xLjQ1NjkgNi44NjgzLTQuMDU4NSAxMS45NjctNy45MDg5IDE1LjQwMi0zLjAxNzkgMi43MDU3LTUuODI3NiAzLjg1MDQtOC40MjkzIDMuMzMwMS0yLjQ5NzYtMC41MjAzMy00LjU3ODktMi43MDU3LTYuMTM5OC02Ljc2NDItMS4yNDg4LTMuMjI2LTEuODczMi02LjQ1Mi0xLjg3MzItOS40Njk5IDAtMi42MDE2IDAuMjA4MTMtNS4yMDMzIDAuNzI4NDYtNy41OTY3IDAuOTM2NTgtNC4yNjY3IDIuNzA1Ny04LjQyOTMgNS41MTU0LTEyLjM4NCAzLjQzNDEtNS4wOTkyIDcuMDc2NC03LjE4MDUgMTAuODIzLTYuNDUyIDIuNDk3NiAwLjUyMDMzIDQuNTc4OSAyLjcwNTcgNi4xMzk4IDYuNzY0MiAxLjI0ODggMy4yMjYgMS44NzMyIDYuNDUyIDEuODczMiA5LjQ2OTkgMC4xMDQwNiAyLjcwNTctMC4yMDgxMyA1LjMwNzMtMC43Mjg0NiA3LjcwMDh6IiBmaWxsPSIjZmZmIi8+Cjwvc3ZnPgo="},"categories":[{"id":2,"name":"Sales"}],"displayName":"WooCommerce Trigger","typeVersion":1}],"categories":[{"id":2,"name":"Sales"}],"image":[]},{"id":1460,"name":"Notify on Slack when refund is registered in WooCommerce","views":85,"recentViews":9541,"totalViews":85,"createdAt":"2022-02-17T15:02:58.662Z","description":"This workflow uses a WooCommerce trigger that will run when an order has been updated and the status is refunded.\n\nIf the value of this is over 100 it will post it to a Slack channel.\n\nTo use this workflow you will need to set the credentials to use for the WooCommerce and Slack nodes, You will also need to pick a channel to post the message to.","workflow":{"id":82,"name":"New WooCommerce refund to Slack","nodes":[{"name":"Order Updated","type":"n8n-nodes-base.wooCommerceTrigger","position":[320,500],"webhookId":"f7736be3-e978-4a17-b936-7ce9f8ccdb72","parameters":{"event":"order.updated"},"credentials":{"wooCommerceApi":{"id":"48","name":"WooCommerce account"}},"typeVersion":1},{"name":"If Refund and Over 100","type":"n8n-nodes-base.if","position":[540,500],"parameters":{"conditions":{"number":[{"value1":"={{$json[\"total\"]}}","value2":100,"operation":"largerEqual"}],"string":[{"value1":"={{$json[\"status\"]}}","value2":"refunded"}]}},"typeVersion":1},{"name":"Send to Slack","type":"n8n-nodes-base.slack","position":[780,480],"parameters":{"text":":x: A refund has been issued :x:","channel":"woo-commerce","blocksUi":{"blocksValues":[]},"attachments":[{"color":"#FF0000","fields":{"item":[{"short":true,"title":"Order ID","value":"={{$json[\"id\"]}}"},{"short":true,"title":"Status","value":"={{$json[\"status\"]}}"},{"short":true,"title":"Total","value":"={{$json[\"currency_symbol\"]}}{{$json[\"total\"]}}"}]},"footer":"=*Order updated:* {{$json[\"date_modified\"]}}"}],"otherOptions":{}},"credentials":{"slackApi":{"id":"53","name":"Slack Access Token"}},"typeVersion":1}],"active":false,"settings":{"saveManualExecutions":true,"saveExecutionProgress":true,"saveDataSuccessExecution":"all"},"connections":{"Order Updated":{"main":[[{"node":"If Refund and Over 100","type":"main","index":0}]]},"If Refund and Over 100":{"main":[[{"node":"Send to Slack","type":"main","index":0}],[]]}}},"workflowInfo":{"nodeCount":4,"nodeTypes":{"n8n-nodes-base.if":{"count":1},"n8n-nodes-base.slack":{"count":1},"n8n-nodes-base.wooCommerceTrigger":{"count":1}}},"user":{"username":"jon-n8n"},"nodes":[{"id":20,"icon":"fa:map-signs","name":"n8n-nodes-base.if","defaults":{"name":"IF","color":"#408000"},"iconData":{"icon":"map-signs","type":"icon"},"categories":[{"id":9,"name":"Core Nodes"}],"displayName":"IF","typeVersion":1},{"id":40,"icon":"file:slack.svg","name":"n8n-nodes-base.slack","defaults":{"name":"Slack"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTUwLjg1MiAxNTAuODUyIiBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48dXNlIHhsaW5rOmhyZWY9IiNhIiB4PSIuOTI2IiB5PSIuOTI2Ii8+PHN5bWJvbCBpZD0iYSIgb3ZlcmZsb3c9InZpc2libGUiPjxnIHN0cm9rZS13aWR0aD0iMS44NTIiPjxwYXRoIGQ9Ik00MC43NDEgOTMuNTVjMC04LjczNSA2LjYwNy0xNS43NzIgMTQuODE1LTE1Ljc3MnMxNC44MTUgNy4wMzcgMTQuODE1IDE1Ljc3MnYzOC44MjRjMCA4LjczNy02LjYwNyAxNS43NzQtMTQuODE1IDE1Ljc3NHMtMTQuODE1LTcuMDM3LTE0LjgxNS0xNS43NzJ6IiBmaWxsPSIjZTAxZTVhIiBzdHJva2U9IiNlMDFlNWEiLz48cGF0aCBkPSJNOTMuNTUgMTA3LjQwOGMtOC43MzUgMC0xNS43NzItNi42MDctMTUuNzcyLTE0LjgxNXM3LjAzNy0xNC44MTUgMTUuNzcyLTE0LjgxNWgzOC44MjZjOC43MzUgMCAxNS43NzIgNi42MDcgMTUuNzcyIDE0LjgxNXMtNy4wMzcgMTQuODE1LTE1Ljc3MiAxNC44MTV6IiBmaWxsPSIjZWNiMjJkIiBzdHJva2U9IiNlY2IyMmQiLz48cGF0aCBkPSJNNzcuNzc4IDE1Ljc3MkM3Ny43NzggNy4wMzcgODQuMzg1IDAgOTIuNTkzIDBzMTQuODE1IDcuMDM3IDE0LjgxNSAxNS43NzJ2MzguODI2YzAgOC43MzUtNi42MDcgMTUuNzcyLTE0LjgxNSAxNS43NzJzLTE0LjgxNS03LjAzNy0xNC44MTUtMTUuNzcyeiIgZmlsbD0iIzJmYjY3YyIgc3Ryb2tlPSIjMmZiNjdjIi8+PHBhdGggZD0iTTE1Ljc3MiA3MC4zNzFDNy4wMzcgNzAuMzcxIDAgNjMuNzYzIDAgNTUuNTU2czcuMDM3LTE0LjgxNSAxNS43NzItMTQuODE1aDM4LjgyNmM4LjczNSAwIDE1Ljc3MiA2LjYwNyAxNS43NzIgMTQuODE1cy03LjAzNyAxNC44MTUtMTUuNzcyIDE0LjgxNXoiIGZpbGw9IiMzNmM1ZjEiIHN0cm9rZT0iIzM2YzVmMSIvPjxnIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiPjxwYXRoIGQ9Ik03Ny43NzggMTMzLjMzM2MwIDguMjA4IDYuNjA3IDE0LjgxNSAxNC44MTUgMTQuODE1czE0LjgxNS02LjYwNyAxNC44MTUtMTQuODE1LTYuNjA3LTE0LjgxNS0xNC44MTUtMTQuODE1SDc3Ljc3OHoiIGZpbGw9IiNlY2IyMmQiIHN0cm9rZT0iI2VjYjIyZCIvPjxwYXRoIGQ9Ik0xMzMuMzM0IDcwLjM3MWgtMTQuODE1VjU1LjU1NmMwLTguMjA3IDYuNjA3LTE0LjgxNSAxNC44MTUtMTQuODE1czE0LjgxNSA2LjYwNyAxNC44MTUgMTQuODE1LTYuNjA3IDE0LjgxNS0xNC44MTUgMTQuODE1eiIgZmlsbD0iIzJmYjY3YyIgc3Ryb2tlPSIjMmZiNjdjIi8+PHBhdGggZD0iTTE0LjgxNSA3Ny43NzhIMjkuNjN2MTQuODE1YzAgOC4yMDctNi42MDcgMTQuODE1LTE0LjgxNSAxNC44MTVTMCAxMDAuOCAwIDkyLjU5M3M2LjYwNy0xNC44MTUgMTQuODE1LTE0LjgxNXoiIGZpbGw9IiNlMDFlNWEiIHN0cm9rZT0iI2UwMWU1YSIvPjxwYXRoIGQ9Ik03MC4zNzEgMTQuODE1VjI5LjYzSDU1LjU1NmMtOC4yMDcgMC0xNC44MTUtNi42MDctMTQuODE1LTE0LjgxNVM0Ny4zNDggMCA1NS41NTYgMHMxNC44MTUgNi42MDcgMTQuODE1IDE0LjgxNXoiIGZpbGw9IiMzNmM1ZjEiIHN0cm9rZT0iIzM2YzVmMSIvPjwvZz48L2c+PC9zeW1ib2w+PC9zdmc+"},"categories":[{"id":6,"name":"Communication"}],"displayName":"Slack","typeVersion":2},{"id":42,"icon":"fa:play","name":"n8n-nodes-base.start","defaults":{"name":"Start","color":"#00e000"},"iconData":{"icon":"play","type":"icon"},"categories":[{"id":9,"name":"Core Nodes"}],"displayName":"Start","typeVersion":1},{"id":235,"icon":"file:wooCommerce.svg","name":"n8n-nodes-base.wooCommerceTrigger","defaults":{"name":"WooCommerce Trigger"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIC01MCAyNTYgMjUzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KPHRpdGxlPldvb0NvbW1lcmNlIExvZ288L3RpdGxlPgo8bWV0YWRhdGE+CjxyZGY6UkRGPgo8Y2M6V29yayByZGY6YWJvdXQ9IiI+CjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0Pgo8ZGM6dHlwZSByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIi8+CjxkYzp0aXRsZS8+CjwvY2M6V29yaz4KPC9yZGY6UkRGPgo8L21ldGFkYXRhPgo8cGF0aCBkPSJtMjMuNzU5IDBoMjA4LjM4YzEzLjE4NyAwIDIzLjg2MyAxMC42NzUgMjMuODYzIDIzLjg2M3Y3OS41NDJjMCAxMy4xODctMTAuNjc1IDIzLjg2My0yMy44NjMgMjMuODYzaC03NC43MjdsMTAuMjU3IDI1LjExOC00NS4xMDktMjUuMTE4aC05OC42OTVjLTEzLjE4NyAwLTIzLjg2My0xMC42NzUtMjMuODYzLTIzLjg2M3YtNzkuNTQyYy0wLjEwNDY2LTEzLjA4MyAxMC41NzEtMjMuODYzIDIzLjc1OC0yMy44NjN6IiBmaWxsPSIjN2Y1NGIzIi8+CjxwYXRoIGQ9Im0xNC41NzggMjEuNzVjMS40NTY5LTEuOTc3MiAzLjY0MjMtMy4wMTc5IDYuNTU2MS0zLjIyNiA1LjMwNzMtMC40MTYyNiA4LjMyNTIgMi4wODEzIDkuMDUzNyA3LjQ5MjcgMy4yMjYgMjEuNzUgNi43NjQyIDQwLjE2OSAxMC41MTEgNTUuMjU5bDIyLjc5LTQzLjM5NWMyLjA4MTMtMy45NTQ1IDQuNjgyOS02LjAzNTggNy44MDQ5LTYuMjQzOSA0LjU3ODktMC4zMTIyIDcuMzg4NiAyLjYwMTYgOC41MzMzIDguNzQxNSAyLjYwMTYgMTMuODQxIDUuOTMxNyAyNS42IDkuODg2MiAzNS41OSAyLjcwNTctMjYuNDMzIDcuMjg0Ni00NS40NzYgMTMuNzM3LTU3LjIzNiAxLjU2MS0yLjkxMzggMy44NTA0LTQuMzcwNyA2Ljg2ODMtNC41Nzg5IDIuMzkzNS0wLjIwODEzIDQuNTc4OSAwLjUyMDMzIDYuNTU2MSAyLjA4MTMgMS45NzcyIDEuNTYxIDMuMDE3OSAzLjUzODIgMy4yMjYgNS45MzE3IDAuMTA0MDYgMS44NzMyLTAuMjA4MTMgMy40MzQxLTEuMDQwNyA0Ljk5NTEtNC4wNTg1IDcuNDkyNy03LjM4ODYgMjAuMDg1LTEwLjA5NCAzNy41NjctMi42MDE2IDE2Ljk2My0zLjUzODIgMzAuMTc5LTIuOTEzOCAzOS42NDkgMC4yMDgxMyAyLjYwMTYtMC4yMDgxMyA0Ljg5MTEtMS4yNDg4IDYuODY4My0xLjI0ODggMi4yODk0LTMuMTIyIDMuNTM4Mi01LjUxNTQgMy43NDYzLTIuNzA1NyAwLjIwODEzLTUuNTE1NC0xLjA0MDYtOC4yMjExLTMuODUwNC05LjY3OC05Ljg4NjItMTcuMzc5LTI0LjY2My0yMi45OTgtNDQuMzMyLTYuNzY0MiAxMy4zMi0xMS43NTkgMjMuMzExLTE0Ljk4NSAyOS45NzEtNi4xMzk4IDExLjc1OS0xMS4zNDMgMTcuNzk1LTE1LjcxNCAxOC4xMDctMi44MDk4IDAuMjA4MTMtNS4yMDMzLTIuMTg1NC03LjI4NDYtNy4xODA1LTUuMzA3My0xMy42MzMtMTEuMDMxLTM5Ljk2MS0xNy4xNzEtNzguOTg1LTAuNDE2MjYtMi43MDU3IDAuMjA4MTMtNS4wOTkyIDEuNjY1LTYuOTcyNHptMjIzLjY0IDE2LjMzOGMtMy43NDYzLTYuNTU2MS05LjI2MTgtMTAuNTExLTE2LjY1LTEyLjA3Mi0xLjk3NzItMC40MTYyNi0zLjg1MDQtMC42MjQzOS01LjYxOTUtMC42MjQzOS05Ljk5MDIgMC0xOC4xMDcgNS4yMDMzLTI0LjQ1NSAxNS42MS01LjQxMTQgOC44NDU1LTguMTE3MSAxOC42MjgtOC4xMTcxIDI5LjM0NiAwIDguMDEzIDEuNjY1IDE0Ljg4MSA0Ljk5NTEgMjAuNjA1IDMuNzQ2MyA2LjU1NjEgOS4yNjE4IDEwLjUxMSAxNi42NSAxMi4wNzIgMS45NzcyIDAuNDE2MjYgMy44NTA0IDAuNjI0MzkgNS42MTk1IDAuNjI0MzkgMTAuMDk0IDAgMTguMjExLTUuMjAzMyAyNC40NTUtMTUuNjEgNS40MTE0LTguOTQ5NiA4LjExNzEtMTguNzMyIDguMTE3MS0yOS40NSAwLjEwNDA2LTguMTE3MS0xLjY2NS0xNC44ODEtNC45OTUxLTIwLjUwMXptLTEzLjExMiAyOC44MjZjLTEuNDU2OSA2Ljg2ODMtNC4wNTg1IDExLjk2Ny03LjkwODkgMTUuNDAyLTMuMDE3OSAyLjcwNTctNS44Mjc2IDMuODUwNC04LjQyOTMgMy4zMzAxLTIuNDk3Ni0wLjUyMDMzLTQuNTc4OS0yLjcwNTctNi4xMzk4LTYuNzY0Mi0xLjI0ODgtMy4yMjYtMS44NzMyLTYuNDUyLTEuODczMi05LjQ2OTkgMC0yLjYwMTYgMC4yMDgxMy01LjIwMzMgMC43Mjg0Ni03LjU5NjcgMC45MzY1OS00LjI2NjcgMi43MDU3LTguNDI5MyA1LjUxNTQtMTIuMzg0IDMuNDM0MS01LjA5OTIgNy4wNzY0LTcuMTgwNSAxMC44MjMtNi40NTIgMi40OTc2IDAuNTIwMzMgNC41Nzg5IDIuNzA1NyA2LjEzOTggNi43NjQyIDEuMjQ4OCAzLjIyNiAxLjg3MzIgNi40NTIgMS44NzMyIDkuNDY5OSAwIDIuNzA1Ny0wLjIwODEzIDUuMzA3My0wLjcyODQ2IDcuNzAwOHptLTUyLjAzMy0yOC44MjZjLTMuNzQ2My02LjU1NjEtOS4zNjU5LTEwLjUxMS0xNi42NS0xMi4wNzItMS45NzcyLTAuNDE2MjYtMy44NTA0LTAuNjI0MzktNS42MTk1LTAuNjI0MzktOS45OTAyIDAtMTguMTA3IDUuMjAzMy0yNC40NTUgMTUuNjEtNS40MTE0IDguODQ1NS04LjExNzEgMTguNjI4LTguMTE3MSAyOS4zNDYgMCA4LjAxMyAxLjY2NSAxNC44ODEgNC45OTUxIDIwLjYwNSAzLjc0NjMgNi41NTYxIDkuMjYxOCAxMC41MTEgMTYuNjUgMTIuMDcyIDEuOTc3MiAwLjQxNjI2IDMuODUwNCAwLjYyNDM5IDUuNjE5NSAwLjYyNDM5IDEwLjA5NCAwIDE4LjIxMS01LjIwMzMgMjQuNDU1LTE1LjYxIDUuNDExNC04Ljk0OTYgOC4xMTcxLTE4LjczMiA4LjExNzEtMjkuNDUgMC04LjExNzEtMS42NjUtMTQuODgxLTQuOTk1MS0yMC41MDF6bS0xMy4yMTYgMjguODI2Yy0xLjQ1NjkgNi44NjgzLTQuMDU4NSAxMS45NjctNy45MDg5IDE1LjQwMi0zLjAxNzkgMi43MDU3LTUuODI3NiAzLjg1MDQtOC40MjkzIDMuMzMwMS0yLjQ5NzYtMC41MjAzMy00LjU3ODktMi43MDU3LTYuMTM5OC02Ljc2NDItMS4yNDg4LTMuMjI2LTEuODczMi02LjQ1Mi0xLjg3MzItOS40Njk5IDAtMi42MDE2IDAuMjA4MTMtNS4yMDMzIDAuNzI4NDYtNy41OTY3IDAuOTM2NTgtNC4yNjY3IDIuNzA1Ny04LjQyOTMgNS41MTU0LTEyLjM4NCAzLjQzNDEtNS4wOTkyIDcuMDc2NC03LjE4MDUgMTAuODIzLTYuNDUyIDIuNDk3NiAwLjUyMDMzIDQuNTc4OSAyLjcwNTcgNi4xMzk4IDYuNzY0MiAxLjI0ODggMy4yMjYgMS44NzMyIDYuNDUyIDEuODczMiA5LjQ2OTkgMC4xMDQwNiAyLjcwNTctMC4yMDgxMyA1LjMwNzMtMC43Mjg0NiA3LjcwMDh6IiBmaWxsPSIjZmZmIi8+Cjwvc3ZnPgo="},"categories":[{"id":2,"name":"Sales"}],"displayName":"WooCommerce Trigger","typeVersion":1}],"categories":[{"id":2,"name":"Sales"},{"id":8,"name":"Finance & Accounting"}],"image":[]}],"image":[]}} +{ + "collection": { + "id": 1, + "name": "eCommerce Starter Pack", + "description": "eCommerce operations are complex — but there are many things that you can automate to make your life easier. This collection provides a few ideas to get started.\n\nReduce manual work and the risk of human error by automating processes such as social media promotion of products, updating customer databases, and get notifications for important events.", + "totalViews": 0, + "createdAt": "2022-02-17T12:40:50.498Z", + "nodes": [ + { + "id": 20, + "name": "n8n-nodes-base.if", + "defaults": { + "name": "IF", + "color": "#408000" + }, + "displayName": "IF", + "icon": "fa:map-signs", + "iconData": { + "icon": "map-signs", + "type": "icon" + }, + "typeVersion": 1, + "categories": [ + { + "id": 9, + "name": "Core Nodes" + } + ] + }, + { + "id": 49, + "name": "n8n-nodes-base.telegram", + "defaults": { + "name": "Telegram" + }, + "displayName": "Telegram", + "icon": "file:telegram.svg", + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNjYgNjYiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48ZyBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZD0iTTAgMzJjMCAxNy42NzMgMTQuMzI3IDMyIDMyIDMyczMyLTE0LjMyNyAzMi0zMlM0OS42NzMgMCAzMiAwIDAgMTQuMzI3IDAgMzIiIGZpbGw9IiMzN2FlZTIiLz48cGF0aCBkPSJNMjEuNjYxIDM0LjMzOGwzLjc5NyAxMC41MDhzLjQ3NS45ODMuOTgzLjk4MyA4LjA2OC03Ljg2NCA4LjA2OC03Ljg2NGw4LjQwNy0xNi4yMzctMjEuMTE5IDkuODk4eiIgZmlsbD0iI2M4ZGFlYSIvPjxwYXRoIGQ9Ik0yNi42OTUgMzcuMDM0bC0uNzI5IDcuNzQ2cy0uMzA1IDIuMzczIDIuMDY4IDBsNC42NDQtNC4yMDMiIGZpbGw9IiNhOWM2ZDgiLz48cGF0aCBkPSJNMjEuNzMgMzQuNzEybC03LjgwOS0yLjU0NXMtLjkzMi0uMzc4LS42MzMtMS4yMzdjLjA2Mi0uMTc3LjE4Ni0uMzI4LjU1OS0uNTg4IDEuNzMxLTEuMjA2IDMyLjAyOC0xMi4wOTYgMzIuMDI4LTEyLjA5NnMuODU2LS4yODggMS4zNjEtLjA5N2MuMjMxLjA4OC4zNzguMTg3LjUwMy41NDguMDQ1LjEzMi4wNzEuNDExLjA2OC42ODktLjAwMy4yMDEtLjAyNy4zODYtLjA0NS42NzgtLjE4NCAyLjk3OC01LjcwNiAyNS4xOTgtNS43MDYgMjUuMTk4cy0uMzMgMS4zLTEuNTE0IDEuMzQ1Yy0uNDMyLjAxNi0uOTU2LS4wNzEtMS41ODItLjYxLTIuMzIzLTEuOTk4LTEwLjM1Mi03LjM5NC0xMi4xMjYtOC41OGEuMzQuMzQgMCAwMS0uMTQ2LS4yMzljLS4wMjUtLjEyNS4xMDgtLjI4LjEwOC0uMjhzMTMuOTgtMTIuNDI3IDE0LjM1Mi0xMy43MzFjLjAyOS0uMTAxLS4wNzktLjE1MS0uMjI2LS4xMDctLjkyOS4zNDItMTcuMDI1IDEwLjUwNi0xOC44MDEgMTEuNjI5LS4xMDQuMDY2LS4zOTUuMDIzLS4zOTUuMDIzIi8+PC9nPjwvc3ltYm9sPjwvc3ZnPg==" + }, + "typeVersion": 1, + "categories": [ + { + "id": 6, + "name": "Communication" + } + ] + }, + { + "id": 107, + "name": "n8n-nodes-base.shopifyTrigger", + "defaults": { + "name": "Shopify Trigger" + }, + "displayName": "Shopify Trigger", + "icon": "file:shopify.svg", + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNTggNjYiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48ZyBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZD0iTTQ5LjI1NSAxMi40ODRhLjYzMy42MzMgMCAwMC0uNTY0LS41MjdjLS4yMjUtLjAzNy01LjE3LS4zNzYtNS4xNy0uMzc2bC0zLjc3LTMuNzdjLS4zNC0uMzc2LTEuMDkyLS4yNjYtMS4zNzYtLjE4OC0uMDM3IDAtLjc1Mi4yMjUtMS45MjIuNjA1LTEuMTM3LTMuMy0zLjE1LTYuMzA2LTYuNjk2LTYuMzA2aC0uMzAzQzI4LjQzOC42MDUgMjcuMTk0IDAgMjYuMTQ0IDBjLTguMjU2LjAzNy0xMi4yIDEwLjMzMy0xMy40MzQgMTUuNTk0bC01Ljc3IDEuNzdjLTEuNzcuNTY0LTEuODM1LjYwNS0yLjA3MyAyLjI5M0wwIDU3LjE3NSAzNi40NjggNjRsMTkuNzYzLTQuMjZjMC0uMDM3LTYuOTQtNDYuODk3LTYuOTc2LTQ3LjI1NXpNMzQuNDMxIDguODZjLS45MTcuMzAzLTEuOTYzLjYwNS0zLjEuOTQ1di0uNjhhMTUuMDMgMTUuMDMgMCAwMC0uNzUyLTQuOTk5YzEuODQ4LjI4NCAzLjEgMi4zNTcgMy44NDMgNC43MzN6bS02LjA2OC00LjI5OGMuNjAzIDEuNzc4Ljg4MyAzLjY1LjgyNiA1LjUyN3YuMzRsLTYuMzc1IDEuOTYzYzEuMjQ4LTQuNjYgMy41NS02Ljk2MiA1LjU1LTcuODN6bS0yLjQ1LTIuMjkzYTEuOTQgMS45NCAwIDAxMS4wNTUuMzM5Yy0yLjY2IDEuMjM4LTUuNDcyIDQuMzY2LTYuNjc4IDEwLjYyN2wtNS4wNDUgMS41NDZDMTYuNjY4IDEwLjAzIDE5Ljk4OCAyLjI2IDI1LjkxIDIuMjZ6IiBmaWxsPSIjOTViZjQ3Ii8+PHBhdGggZD0iTTQ4LjY5MSAxMS45NTdjLS4yMjUtLjAzNy01LjE3LS4zNzYtNS4xNy0uMzc2bC0zLjc3LTMuNzdhLjc1My43NTMgMCAwMC0uNTI3LS4yMjVMMzYuNDcyIDY0bDE5Ljc2My00LjI2LTYuOTgtNDcuMjE4YS42OC42OCAwIDAwLS41NjQtLjU2NHoiIGZpbGw9IiM1ZThlM2UiLz48cGF0aCBkPSJNMjkuNzU4IDIyLjlsLTIuNDU0IDcuMjQyYTExLjM2IDExLjM2IDAgMDAtNC43NTItMS4xMzNjLTMuODQ4IDAtNC4wMzYgMi40MTItNC4wMzYgMy4wMTggMCAzLjI5OCA4LjYzNiA0LjU2NCA4LjYzNiAxMi4zMzMgMCA2LjEtMy44ODUgMTAuMDMtOS4xIDEwLjAzLTYuMjYgMC05LjQ2Ny0zLjg4NS05LjQ2Ny0zLjg4NWwxLjY2NS01LjU1czMuMjggMi44MyA2LjA3MyAyLjgzYTIuNDcgMi40NyAwIDAwMi41NjQtMi40OWMwLTQuMzQtNy4xLTQuNTI3LTcuMS0xMS42MTggMC01Ljk2MiA0LjI5OC0xMS43NyAxMi45MzQtMTEuNzcgMy4zOTQuMDUgNS4wMTggMSA1LjAxOCAxeiIvPjwvZz48L3N5bWJvbD48L3N2Zz4=" + }, + "typeVersion": 1, + "categories": [ + { + "id": 2, + "name": "Sales" + } + ] + }, + { + "id": 126, + "name": "n8n-nodes-base.mautic", + "defaults": { + "name": "Mautic" + }, + "displayName": "Mautic", + "icon": "file:mautic.svg", + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNi4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iMzQ5Ljc3OXB4IiBoZWlnaHQ9IjM0OS43NzlweCIgdmlld0JveD0iMCAwIDM0OS43NzkgMzQ5Ljc3OSIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMzQ5Ljc3OSAzNDkuNzc5Ig0KCSB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxwYXRoIGZpbGw9IiNGRkZGRkYiIGQ9Ik0xNzQuODksMzQ5Ljc3OUM3OC42MTIsMzQ5Ljc3OSwwLDI3MS40NjIsMCwxNzQuODlTNzguNjEyLDAsMTc0Ljg5LDBjMjMuMjYsMCw0NS45MzEsNC40MTcsNjcuMTI5LDEzLjU0Mw0KCWM1Ljg4OSwyLjY1LDguODMzLDkuNDIyLDYuNDc4LDE1LjYwNWMtMi42NDksNS44ODgtOS40MjEsOC44MzMtMTUuNjA0LDYuNDc3Yy0xOC41NDktNy42NTUtMzcuOTgtMTEuNDgyLTU4LjAwMi0xMS40ODINCgljLTgzLjMyMywwLTE1MS4wNDEsNjcuNzE4LTE1MS4wNDEsMTUxLjA0MVM5MS41NjcsMzI2LjIyNSwxNzQuODksMzI2LjIyNWM4My4zMjMsMCwxNTEuMDQxLTY3LjcxOCwxNTEuMDQxLTE1MS4wNDENCgljMC0xNy45Ni0yLjk0NC0zNS4zMzItOS4xMjctNTEuODE5Yy0yLjM1NS02LjE4MywwLjg4My0xMi45NTUsNy4wNjYtMTUuMzFjNi4xODMtMi4zNTUsMTIuOTU0LDAuODgzLDE1LjMxLDcuMDY2DQoJYzcuMDY2LDE5LjEzOCwxMC42LDM5LjQ1MywxMC42LDYwLjA2M0MzNDkuNzc5LDI3MS4xNjcsMjcxLjQ2MiwzNDkuNzc5LDE3NC44OSwzNDkuNzc5Ii8+DQo8Zz4NCgk8cG9seWdvbiBmaWxsPSIjRkRCOTMzIiBwb2ludHM9IjI1MS40NCwxNTYuOTMgMjI0LjM1NCwxODUuMTk0IDIzOS4zNjksMjQ4LjQ5NiAyNzMuNTIyLDI0OC40OTYgCSIvPg0KPC9nPg0KPHBvbHlnb24gZmlsbD0iI0ZEQjkzMyIgcG9pbnRzPSIyNDAuMjUzLDczLjMxMiAyNDkuNjc0LDgyLjczNCAxNzQuODksMTYxLjkzNSAxMTAuOTk5LDk2LjI3NyA3NC4xOTYsMjQ4LjQ5NiAxMDguMzUsMjQ4LjQ5NiANCgkxMjguNjY1LDE2My45OTYgMTc0Ljg5LDIxNC4zNDMgMjczLjgxNywxMDYuNTgzIDI4My4yMzksMTE2LjI5OSAyOTIuNjYsNjMuMDA3ICIvPg0KPC9zdmc+DQo=" + }, + "typeVersion": 1, + "categories": [ + { + "id": 1, + "name": "Marketing & Content" + }, + { + "id": 6, + "name": "Communication" + } + ] + }, + { + "id": 235, + "name": "n8n-nodes-base.wooCommerceTrigger", + "defaults": { + "name": "WooCommerce Trigger" + }, + "displayName": "WooCommerce Trigger", + "icon": "file:wooCommerce.svg", + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIC01MCAyNTYgMjUzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KPHRpdGxlPldvb0NvbW1lcmNlIExvZ288L3RpdGxlPgo8bWV0YWRhdGE+CjxyZGY6UkRGPgo8Y2M6V29yayByZGY6YWJvdXQ9IiI+CjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0Pgo8ZGM6dHlwZSByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIi8+CjxkYzp0aXRsZS8+CjwvY2M6V29yaz4KPC9yZGY6UkRGPgo8L21ldGFkYXRhPgo8cGF0aCBkPSJtMjMuNzU5IDBoMjA4LjM4YzEzLjE4NyAwIDIzLjg2MyAxMC42NzUgMjMuODYzIDIzLjg2M3Y3OS41NDJjMCAxMy4xODctMTAuNjc1IDIzLjg2My0yMy44NjMgMjMuODYzaC03NC43MjdsMTAuMjU3IDI1LjExOC00NS4xMDktMjUuMTE4aC05OC42OTVjLTEzLjE4NyAwLTIzLjg2My0xMC42NzUtMjMuODYzLTIzLjg2M3YtNzkuNTQyYy0wLjEwNDY2LTEzLjA4MyAxMC41NzEtMjMuODYzIDIzLjc1OC0yMy44NjN6IiBmaWxsPSIjN2Y1NGIzIi8+CjxwYXRoIGQ9Im0xNC41NzggMjEuNzVjMS40NTY5LTEuOTc3MiAzLjY0MjMtMy4wMTc5IDYuNTU2MS0zLjIyNiA1LjMwNzMtMC40MTYyNiA4LjMyNTIgMi4wODEzIDkuMDUzNyA3LjQ5MjcgMy4yMjYgMjEuNzUgNi43NjQyIDQwLjE2OSAxMC41MTEgNTUuMjU5bDIyLjc5LTQzLjM5NWMyLjA4MTMtMy45NTQ1IDQuNjgyOS02LjAzNTggNy44MDQ5LTYuMjQzOSA0LjU3ODktMC4zMTIyIDcuMzg4NiAyLjYwMTYgOC41MzMzIDguNzQxNSAyLjYwMTYgMTMuODQxIDUuOTMxNyAyNS42IDkuODg2MiAzNS41OSAyLjcwNTctMjYuNDMzIDcuMjg0Ni00NS40NzYgMTMuNzM3LTU3LjIzNiAxLjU2MS0yLjkxMzggMy44NTA0LTQuMzcwNyA2Ljg2ODMtNC41Nzg5IDIuMzkzNS0wLjIwODEzIDQuNTc4OSAwLjUyMDMzIDYuNTU2MSAyLjA4MTMgMS45NzcyIDEuNTYxIDMuMDE3OSAzLjUzODIgMy4yMjYgNS45MzE3IDAuMTA0MDYgMS44NzMyLTAuMjA4MTMgMy40MzQxLTEuMDQwNyA0Ljk5NTEtNC4wNTg1IDcuNDkyNy03LjM4ODYgMjAuMDg1LTEwLjA5NCAzNy41NjctMi42MDE2IDE2Ljk2My0zLjUzODIgMzAuMTc5LTIuOTEzOCAzOS42NDkgMC4yMDgxMyAyLjYwMTYtMC4yMDgxMyA0Ljg5MTEtMS4yNDg4IDYuODY4My0xLjI0ODggMi4yODk0LTMuMTIyIDMuNTM4Mi01LjUxNTQgMy43NDYzLTIuNzA1NyAwLjIwODEzLTUuNTE1NC0xLjA0MDYtOC4yMjExLTMuODUwNC05LjY3OC05Ljg4NjItMTcuMzc5LTI0LjY2My0yMi45OTgtNDQuMzMyLTYuNzY0MiAxMy4zMi0xMS43NTkgMjMuMzExLTE0Ljk4NSAyOS45NzEtNi4xMzk4IDExLjc1OS0xMS4zNDMgMTcuNzk1LTE1LjcxNCAxOC4xMDctMi44MDk4IDAuMjA4MTMtNS4yMDMzLTIuMTg1NC03LjI4NDYtNy4xODA1LTUuMzA3My0xMy42MzMtMTEuMDMxLTM5Ljk2MS0xNy4xNzEtNzguOTg1LTAuNDE2MjYtMi43MDU3IDAuMjA4MTMtNS4wOTkyIDEuNjY1LTYuOTcyNHptMjIzLjY0IDE2LjMzOGMtMy43NDYzLTYuNTU2MS05LjI2MTgtMTAuNTExLTE2LjY1LTEyLjA3Mi0xLjk3NzItMC40MTYyNi0zLjg1MDQtMC42MjQzOS01LjYxOTUtMC42MjQzOS05Ljk5MDIgMC0xOC4xMDcgNS4yMDMzLTI0LjQ1NSAxNS42MS01LjQxMTQgOC44NDU1LTguMTE3MSAxOC42MjgtOC4xMTcxIDI5LjM0NiAwIDguMDEzIDEuNjY1IDE0Ljg4MSA0Ljk5NTEgMjAuNjA1IDMuNzQ2MyA2LjU1NjEgOS4yNjE4IDEwLjUxMSAxNi42NSAxMi4wNzIgMS45NzcyIDAuNDE2MjYgMy44NTA0IDAuNjI0MzkgNS42MTk1IDAuNjI0MzkgMTAuMDk0IDAgMTguMjExLTUuMjAzMyAyNC40NTUtMTUuNjEgNS40MTE0LTguOTQ5NiA4LjExNzEtMTguNzMyIDguMTE3MS0yOS40NSAwLjEwNDA2LTguMTE3MS0xLjY2NS0xNC44ODEtNC45OTUxLTIwLjUwMXptLTEzLjExMiAyOC44MjZjLTEuNDU2OSA2Ljg2ODMtNC4wNTg1IDExLjk2Ny03LjkwODkgMTUuNDAyLTMuMDE3OSAyLjcwNTctNS44Mjc2IDMuODUwNC04LjQyOTMgMy4zMzAxLTIuNDk3Ni0wLjUyMDMzLTQuNTc4OS0yLjcwNTctNi4xMzk4LTYuNzY0Mi0xLjI0ODgtMy4yMjYtMS44NzMyLTYuNDUyLTEuODczMi05LjQ2OTkgMC0yLjYwMTYgMC4yMDgxMy01LjIwMzMgMC43Mjg0Ni03LjU5NjcgMC45MzY1OS00LjI2NjcgMi43MDU3LTguNDI5MyA1LjUxNTQtMTIuMzg0IDMuNDM0MS01LjA5OTIgNy4wNzY0LTcuMTgwNSAxMC44MjMtNi40NTIgMi40OTc2IDAuNTIwMzMgNC41Nzg5IDIuNzA1NyA2LjEzOTggNi43NjQyIDEuMjQ4OCAzLjIyNiAxLjg3MzIgNi40NTIgMS44NzMyIDkuNDY5OSAwIDIuNzA1Ny0wLjIwODEzIDUuMzA3My0wLjcyODQ2IDcuNzAwOHptLTUyLjAzMy0yOC44MjZjLTMuNzQ2My02LjU1NjEtOS4zNjU5LTEwLjUxMS0xNi42NS0xMi4wNzItMS45NzcyLTAuNDE2MjYtMy44NTA0LTAuNjI0MzktNS42MTk1LTAuNjI0MzktOS45OTAyIDAtMTguMTA3IDUuMjAzMy0yNC40NTUgMTUuNjEtNS40MTE0IDguODQ1NS04LjExNzEgMTguNjI4LTguMTE3MSAyOS4zNDYgMCA4LjAxMyAxLjY2NSAxNC44ODEgNC45OTUxIDIwLjYwNSAzLjc0NjMgNi41NTYxIDkuMjYxOCAxMC41MTEgMTYuNjUgMTIuMDcyIDEuOTc3MiAwLjQxNjI2IDMuODUwNCAwLjYyNDM5IDUuNjE5NSAwLjYyNDM5IDEwLjA5NCAwIDE4LjIxMS01LjIwMzMgMjQuNDU1LTE1LjYxIDUuNDExNC04Ljk0OTYgOC4xMTcxLTE4LjczMiA4LjExNzEtMjkuNDUgMC04LjExNzEtMS42NjUtMTQuODgxLTQuOTk1MS0yMC41MDF6bS0xMy4yMTYgMjguODI2Yy0xLjQ1NjkgNi44NjgzLTQuMDU4NSAxMS45NjctNy45MDg5IDE1LjQwMi0zLjAxNzkgMi43MDU3LTUuODI3NiAzLjg1MDQtOC40MjkzIDMuMzMwMS0yLjQ5NzYtMC41MjAzMy00LjU3ODktMi43MDU3LTYuMTM5OC02Ljc2NDItMS4yNDg4LTMuMjI2LTEuODczMi02LjQ1Mi0xLjg3MzItOS40Njk5IDAtMi42MDE2IDAuMjA4MTMtNS4yMDMzIDAuNzI4NDYtNy41OTY3IDAuOTM2NTgtNC4yNjY3IDIuNzA1Ny04LjQyOTMgNS41MTU0LTEyLjM4NCAzLjQzNDEtNS4wOTkyIDcuMDc2NC03LjE4MDUgMTAuODIzLTYuNDUyIDIuNDk3NiAwLjUyMDMzIDQuNTc4OSAyLjcwNTcgNi4xMzk4IDYuNzY0MiAxLjI0ODggMy4yMjYgMS44NzMyIDYuNDUyIDEuODczMiA5LjQ2OTkgMC4xMDQwNiAyLjcwNTctMC4yMDgxMyA1LjMwNzMtMC43Mjg0NiA3LjcwMDh6IiBmaWxsPSIjZmZmIi8+Cjwvc3ZnPgo=" + }, + "typeVersion": 1, + "categories": [ + { + "id": 2, + "name": "Sales" + } + ] + }, + { + "id": 325, + "name": "n8n-nodes-base.twitter", + "defaults": { + "name": "X" + }, + "displayName": "X (Formerly Twitter)", + "icon": "file:x.svg", + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE4LjI0NCAyLjI1aDMuMzA4bC03LjIyNyA4LjI2IDguNTAyIDExLjI0SDE2LjE3bC01LjIxNC02LjgxN0w0Ljk5IDIxLjc1SDEuNjhsNy43My04LjgzNUwxLjI1NCAyLjI1SDguMDhsNC43MTMgNi4yMzF6bS0xLjE2MSAxNy41MmgxLjgzM0w3LjA4NCA0LjEyNkg1LjExN3oiPjwvcGF0aD48L3N2Zz4K" + }, + "typeVersion": 2, + "categories": [ + { + "id": 1, + "name": "Marketing & Content" + } + ] + } + ], + "categories": [ + { + "id": 2, + "name": "Sales" + } + ], + "workflows": [ + { + "id": 1205, + "name": "Promote new Shopify products", + "views": 485, + "recentViews": 9850, + "totalViews": 485, + "createdAt": "2021-08-24T10:40:50.007Z", + "description": "This workflow automatically promotes your new Shopify products on Twitter and Telegram. This workflow is also featured in the blog post [*6 e-commerce workflows to power up your Shopify store*](https://n8n.io/blog/no-code-ecommerce-workflow-automations/#promote-your-new-products-on-social-media).\n\n## Prerequisites\n\n- A Shopify account and [credentials](https://docs.n8n.io/integrations/credentials/shopify/)\n- A Twitter account and [credentials](https://docs.n8n.io/integrations/credentials/twitter/)\n- A Telegram account and [credentials](https://docs.n8n.io/integrations/credentials/telegram/) for the channel you want to send messages to.\n\n## Nodes\n\n- [Shopify Trigger node](https://docs.n8n.io/integrations/trigger-nodes/n8n-nodes-base.shopifytrigger/) triggers the workflow when you create a new product in Shopify.\n- [Twitter node](https://docs.n8n.io/integrations/nodes/n8n-nodes-base.twitter/) posts a tweet with the text \"Hey there, my design is now on a new product! Visit my {shop name} to get this cool {product title} (and check out more {product type})\".\n- [Telegram node](https://docs.n8n.io/integrations/nodes/n8n-nodes-base.telegram/) posts a message with the same text as above in a Telegram channel.", + "workflow": { + "nodes": [ + { + "name": "Twitter", + "type": "n8n-nodes-base.twitter", + "position": [ + 720, + -220 + ], + "parameters": { + "text": "=Hey there, my design is now on a new product ✨\nVisit my {{$json[\"vendor\"]}} shop to get this cool{{$json[\"title\"]}} (and check out more {{$json[\"product_type\"]}}) 🛍️", + "additionalFields": {} + }, + "credentials": { + "twitterOAuth1Api": "twitter" + }, + "typeVersion": 1 + }, + { + "name": "Telegram", + "type": "n8n-nodes-base.telegram", + "position": [ + 720, + -20 + ], + "parameters": { + "text": "=Hey there, my design is now on a new product!\nVisit my {{$json[\"vendor\"]}} shop to get this cool{{$json[\"title\"]}} (and check out more {{$json[\"product_type\"]}})", + "chatId": "123456", + "additionalFields": {} + }, + "credentials": { + "telegramApi": "telegram_habot" + }, + "typeVersion": 1 + }, + { + "name": "product created", + "type": "n8n-nodes-base.shopifyTrigger", + "position": [ + 540, + -110 + ], + "webhookId": "2a7e0e50-8f09-4a2b-bf54-a849a6ac4fe0", + "parameters": { + "topic": "products/create" + }, + "credentials": { + "shopifyApi": "shopify_nodeqa" + }, + "typeVersion": 1 + } + ], + "connections": { + "product created": { + "main": [ + [ + { + "node": "Twitter", + "type": "main", + "index": 0 + }, + { + "node": "Telegram", + "type": "main", + "index": 0 + } + ] + ] + } + } + }, + "workflowInfo": { + "nodeCount": 3, + "nodeTypes": { + "n8n-nodes-base.twitter": { + "count": 1 + }, + "n8n-nodes-base.telegram": { + "count": 1 + }, + "n8n-nodes-base.shopifyTrigger": { + "count": 1 + } + } + }, + "user": { + "username": "lorenanda" + }, + "nodes": [ + { + "id": 49, + "icon": "file:telegram.svg", + "name": "n8n-nodes-base.telegram", + "defaults": { + "name": "Telegram" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNjYgNjYiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48ZyBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZD0iTTAgMzJjMCAxNy42NzMgMTQuMzI3IDMyIDMyIDMyczMyLTE0LjMyNyAzMi0zMlM0OS42NzMgMCAzMiAwIDAgMTQuMzI3IDAgMzIiIGZpbGw9IiMzN2FlZTIiLz48cGF0aCBkPSJNMjEuNjYxIDM0LjMzOGwzLjc5NyAxMC41MDhzLjQ3NS45ODMuOTgzLjk4MyA4LjA2OC03Ljg2NCA4LjA2OC03Ljg2NGw4LjQwNy0xNi4yMzctMjEuMTE5IDkuODk4eiIgZmlsbD0iI2M4ZGFlYSIvPjxwYXRoIGQ9Ik0yNi42OTUgMzcuMDM0bC0uNzI5IDcuNzQ2cy0uMzA1IDIuMzczIDIuMDY4IDBsNC42NDQtNC4yMDMiIGZpbGw9IiNhOWM2ZDgiLz48cGF0aCBkPSJNMjEuNzMgMzQuNzEybC03LjgwOS0yLjU0NXMtLjkzMi0uMzc4LS42MzMtMS4yMzdjLjA2Mi0uMTc3LjE4Ni0uMzI4LjU1OS0uNTg4IDEuNzMxLTEuMjA2IDMyLjAyOC0xMi4wOTYgMzIuMDI4LTEyLjA5NnMuODU2LS4yODggMS4zNjEtLjA5N2MuMjMxLjA4OC4zNzguMTg3LjUwMy41NDguMDQ1LjEzMi4wNzEuNDExLjA2OC42ODktLjAwMy4yMDEtLjAyNy4zODYtLjA0NS42NzgtLjE4NCAyLjk3OC01LjcwNiAyNS4xOTgtNS43MDYgMjUuMTk4cy0uMzMgMS4zLTEuNTE0IDEuMzQ1Yy0uNDMyLjAxNi0uOTU2LS4wNzEtMS41ODItLjYxLTIuMzIzLTEuOTk4LTEwLjM1Mi03LjM5NC0xMi4xMjYtOC41OGEuMzQuMzQgMCAwMS0uMTQ2LS4yMzljLS4wMjUtLjEyNS4xMDgtLjI4LjEwOC0uMjhzMTMuOTgtMTIuNDI3IDE0LjM1Mi0xMy43MzFjLjAyOS0uMTAxLS4wNzktLjE1MS0uMjI2LS4xMDctLjkyOS4zNDItMTcuMDI1IDEwLjUwNi0xOC44MDEgMTEuNjI5LS4xMDQuMDY2LS4zOTUuMDIzLS4zOTUuMDIzIi8+PC9nPjwvc3ltYm9sPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "Telegram", + "typeVersion": 1 + }, + { + "id": 107, + "icon": "file:shopify.svg", + "name": "n8n-nodes-base.shopifyTrigger", + "defaults": { + "name": "Shopify Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNTggNjYiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48ZyBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZD0iTTQ5LjI1NSAxMi40ODRhLjYzMy42MzMgMCAwMC0uNTY0LS41MjdjLS4yMjUtLjAzNy01LjE3LS4zNzYtNS4xNy0uMzc2bC0zLjc3LTMuNzdjLS4zNC0uMzc2LTEuMDkyLS4yNjYtMS4zNzYtLjE4OC0uMDM3IDAtLjc1Mi4yMjUtMS45MjIuNjA1LTEuMTM3LTMuMy0zLjE1LTYuMzA2LTYuNjk2LTYuMzA2aC0uMzAzQzI4LjQzOC42MDUgMjcuMTk0IDAgMjYuMTQ0IDBjLTguMjU2LjAzNy0xMi4yIDEwLjMzMy0xMy40MzQgMTUuNTk0bC01Ljc3IDEuNzdjLTEuNzcuNTY0LTEuODM1LjYwNS0yLjA3MyAyLjI5M0wwIDU3LjE3NSAzNi40NjggNjRsMTkuNzYzLTQuMjZjMC0uMDM3LTYuOTQtNDYuODk3LTYuOTc2LTQ3LjI1NXpNMzQuNDMxIDguODZjLS45MTcuMzAzLTEuOTYzLjYwNS0zLjEuOTQ1di0uNjhhMTUuMDMgMTUuMDMgMCAwMC0uNzUyLTQuOTk5YzEuODQ4LjI4NCAzLjEgMi4zNTcgMy44NDMgNC43MzN6bS02LjA2OC00LjI5OGMuNjAzIDEuNzc4Ljg4MyAzLjY1LjgyNiA1LjUyN3YuMzRsLTYuMzc1IDEuOTYzYzEuMjQ4LTQuNjYgMy41NS02Ljk2MiA1LjU1LTcuODN6bS0yLjQ1LTIuMjkzYTEuOTQgMS45NCAwIDAxMS4wNTUuMzM5Yy0yLjY2IDEuMjM4LTUuNDcyIDQuMzY2LTYuNjc4IDEwLjYyN2wtNS4wNDUgMS41NDZDMTYuNjY4IDEwLjAzIDE5Ljk4OCAyLjI2IDI1LjkxIDIuMjZ6IiBmaWxsPSIjOTViZjQ3Ii8+PHBhdGggZD0iTTQ4LjY5MSAxMS45NTdjLS4yMjUtLjAzNy01LjE3LS4zNzYtNS4xNy0uMzc2bC0zLjc3LTMuNzdhLjc1My43NTMgMCAwMC0uNTI3LS4yMjVMMzYuNDcyIDY0bDE5Ljc2My00LjI2LTYuOTgtNDcuMjE4YS42OC42OCAwIDAwLS41NjQtLjU2NHoiIGZpbGw9IiM1ZThlM2UiLz48cGF0aCBkPSJNMjkuNzU4IDIyLjlsLTIuNDU0IDcuMjQyYTExLjM2IDExLjM2IDAgMDAtNC43NTItMS4xMzNjLTMuODQ4IDAtNC4wMzYgMi40MTItNC4wMzYgMy4wMTggMCAzLjI5OCA4LjYzNiA0LjU2NCA4LjYzNiAxMi4zMzMgMCA2LjEtMy44ODUgMTAuMDMtOS4xIDEwLjAzLTYuMjYgMC05LjQ2Ny0zLjg4NS05LjQ2Ny0zLjg4NWwxLjY2NS01LjU1czMuMjggMi44MyA2LjA3MyAyLjgzYTIuNDcgMi40NyAwIDAwMi41NjQtMi40OWMwLTQuMzQtNy4xLTQuNTI3LTcuMS0xMS42MTggMC01Ljk2MiA0LjI5OC0xMS43NyAxMi45MzQtMTEuNzcgMy4zOTQuMDUgNS4wMTggMSA1LjAxOCAxeiIvPjwvZz48L3N5bWJvbD48L3N2Zz4=" + }, + "categories": [ + { + "id": 2, + "name": "Sales" + } + ], + "displayName": "Shopify Trigger", + "typeVersion": 1 + }, + { + "id": 325, + "icon": "file:x.svg", + "name": "n8n-nodes-base.twitter", + "defaults": { + "name": "X" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE4LjI0NCAyLjI1aDMuMzA4bC03LjIyNyA4LjI2IDguNTAyIDExLjI0SDE2LjE3bC01LjIxNC02LjgxN0w0Ljk5IDIxLjc1SDEuNjhsNy43My04LjgzNUwxLjI1NCAyLjI1SDguMDhsNC43MTMgNi4yMzF6bS0xLjE2MSAxNy41MmgxLjgzM0w3LjA4NCA0LjEyNkg1LjExN3oiPjwvcGF0aD48L3N2Zz4K" + }, + "categories": [ + { + "id": 1, + "name": "Marketing & Content" + } + ], + "displayName": "X (Formerly Twitter)", + "typeVersion": 2 + } + ], + "categories": [ + { + "id": 2, + "name": "Sales" + }, + { + "id": 19, + "name": "Marketing & Growth" + } + ], + "image": [ + { + "id": 527, + "url": "https://n8niostorageaccount.blob.core.windows.net/n8nio-strapi-blobs-prod/assets/89a078b208fe4c6181902608b1cd1332.png" + } + ] + }, + { + "id": 1456, + "name": "Add new customers from WooCommerce to Mautic", + "views": 333, + "recentViews": 9833, + "totalViews": 333, + "createdAt": "2022-02-17T15:00:40.748Z", + "description": "This workflow uses a WooCommerce trigger that will run when a new customer has been added, It will then add the customer to Mautic.\n\nTo use this workflow you will need to set the credentials to use for the WooCommerce and Mautic nodes.", + "workflow": { + "id": 83, + "name": "New WooCommerce Customer to Mautic", + "nodes": [ + { + "name": "Check for Existing", + "type": "n8n-nodes-base.mautic", + "position": [ + 280, + 480 + ], + "parameters": { + "options": { + "search": "={{$json[\"email\"]}}" + }, + "operation": "getAll", + "authentication": "oAuth2" + }, + "credentials": { + "mauticOAuth2Api": { + "id": "54", + "name": "Mautic account" + } + }, + "typeVersion": 1, + "alwaysOutputData": true + }, + { + "name": "If New", + "type": "n8n-nodes-base.if", + "position": [ + 460, + 480 + ], + "parameters": { + "conditions": { + "string": [ + { + "value1": "={{$json[\"id\"]}}", + "operation": "isEmpty" + } + ] + } + }, + "typeVersion": 1 + }, + { + "name": "Create Contact", + "type": "n8n-nodes-base.mautic", + "position": [ + 680, + 320 + ], + "parameters": { + "email": "={{$node[\"Customer Created\"].json[\"email\"]}}", + "company": "={{$node[\"Customer Created\"].json[\"billing\"][\"company\"]}}", + "options": {}, + "lastName": "={{$node[\"Customer Created\"].json[\"last_name\"]}}", + "firstName": "={{$node[\"Customer Created\"].json[\"first_name\"]}}", + "authentication": "oAuth2", + "additionalFields": {} + }, + "credentials": { + "mauticOAuth2Api": { + "id": "54", + "name": "Mautic account" + } + }, + "typeVersion": 1 + }, + { + "name": "Update Contact", + "type": "n8n-nodes-base.mautic", + "position": [ + 680, + 580 + ], + "parameters": { + "options": {}, + "contactId": "={{$json[\"id\"]}}", + "operation": "update", + "updateFields": { + "lastName": "={{$node[\"Customer Created or Updated\"].json[\"last_name\"]}}", + "firstName": "={{$node[\"Customer Created or Updated\"].json[\"first_name\"]}}" + }, + "authentication": "oAuth2" + }, + "credentials": { + "mauticOAuth2Api": { + "id": "54", + "name": "Mautic account" + } + }, + "typeVersion": 1 + }, + { + "name": "Customer Created or Updated", + "type": "n8n-nodes-base.wooCommerceTrigger", + "position": [ + 100, + 480 + ], + "webhookId": "5d89e322-a5e0-4cce-9eab-185e8375175b", + "parameters": { + "event": "customer.updated" + }, + "credentials": { + "wooCommerceApi": { + "id": "48", + "name": "WooCommerce account" + } + }, + "typeVersion": 1 + } + ], + "active": false, + "settings": { + "saveManualExecutions": true, + "saveExecutionProgress": true, + "saveDataSuccessExecution": "all" + }, + "connections": { + "If New": { + "main": [ + [ + { + "node": "Create Contact", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Update Contact", + "type": "main", + "index": 0 + } + ] + ] + }, + "Check for Existing": { + "main": [ + [ + { + "node": "If New", + "type": "main", + "index": 0 + } + ] + ] + }, + "Customer Created or Updated": { + "main": [ + [ + { + "node": "Check for Existing", + "type": "main", + "index": 0 + } + ] + ] + } + } + }, + "workflowInfo": { + "nodeCount": 6, + "nodeTypes": { + "n8n-nodes-base.if": { + "count": 1 + }, + "n8n-nodes-base.mautic": { + "count": 3 + }, + "n8n-nodes-base.wooCommerceTrigger": { + "count": 1 + } + } + }, + "user": { + "username": "jon-n8n" + }, + "nodes": [ + { + "id": 20, + "icon": "fa:map-signs", + "name": "n8n-nodes-base.if", + "defaults": { + "name": "IF", + "color": "#408000" + }, + "iconData": { + "icon": "map-signs", + "type": "icon" + }, + "categories": [ + { + "id": 9, + "name": "Core Nodes" + } + ], + "displayName": "IF", + "typeVersion": 1 + }, + { + "id": 42, + "icon": "fa:play", + "name": "n8n-nodes-base.start", + "defaults": { + "name": "Start", + "color": "#00e000" + }, + "iconData": { + "icon": "play", + "type": "icon" + }, + "categories": [ + { + "id": 9, + "name": "Core Nodes" + } + ], + "displayName": "Start", + "typeVersion": 1 + }, + { + "id": 126, + "icon": "file:mautic.svg", + "name": "n8n-nodes-base.mautic", + "defaults": { + "name": "Mautic" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNi4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iMzQ5Ljc3OXB4IiBoZWlnaHQ9IjM0OS43NzlweCIgdmlld0JveD0iMCAwIDM0OS43NzkgMzQ5Ljc3OSIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMzQ5Ljc3OSAzNDkuNzc5Ig0KCSB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxwYXRoIGZpbGw9IiNGRkZGRkYiIGQ9Ik0xNzQuODksMzQ5Ljc3OUM3OC42MTIsMzQ5Ljc3OSwwLDI3MS40NjIsMCwxNzQuODlTNzguNjEyLDAsMTc0Ljg5LDBjMjMuMjYsMCw0NS45MzEsNC40MTcsNjcuMTI5LDEzLjU0Mw0KCWM1Ljg4OSwyLjY1LDguODMzLDkuNDIyLDYuNDc4LDE1LjYwNWMtMi42NDksNS44ODgtOS40MjEsOC44MzMtMTUuNjA0LDYuNDc3Yy0xOC41NDktNy42NTUtMzcuOTgtMTEuNDgyLTU4LjAwMi0xMS40ODINCgljLTgzLjMyMywwLTE1MS4wNDEsNjcuNzE4LTE1MS4wNDEsMTUxLjA0MVM5MS41NjcsMzI2LjIyNSwxNzQuODksMzI2LjIyNWM4My4zMjMsMCwxNTEuMDQxLTY3LjcxOCwxNTEuMDQxLTE1MS4wNDENCgljMC0xNy45Ni0yLjk0NC0zNS4zMzItOS4xMjctNTEuODE5Yy0yLjM1NS02LjE4MywwLjg4My0xMi45NTUsNy4wNjYtMTUuMzFjNi4xODMtMi4zNTUsMTIuOTU0LDAuODgzLDE1LjMxLDcuMDY2DQoJYzcuMDY2LDE5LjEzOCwxMC42LDM5LjQ1MywxMC42LDYwLjA2M0MzNDkuNzc5LDI3MS4xNjcsMjcxLjQ2MiwzNDkuNzc5LDE3NC44OSwzNDkuNzc5Ii8+DQo8Zz4NCgk8cG9seWdvbiBmaWxsPSIjRkRCOTMzIiBwb2ludHM9IjI1MS40NCwxNTYuOTMgMjI0LjM1NCwxODUuMTk0IDIzOS4zNjksMjQ4LjQ5NiAyNzMuNTIyLDI0OC40OTYgCSIvPg0KPC9nPg0KPHBvbHlnb24gZmlsbD0iI0ZEQjkzMyIgcG9pbnRzPSIyNDAuMjUzLDczLjMxMiAyNDkuNjc0LDgyLjczNCAxNzQuODksMTYxLjkzNSAxMTAuOTk5LDk2LjI3NyA3NC4xOTYsMjQ4LjQ5NiAxMDguMzUsMjQ4LjQ5NiANCgkxMjguNjY1LDE2My45OTYgMTc0Ljg5LDIxNC4zNDMgMjczLjgxNywxMDYuNTgzIDI4My4yMzksMTE2LjI5OSAyOTIuNjYsNjMuMDA3ICIvPg0KPC9zdmc+DQo=" + }, + "categories": [ + { + "id": 1, + "name": "Marketing & Content" + }, + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "Mautic", + "typeVersion": 1 + }, + { + "id": 235, + "icon": "file:wooCommerce.svg", + "name": "n8n-nodes-base.wooCommerceTrigger", + "defaults": { + "name": "WooCommerce Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIC01MCAyNTYgMjUzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KPHRpdGxlPldvb0NvbW1lcmNlIExvZ288L3RpdGxlPgo8bWV0YWRhdGE+CjxyZGY6UkRGPgo8Y2M6V29yayByZGY6YWJvdXQ9IiI+CjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0Pgo8ZGM6dHlwZSByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIi8+CjxkYzp0aXRsZS8+CjwvY2M6V29yaz4KPC9yZGY6UkRGPgo8L21ldGFkYXRhPgo8cGF0aCBkPSJtMjMuNzU5IDBoMjA4LjM4YzEzLjE4NyAwIDIzLjg2MyAxMC42NzUgMjMuODYzIDIzLjg2M3Y3OS41NDJjMCAxMy4xODctMTAuNjc1IDIzLjg2My0yMy44NjMgMjMuODYzaC03NC43MjdsMTAuMjU3IDI1LjExOC00NS4xMDktMjUuMTE4aC05OC42OTVjLTEzLjE4NyAwLTIzLjg2My0xMC42NzUtMjMuODYzLTIzLjg2M3YtNzkuNTQyYy0wLjEwNDY2LTEzLjA4MyAxMC41NzEtMjMuODYzIDIzLjc1OC0yMy44NjN6IiBmaWxsPSIjN2Y1NGIzIi8+CjxwYXRoIGQ9Im0xNC41NzggMjEuNzVjMS40NTY5LTEuOTc3MiAzLjY0MjMtMy4wMTc5IDYuNTU2MS0zLjIyNiA1LjMwNzMtMC40MTYyNiA4LjMyNTIgMi4wODEzIDkuMDUzNyA3LjQ5MjcgMy4yMjYgMjEuNzUgNi43NjQyIDQwLjE2OSAxMC41MTEgNTUuMjU5bDIyLjc5LTQzLjM5NWMyLjA4MTMtMy45NTQ1IDQuNjgyOS02LjAzNTggNy44MDQ5LTYuMjQzOSA0LjU3ODktMC4zMTIyIDcuMzg4NiAyLjYwMTYgOC41MzMzIDguNzQxNSAyLjYwMTYgMTMuODQxIDUuOTMxNyAyNS42IDkuODg2MiAzNS41OSAyLjcwNTctMjYuNDMzIDcuMjg0Ni00NS40NzYgMTMuNzM3LTU3LjIzNiAxLjU2MS0yLjkxMzggMy44NTA0LTQuMzcwNyA2Ljg2ODMtNC41Nzg5IDIuMzkzNS0wLjIwODEzIDQuNTc4OSAwLjUyMDMzIDYuNTU2MSAyLjA4MTMgMS45NzcyIDEuNTYxIDMuMDE3OSAzLjUzODIgMy4yMjYgNS45MzE3IDAuMTA0MDYgMS44NzMyLTAuMjA4MTMgMy40MzQxLTEuMDQwNyA0Ljk5NTEtNC4wNTg1IDcuNDkyNy03LjM4ODYgMjAuMDg1LTEwLjA5NCAzNy41NjctMi42MDE2IDE2Ljk2My0zLjUzODIgMzAuMTc5LTIuOTEzOCAzOS42NDkgMC4yMDgxMyAyLjYwMTYtMC4yMDgxMyA0Ljg5MTEtMS4yNDg4IDYuODY4My0xLjI0ODggMi4yODk0LTMuMTIyIDMuNTM4Mi01LjUxNTQgMy43NDYzLTIuNzA1NyAwLjIwODEzLTUuNTE1NC0xLjA0MDYtOC4yMjExLTMuODUwNC05LjY3OC05Ljg4NjItMTcuMzc5LTI0LjY2My0yMi45OTgtNDQuMzMyLTYuNzY0MiAxMy4zMi0xMS43NTkgMjMuMzExLTE0Ljk4NSAyOS45NzEtNi4xMzk4IDExLjc1OS0xMS4zNDMgMTcuNzk1LTE1LjcxNCAxOC4xMDctMi44MDk4IDAuMjA4MTMtNS4yMDMzLTIuMTg1NC03LjI4NDYtNy4xODA1LTUuMzA3My0xMy42MzMtMTEuMDMxLTM5Ljk2MS0xNy4xNzEtNzguOTg1LTAuNDE2MjYtMi43MDU3IDAuMjA4MTMtNS4wOTkyIDEuNjY1LTYuOTcyNHptMjIzLjY0IDE2LjMzOGMtMy43NDYzLTYuNTU2MS05LjI2MTgtMTAuNTExLTE2LjY1LTEyLjA3Mi0xLjk3NzItMC40MTYyNi0zLjg1MDQtMC42MjQzOS01LjYxOTUtMC42MjQzOS05Ljk5MDIgMC0xOC4xMDcgNS4yMDMzLTI0LjQ1NSAxNS42MS01LjQxMTQgOC44NDU1LTguMTE3MSAxOC42MjgtOC4xMTcxIDI5LjM0NiAwIDguMDEzIDEuNjY1IDE0Ljg4MSA0Ljk5NTEgMjAuNjA1IDMuNzQ2MyA2LjU1NjEgOS4yNjE4IDEwLjUxMSAxNi42NSAxMi4wNzIgMS45NzcyIDAuNDE2MjYgMy44NTA0IDAuNjI0MzkgNS42MTk1IDAuNjI0MzkgMTAuMDk0IDAgMTguMjExLTUuMjAzMyAyNC40NTUtMTUuNjEgNS40MTE0LTguOTQ5NiA4LjExNzEtMTguNzMyIDguMTE3MS0yOS40NSAwLjEwNDA2LTguMTE3MS0xLjY2NS0xNC44ODEtNC45OTUxLTIwLjUwMXptLTEzLjExMiAyOC44MjZjLTEuNDU2OSA2Ljg2ODMtNC4wNTg1IDExLjk2Ny03LjkwODkgMTUuNDAyLTMuMDE3OSAyLjcwNTctNS44Mjc2IDMuODUwNC04LjQyOTMgMy4zMzAxLTIuNDk3Ni0wLjUyMDMzLTQuNTc4OS0yLjcwNTctNi4xMzk4LTYuNzY0Mi0xLjI0ODgtMy4yMjYtMS44NzMyLTYuNDUyLTEuODczMi05LjQ2OTkgMC0yLjYwMTYgMC4yMDgxMy01LjIwMzMgMC43Mjg0Ni03LjU5NjcgMC45MzY1OS00LjI2NjcgMi43MDU3LTguNDI5MyA1LjUxNTQtMTIuMzg0IDMuNDM0MS01LjA5OTIgNy4wNzY0LTcuMTgwNSAxMC44MjMtNi40NTIgMi40OTc2IDAuNTIwMzMgNC41Nzg5IDIuNzA1NyA2LjEzOTggNi43NjQyIDEuMjQ4OCAzLjIyNiAxLjg3MzIgNi40NTIgMS44NzMyIDkuNDY5OSAwIDIuNzA1Ny0wLjIwODEzIDUuMzA3My0wLjcyODQ2IDcuNzAwOHptLTUyLjAzMy0yOC44MjZjLTMuNzQ2My02LjU1NjEtOS4zNjU5LTEwLjUxMS0xNi42NS0xMi4wNzItMS45NzcyLTAuNDE2MjYtMy44NTA0LTAuNjI0MzktNS42MTk1LTAuNjI0MzktOS45OTAyIDAtMTguMTA3IDUuMjAzMy0yNC40NTUgMTUuNjEtNS40MTE0IDguODQ1NS04LjExNzEgMTguNjI4LTguMTE3MSAyOS4zNDYgMCA4LjAxMyAxLjY2NSAxNC44ODEgNC45OTUxIDIwLjYwNSAzLjc0NjMgNi41NTYxIDkuMjYxOCAxMC41MTEgMTYuNjUgMTIuMDcyIDEuOTc3MiAwLjQxNjI2IDMuODUwNCAwLjYyNDM5IDUuNjE5NSAwLjYyNDM5IDEwLjA5NCAwIDE4LjIxMS01LjIwMzMgMjQuNDU1LTE1LjYxIDUuNDExNC04Ljk0OTYgOC4xMTcxLTE4LjczMiA4LjExNzEtMjkuNDUgMC04LjExNzEtMS42NjUtMTQuODgxLTQuOTk1MS0yMC41MDF6bS0xMy4yMTYgMjguODI2Yy0xLjQ1NjkgNi44NjgzLTQuMDU4NSAxMS45NjctNy45MDg5IDE1LjQwMi0zLjAxNzkgMi43MDU3LTUuODI3NiAzLjg1MDQtOC40MjkzIDMuMzMwMS0yLjQ5NzYtMC41MjAzMy00LjU3ODktMi43MDU3LTYuMTM5OC02Ljc2NDItMS4yNDg4LTMuMjI2LTEuODczMi02LjQ1Mi0xLjg3MzItOS40Njk5IDAtMi42MDE2IDAuMjA4MTMtNS4yMDMzIDAuNzI4NDYtNy41OTY3IDAuOTM2NTgtNC4yNjY3IDIuNzA1Ny04LjQyOTMgNS41MTU0LTEyLjM4NCAzLjQzNDEtNS4wOTkyIDcuMDc2NC03LjE4MDUgMTAuODIzLTYuNDUyIDIuNDk3NiAwLjUyMDMzIDQuNTc4OSAyLjcwNTcgNi4xMzk4IDYuNzY0MiAxLjI0ODggMy4yMjYgMS44NzMyIDYuNDUyIDEuODczMiA5LjQ2OTkgMC4xMDQwNiAyLjcwNTctMC4yMDgxMyA1LjMwNzMtMC43Mjg0NiA3LjcwMDh6IiBmaWxsPSIjZmZmIi8+Cjwvc3ZnPgo=" + }, + "categories": [ + { + "id": 2, + "name": "Sales" + } + ], + "displayName": "WooCommerce Trigger", + "typeVersion": 1 + } + ], + "categories": [ + { + "id": 2, + "name": "Sales" + } + ], + "image": [] + }, + { + "id": 1459, + "name": "Notify on Telegram and Twitter when new order is added in WooCommerce", + "views": 620, + "recentViews": 9823, + "totalViews": 620, + "createdAt": "2022-02-17T15:02:14.961Z", + "description": "This workflow uses a WooCommerce trigger that will run a new product has been added, It will then post the product to Telegram and Twitter.\n\nTo use this workflow you will need to set the credentials to use for the WooCommerce, Twitter and Telegram nodes.", + "workflow": { + "id": 85, + "name": "New WooCommerce Product to Twitter and Telegram", + "nodes": [ + { + "name": "Twitter", + "type": "n8n-nodes-base.twitter", + "position": [ + 720, + 300 + ], + "parameters": { + "text": "=✨ New Product Announcement ✨\nWe have just added {{$json[\"name\"]}}, Head to {{$json[\"permalink\"]}} to find out more.", + "additionalFields": {} + }, + "credentials": { + "twitterOAuth1Api": { + "id": "37", + "name": "joffcom" + } + }, + "typeVersion": 1 + }, + { + "name": "Telegram", + "type": "n8n-nodes-base.telegram", + "position": [ + 720, + 500 + ], + "parameters": { + "text": "=✨ New Product Announcement ✨\nWe have just added {{$json[\"name\"]}}, Head to {{$json[\"permalink\"]}} to find out more.", + "chatId": "123456", + "additionalFields": {} + }, + "credentials": { + "telegramApi": { + "id": "56", + "name": "Telegram account" + } + }, + "typeVersion": 1 + }, + { + "name": "WooCommerce Trigger", + "type": "n8n-nodes-base.wooCommerceTrigger", + "position": [ + 540, + 400 + ], + "webhookId": "ab7b134b-9b2d-4e0d-b496-1aee30db0808", + "parameters": { + "event": "product.created" + }, + "credentials": { + "wooCommerceApi": { + "id": "48", + "name": "WooCommerce account" + } + }, + "typeVersion": 1 + } + ], + "active": false, + "settings": {}, + "connections": { + "WooCommerce Trigger": { + "main": [ + [ + { + "node": "Twitter", + "type": "main", + "index": 0 + }, + { + "node": "Telegram", + "type": "main", + "index": 0 + } + ] + ] + } + } + }, + "workflowInfo": { + "nodeCount": 4, + "nodeTypes": { + "n8n-nodes-base.twitter": { + "count": 1 + }, + "n8n-nodes-base.telegram": { + "count": 1 + }, + "n8n-nodes-base.wooCommerceTrigger": { + "count": 1 + } + } + }, + "user": { + "username": "jon-n8n" + }, + "nodes": [ + { + "id": 42, + "icon": "fa:play", + "name": "n8n-nodes-base.start", + "defaults": { + "name": "Start", + "color": "#00e000" + }, + "iconData": { + "icon": "play", + "type": "icon" + }, + "categories": [ + { + "id": 9, + "name": "Core Nodes" + } + ], + "displayName": "Start", + "typeVersion": 1 + }, + { + "id": 49, + "icon": "file:telegram.svg", + "name": "n8n-nodes-base.telegram", + "defaults": { + "name": "Telegram" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNjYgNjYiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48ZyBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZD0iTTAgMzJjMCAxNy42NzMgMTQuMzI3IDMyIDMyIDMyczMyLTE0LjMyNyAzMi0zMlM0OS42NzMgMCAzMiAwIDAgMTQuMzI3IDAgMzIiIGZpbGw9IiMzN2FlZTIiLz48cGF0aCBkPSJNMjEuNjYxIDM0LjMzOGwzLjc5NyAxMC41MDhzLjQ3NS45ODMuOTgzLjk4MyA4LjA2OC03Ljg2NCA4LjA2OC03Ljg2NGw4LjQwNy0xNi4yMzctMjEuMTE5IDkuODk4eiIgZmlsbD0iI2M4ZGFlYSIvPjxwYXRoIGQ9Ik0yNi42OTUgMzcuMDM0bC0uNzI5IDcuNzQ2cy0uMzA1IDIuMzczIDIuMDY4IDBsNC42NDQtNC4yMDMiIGZpbGw9IiNhOWM2ZDgiLz48cGF0aCBkPSJNMjEuNzMgMzQuNzEybC03LjgwOS0yLjU0NXMtLjkzMi0uMzc4LS42MzMtMS4yMzdjLjA2Mi0uMTc3LjE4Ni0uMzI4LjU1OS0uNTg4IDEuNzMxLTEuMjA2IDMyLjAyOC0xMi4wOTYgMzIuMDI4LTEyLjA5NnMuODU2LS4yODggMS4zNjEtLjA5N2MuMjMxLjA4OC4zNzguMTg3LjUwMy41NDguMDQ1LjEzMi4wNzEuNDExLjA2OC42ODktLjAwMy4yMDEtLjAyNy4zODYtLjA0NS42NzgtLjE4NCAyLjk3OC01LjcwNiAyNS4xOTgtNS43MDYgMjUuMTk4cy0uMzMgMS4zLTEuNTE0IDEuMzQ1Yy0uNDMyLjAxNi0uOTU2LS4wNzEtMS41ODItLjYxLTIuMzIzLTEuOTk4LTEwLjM1Mi03LjM5NC0xMi4xMjYtOC41OGEuMzQuMzQgMCAwMS0uMTQ2LS4yMzljLS4wMjUtLjEyNS4xMDgtLjI4LjEwOC0uMjhzMTMuOTgtMTIuNDI3IDE0LjM1Mi0xMy43MzFjLjAyOS0uMTAxLS4wNzktLjE1MS0uMjI2LS4xMDctLjkyOS4zNDItMTcuMDI1IDEwLjUwNi0xOC44MDEgMTEuNjI5LS4xMDQuMDY2LS4zOTUuMDIzLS4zOTUuMDIzIi8+PC9nPjwvc3ltYm9sPjwvc3ZnPg==" + }, + "categories": [ + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "Telegram", + "typeVersion": 1 + }, + { + "id": 235, + "icon": "file:wooCommerce.svg", + "name": "n8n-nodes-base.wooCommerceTrigger", + "defaults": { + "name": "WooCommerce Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIC01MCAyNTYgMjUzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KPHRpdGxlPldvb0NvbW1lcmNlIExvZ288L3RpdGxlPgo8bWV0YWRhdGE+CjxyZGY6UkRGPgo8Y2M6V29yayByZGY6YWJvdXQ9IiI+CjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0Pgo8ZGM6dHlwZSByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIi8+CjxkYzp0aXRsZS8+CjwvY2M6V29yaz4KPC9yZGY6UkRGPgo8L21ldGFkYXRhPgo8cGF0aCBkPSJtMjMuNzU5IDBoMjA4LjM4YzEzLjE4NyAwIDIzLjg2MyAxMC42NzUgMjMuODYzIDIzLjg2M3Y3OS41NDJjMCAxMy4xODctMTAuNjc1IDIzLjg2My0yMy44NjMgMjMuODYzaC03NC43MjdsMTAuMjU3IDI1LjExOC00NS4xMDktMjUuMTE4aC05OC42OTVjLTEzLjE4NyAwLTIzLjg2My0xMC42NzUtMjMuODYzLTIzLjg2M3YtNzkuNTQyYy0wLjEwNDY2LTEzLjA4MyAxMC41NzEtMjMuODYzIDIzLjc1OC0yMy44NjN6IiBmaWxsPSIjN2Y1NGIzIi8+CjxwYXRoIGQ9Im0xNC41NzggMjEuNzVjMS40NTY5LTEuOTc3MiAzLjY0MjMtMy4wMTc5IDYuNTU2MS0zLjIyNiA1LjMwNzMtMC40MTYyNiA4LjMyNTIgMi4wODEzIDkuMDUzNyA3LjQ5MjcgMy4yMjYgMjEuNzUgNi43NjQyIDQwLjE2OSAxMC41MTEgNTUuMjU5bDIyLjc5LTQzLjM5NWMyLjA4MTMtMy45NTQ1IDQuNjgyOS02LjAzNTggNy44MDQ5LTYuMjQzOSA0LjU3ODktMC4zMTIyIDcuMzg4NiAyLjYwMTYgOC41MzMzIDguNzQxNSAyLjYwMTYgMTMuODQxIDUuOTMxNyAyNS42IDkuODg2MiAzNS41OSAyLjcwNTctMjYuNDMzIDcuMjg0Ni00NS40NzYgMTMuNzM3LTU3LjIzNiAxLjU2MS0yLjkxMzggMy44NTA0LTQuMzcwNyA2Ljg2ODMtNC41Nzg5IDIuMzkzNS0wLjIwODEzIDQuNTc4OSAwLjUyMDMzIDYuNTU2MSAyLjA4MTMgMS45NzcyIDEuNTYxIDMuMDE3OSAzLjUzODIgMy4yMjYgNS45MzE3IDAuMTA0MDYgMS44NzMyLTAuMjA4MTMgMy40MzQxLTEuMDQwNyA0Ljk5NTEtNC4wNTg1IDcuNDkyNy03LjM4ODYgMjAuMDg1LTEwLjA5NCAzNy41NjctMi42MDE2IDE2Ljk2My0zLjUzODIgMzAuMTc5LTIuOTEzOCAzOS42NDkgMC4yMDgxMyAyLjYwMTYtMC4yMDgxMyA0Ljg5MTEtMS4yNDg4IDYuODY4My0xLjI0ODggMi4yODk0LTMuMTIyIDMuNTM4Mi01LjUxNTQgMy43NDYzLTIuNzA1NyAwLjIwODEzLTUuNTE1NC0xLjA0MDYtOC4yMjExLTMuODUwNC05LjY3OC05Ljg4NjItMTcuMzc5LTI0LjY2My0yMi45OTgtNDQuMzMyLTYuNzY0MiAxMy4zMi0xMS43NTkgMjMuMzExLTE0Ljk4NSAyOS45NzEtNi4xMzk4IDExLjc1OS0xMS4zNDMgMTcuNzk1LTE1LjcxNCAxOC4xMDctMi44MDk4IDAuMjA4MTMtNS4yMDMzLTIuMTg1NC03LjI4NDYtNy4xODA1LTUuMzA3My0xMy42MzMtMTEuMDMxLTM5Ljk2MS0xNy4xNzEtNzguOTg1LTAuNDE2MjYtMi43MDU3IDAuMjA4MTMtNS4wOTkyIDEuNjY1LTYuOTcyNHptMjIzLjY0IDE2LjMzOGMtMy43NDYzLTYuNTU2MS05LjI2MTgtMTAuNTExLTE2LjY1LTEyLjA3Mi0xLjk3NzItMC40MTYyNi0zLjg1MDQtMC42MjQzOS01LjYxOTUtMC42MjQzOS05Ljk5MDIgMC0xOC4xMDcgNS4yMDMzLTI0LjQ1NSAxNS42MS01LjQxMTQgOC44NDU1LTguMTE3MSAxOC42MjgtOC4xMTcxIDI5LjM0NiAwIDguMDEzIDEuNjY1IDE0Ljg4MSA0Ljk5NTEgMjAuNjA1IDMuNzQ2MyA2LjU1NjEgOS4yNjE4IDEwLjUxMSAxNi42NSAxMi4wNzIgMS45NzcyIDAuNDE2MjYgMy44NTA0IDAuNjI0MzkgNS42MTk1IDAuNjI0MzkgMTAuMDk0IDAgMTguMjExLTUuMjAzMyAyNC40NTUtMTUuNjEgNS40MTE0LTguOTQ5NiA4LjExNzEtMTguNzMyIDguMTE3MS0yOS40NSAwLjEwNDA2LTguMTE3MS0xLjY2NS0xNC44ODEtNC45OTUxLTIwLjUwMXptLTEzLjExMiAyOC44MjZjLTEuNDU2OSA2Ljg2ODMtNC4wNTg1IDExLjk2Ny03LjkwODkgMTUuNDAyLTMuMDE3OSAyLjcwNTctNS44Mjc2IDMuODUwNC04LjQyOTMgMy4zMzAxLTIuNDk3Ni0wLjUyMDMzLTQuNTc4OS0yLjcwNTctNi4xMzk4LTYuNzY0Mi0xLjI0ODgtMy4yMjYtMS44NzMyLTYuNDUyLTEuODczMi05LjQ2OTkgMC0yLjYwMTYgMC4yMDgxMy01LjIwMzMgMC43Mjg0Ni03LjU5NjcgMC45MzY1OS00LjI2NjcgMi43MDU3LTguNDI5MyA1LjUxNTQtMTIuMzg0IDMuNDM0MS01LjA5OTIgNy4wNzY0LTcuMTgwNSAxMC44MjMtNi40NTIgMi40OTc2IDAuNTIwMzMgNC41Nzg5IDIuNzA1NyA2LjEzOTggNi43NjQyIDEuMjQ4OCAzLjIyNiAxLjg3MzIgNi40NTIgMS44NzMyIDkuNDY5OSAwIDIuNzA1Ny0wLjIwODEzIDUuMzA3My0wLjcyODQ2IDcuNzAwOHptLTUyLjAzMy0yOC44MjZjLTMuNzQ2My02LjU1NjEtOS4zNjU5LTEwLjUxMS0xNi42NS0xMi4wNzItMS45NzcyLTAuNDE2MjYtMy44NTA0LTAuNjI0MzktNS42MTk1LTAuNjI0MzktOS45OTAyIDAtMTguMTA3IDUuMjAzMy0yNC40NTUgMTUuNjEtNS40MTE0IDguODQ1NS04LjExNzEgMTguNjI4LTguMTE3MSAyOS4zNDYgMCA4LjAxMyAxLjY2NSAxNC44ODEgNC45OTUxIDIwLjYwNSAzLjc0NjMgNi41NTYxIDkuMjYxOCAxMC41MTEgMTYuNjUgMTIuMDcyIDEuOTc3MiAwLjQxNjI2IDMuODUwNCAwLjYyNDM5IDUuNjE5NSAwLjYyNDM5IDEwLjA5NCAwIDE4LjIxMS01LjIwMzMgMjQuNDU1LTE1LjYxIDUuNDExNC04Ljk0OTYgOC4xMTcxLTE4LjczMiA4LjExNzEtMjkuNDUgMC04LjExNzEtMS42NjUtMTQuODgxLTQuOTk1MS0yMC41MDF6bS0xMy4yMTYgMjguODI2Yy0xLjQ1NjkgNi44NjgzLTQuMDU4NSAxMS45NjctNy45MDg5IDE1LjQwMi0zLjAxNzkgMi43MDU3LTUuODI3NiAzLjg1MDQtOC40MjkzIDMuMzMwMS0yLjQ5NzYtMC41MjAzMy00LjU3ODktMi43MDU3LTYuMTM5OC02Ljc2NDItMS4yNDg4LTMuMjI2LTEuODczMi02LjQ1Mi0xLjg3MzItOS40Njk5IDAtMi42MDE2IDAuMjA4MTMtNS4yMDMzIDAuNzI4NDYtNy41OTY3IDAuOTM2NTgtNC4yNjY3IDIuNzA1Ny04LjQyOTMgNS41MTU0LTEyLjM4NCAzLjQzNDEtNS4wOTkyIDcuMDc2NC03LjE4MDUgMTAuODIzLTYuNDUyIDIuNDk3NiAwLjUyMDMzIDQuNTc4OSAyLjcwNTcgNi4xMzk4IDYuNzY0MiAxLjI0ODggMy4yMjYgMS44NzMyIDYuNDUyIDEuODczMiA5LjQ2OTkgMC4xMDQwNiAyLjcwNTctMC4yMDgxMyA1LjMwNzMtMC43Mjg0NiA3LjcwMDh6IiBmaWxsPSIjZmZmIi8+Cjwvc3ZnPgo=" + }, + "categories": [ + { + "id": 2, + "name": "Sales" + } + ], + "displayName": "WooCommerce Trigger", + "typeVersion": 1 + }, + { + "id": 325, + "icon": "file:x.svg", + "name": "n8n-nodes-base.twitter", + "defaults": { + "name": "X" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE4LjI0NCAyLjI1aDMuMzA4bC03LjIyNyA4LjI2IDguNTAyIDExLjI0SDE2LjE3bC01LjIxNC02LjgxN0w0Ljk5IDIxLjc1SDEuNjhsNy43My04LjgzNUwxLjI1NCAyLjI1SDguMDhsNC43MTMgNi4yMzF6bS0xLjE2MSAxNy41MmgxLjgzM0w3LjA4NCA0LjEyNkg1LjExN3oiPjwvcGF0aD48L3N2Zz4K" + }, + "categories": [ + { + "id": 1, + "name": "Marketing & Content" + } + ], + "displayName": "X (Formerly Twitter)", + "typeVersion": 2 + } + ], + "categories": [ + { + "id": 2, + "name": "Sales" + }, + { + "id": 19, + "name": "Marketing & Growth" + } + ], + "image": [] + }, + { + "id": 1457, + "name": "Notify on Slack when new order is registered in WooCommerce", + "views": 178, + "recentViews": 9787, + "totalViews": 178, + "createdAt": "2022-02-17T15:01:13.489Z", + "description": "This workflow uses a WooCommerce trigger that will run when an order has been placed.\n\nIf the value of this is over 100 it will post it to a Slack channel.\n\nTo use this workflow you will need to set the credentials to use for the WooCommerce and Slack nodes, You will also need to pick a channel to post the message to.", + "workflow": { + "id": 81, + "name": "New WooCommerce order to Slack", + "nodes": [ + { + "name": "Order Created", + "type": "n8n-nodes-base.wooCommerceTrigger", + "position": [ + 340, + 500 + ], + "webhookId": "287b4bf4-67ec-4c97-85d9-c0d3e6f59e6b", + "parameters": { + "event": "order.created" + }, + "credentials": { + "wooCommerceApi": { + "id": "48", + "name": "WooCommerce account" + } + }, + "typeVersion": 1 + }, + { + "name": "Send to Slack", + "type": "n8n-nodes-base.slack", + "position": [ + 780, + 480 + ], + "parameters": { + "text": ":sparkles: There is a new order :sparkles:", + "channel": "woo-commerce", + "blocksUi": { + "blocksValues": [] + }, + "attachments": [ + { + "color": "#66FF00", + "fields": { + "item": [ + { + "short": true, + "title": "Order ID", + "value": "={{$json[\"id\"]}}" + }, + { + "short": true, + "title": "Status", + "value": "={{$json[\"status\"]}}" + }, + { + "short": true, + "title": "Total", + "value": "={{$json[\"currency_symbol\"]}}{{$json[\"total\"]}}" + }, + { + "short": false, + "title": "Link", + "value": "={{$node[\"Order Created\"].json[\"_links\"][\"self\"][0][\"href\"]}}" + } + ] + }, + "footer": "=*Ordered:* {{$json[\"date_created\"]}} | *Transaction ID:* {{$json[\"transaction_id\"]}}" + } + ], + "otherOptions": {} + }, + "credentials": { + "slackApi": { + "id": "53", + "name": "Slack Access Token" + } + }, + "typeVersion": 1 + }, + { + "name": "Price over 100", + "type": "n8n-nodes-base.if", + "position": [ + 540, + 500 + ], + "parameters": { + "conditions": { + "number": [ + { + "value1": "={{$json[\"total\"]}}", + "value2": 100, + "operation": "largerEqual" + } + ] + } + }, + "typeVersion": 1 + } + ], + "active": false, + "settings": { + "saveManualExecutions": true, + "saveExecutionProgress": true, + "saveDataSuccessExecution": "all" + }, + "connections": { + "Order Created": { + "main": [ + [ + { + "node": "Price over 100", + "type": "main", + "index": 0 + } + ] + ] + }, + "Price over 100": { + "main": [ + [ + { + "node": "Send to Slack", + "type": "main", + "index": 0 + } + ], + [] + ] + } + } + }, + "workflowInfo": { + "nodeCount": 4, + "nodeTypes": { + "n8n-nodes-base.if": { + "count": 1 + }, + "n8n-nodes-base.slack": { + "count": 1 + }, + "n8n-nodes-base.wooCommerceTrigger": { + "count": 1 + } + } + }, + "user": { + "username": "jon-n8n" + }, + "nodes": [ + { + "id": 20, + "icon": "fa:map-signs", + "name": "n8n-nodes-base.if", + "defaults": { + "name": "IF", + "color": "#408000" + }, + "iconData": { + "icon": "map-signs", + "type": "icon" + }, + "categories": [ + { + "id": 9, + "name": "Core Nodes" + } + ], + "displayName": "IF", + "typeVersion": 1 + }, + { + "id": 40, + "icon": "file:slack.svg", + "name": "n8n-nodes-base.slack", + "defaults": { + "name": "Slack" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTUwLjg1MiAxNTAuODUyIiBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48dXNlIHhsaW5rOmhyZWY9IiNhIiB4PSIuOTI2IiB5PSIuOTI2Ii8+PHN5bWJvbCBpZD0iYSIgb3ZlcmZsb3c9InZpc2libGUiPjxnIHN0cm9rZS13aWR0aD0iMS44NTIiPjxwYXRoIGQ9Ik00MC43NDEgOTMuNTVjMC04LjczNSA2LjYwNy0xNS43NzIgMTQuODE1LTE1Ljc3MnMxNC44MTUgNy4wMzcgMTQuODE1IDE1Ljc3MnYzOC44MjRjMCA4LjczNy02LjYwNyAxNS43NzQtMTQuODE1IDE1Ljc3NHMtMTQuODE1LTcuMDM3LTE0LjgxNS0xNS43NzJ6IiBmaWxsPSIjZTAxZTVhIiBzdHJva2U9IiNlMDFlNWEiLz48cGF0aCBkPSJNOTMuNTUgMTA3LjQwOGMtOC43MzUgMC0xNS43NzItNi42MDctMTUuNzcyLTE0LjgxNXM3LjAzNy0xNC44MTUgMTUuNzcyLTE0LjgxNWgzOC44MjZjOC43MzUgMCAxNS43NzIgNi42MDcgMTUuNzcyIDE0LjgxNXMtNy4wMzcgMTQuODE1LTE1Ljc3MiAxNC44MTV6IiBmaWxsPSIjZWNiMjJkIiBzdHJva2U9IiNlY2IyMmQiLz48cGF0aCBkPSJNNzcuNzc4IDE1Ljc3MkM3Ny43NzggNy4wMzcgODQuMzg1IDAgOTIuNTkzIDBzMTQuODE1IDcuMDM3IDE0LjgxNSAxNS43NzJ2MzguODI2YzAgOC43MzUtNi42MDcgMTUuNzcyLTE0LjgxNSAxNS43NzJzLTE0LjgxNS03LjAzNy0xNC44MTUtMTUuNzcyeiIgZmlsbD0iIzJmYjY3YyIgc3Ryb2tlPSIjMmZiNjdjIi8+PHBhdGggZD0iTTE1Ljc3MiA3MC4zNzFDNy4wMzcgNzAuMzcxIDAgNjMuNzYzIDAgNTUuNTU2czcuMDM3LTE0LjgxNSAxNS43NzItMTQuODE1aDM4LjgyNmM4LjczNSAwIDE1Ljc3MiA2LjYwNyAxNS43NzIgMTQuODE1cy03LjAzNyAxNC44MTUtMTUuNzcyIDE0LjgxNXoiIGZpbGw9IiMzNmM1ZjEiIHN0cm9rZT0iIzM2YzVmMSIvPjxnIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiPjxwYXRoIGQ9Ik03Ny43NzggMTMzLjMzM2MwIDguMjA4IDYuNjA3IDE0LjgxNSAxNC44MTUgMTQuODE1czE0LjgxNS02LjYwNyAxNC44MTUtMTQuODE1LTYuNjA3LTE0LjgxNS0xNC44MTUtMTQuODE1SDc3Ljc3OHoiIGZpbGw9IiNlY2IyMmQiIHN0cm9rZT0iI2VjYjIyZCIvPjxwYXRoIGQ9Ik0xMzMuMzM0IDcwLjM3MWgtMTQuODE1VjU1LjU1NmMwLTguMjA3IDYuNjA3LTE0LjgxNSAxNC44MTUtMTQuODE1czE0LjgxNSA2LjYwNyAxNC44MTUgMTQuODE1LTYuNjA3IDE0LjgxNS0xNC44MTUgMTQuODE1eiIgZmlsbD0iIzJmYjY3YyIgc3Ryb2tlPSIjMmZiNjdjIi8+PHBhdGggZD0iTTE0LjgxNSA3Ny43NzhIMjkuNjN2MTQuODE1YzAgOC4yMDctNi42MDcgMTQuODE1LTE0LjgxNSAxNC44MTVTMCAxMDAuOCAwIDkyLjU5M3M2LjYwNy0xNC44MTUgMTQuODE1LTE0LjgxNXoiIGZpbGw9IiNlMDFlNWEiIHN0cm9rZT0iI2UwMWU1YSIvPjxwYXRoIGQ9Ik03MC4zNzEgMTQuODE1VjI5LjYzSDU1LjU1NmMtOC4yMDcgMC0xNC44MTUtNi42MDctMTQuODE1LTE0LjgxNVM0Ny4zNDggMCA1NS41NTYgMHMxNC44MTUgNi42MDcgMTQuODE1IDE0LjgxNXoiIGZpbGw9IiMzNmM1ZjEiIHN0cm9rZT0iIzM2YzVmMSIvPjwvZz48L2c+PC9zeW1ib2w+PC9zdmc+" + }, + "categories": [ + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "Slack", + "typeVersion": 2 + }, + { + "id": 42, + "icon": "fa:play", + "name": "n8n-nodes-base.start", + "defaults": { + "name": "Start", + "color": "#00e000" + }, + "iconData": { + "icon": "play", + "type": "icon" + }, + "categories": [ + { + "id": 9, + "name": "Core Nodes" + } + ], + "displayName": "Start", + "typeVersion": 1 + }, + { + "id": 235, + "icon": "file:wooCommerce.svg", + "name": "n8n-nodes-base.wooCommerceTrigger", + "defaults": { + "name": "WooCommerce Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIC01MCAyNTYgMjUzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KPHRpdGxlPldvb0NvbW1lcmNlIExvZ288L3RpdGxlPgo8bWV0YWRhdGE+CjxyZGY6UkRGPgo8Y2M6V29yayByZGY6YWJvdXQ9IiI+CjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0Pgo8ZGM6dHlwZSByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIi8+CjxkYzp0aXRsZS8+CjwvY2M6V29yaz4KPC9yZGY6UkRGPgo8L21ldGFkYXRhPgo8cGF0aCBkPSJtMjMuNzU5IDBoMjA4LjM4YzEzLjE4NyAwIDIzLjg2MyAxMC42NzUgMjMuODYzIDIzLjg2M3Y3OS41NDJjMCAxMy4xODctMTAuNjc1IDIzLjg2My0yMy44NjMgMjMuODYzaC03NC43MjdsMTAuMjU3IDI1LjExOC00NS4xMDktMjUuMTE4aC05OC42OTVjLTEzLjE4NyAwLTIzLjg2My0xMC42NzUtMjMuODYzLTIzLjg2M3YtNzkuNTQyYy0wLjEwNDY2LTEzLjA4MyAxMC41NzEtMjMuODYzIDIzLjc1OC0yMy44NjN6IiBmaWxsPSIjN2Y1NGIzIi8+CjxwYXRoIGQ9Im0xNC41NzggMjEuNzVjMS40NTY5LTEuOTc3MiAzLjY0MjMtMy4wMTc5IDYuNTU2MS0zLjIyNiA1LjMwNzMtMC40MTYyNiA4LjMyNTIgMi4wODEzIDkuMDUzNyA3LjQ5MjcgMy4yMjYgMjEuNzUgNi43NjQyIDQwLjE2OSAxMC41MTEgNTUuMjU5bDIyLjc5LTQzLjM5NWMyLjA4MTMtMy45NTQ1IDQuNjgyOS02LjAzNTggNy44MDQ5LTYuMjQzOSA0LjU3ODktMC4zMTIyIDcuMzg4NiAyLjYwMTYgOC41MzMzIDguNzQxNSAyLjYwMTYgMTMuODQxIDUuOTMxNyAyNS42IDkuODg2MiAzNS41OSAyLjcwNTctMjYuNDMzIDcuMjg0Ni00NS40NzYgMTMuNzM3LTU3LjIzNiAxLjU2MS0yLjkxMzggMy44NTA0LTQuMzcwNyA2Ljg2ODMtNC41Nzg5IDIuMzkzNS0wLjIwODEzIDQuNTc4OSAwLjUyMDMzIDYuNTU2MSAyLjA4MTMgMS45NzcyIDEuNTYxIDMuMDE3OSAzLjUzODIgMy4yMjYgNS45MzE3IDAuMTA0MDYgMS44NzMyLTAuMjA4MTMgMy40MzQxLTEuMDQwNyA0Ljk5NTEtNC4wNTg1IDcuNDkyNy03LjM4ODYgMjAuMDg1LTEwLjA5NCAzNy41NjctMi42MDE2IDE2Ljk2My0zLjUzODIgMzAuMTc5LTIuOTEzOCAzOS42NDkgMC4yMDgxMyAyLjYwMTYtMC4yMDgxMyA0Ljg5MTEtMS4yNDg4IDYuODY4My0xLjI0ODggMi4yODk0LTMuMTIyIDMuNTM4Mi01LjUxNTQgMy43NDYzLTIuNzA1NyAwLjIwODEzLTUuNTE1NC0xLjA0MDYtOC4yMjExLTMuODUwNC05LjY3OC05Ljg4NjItMTcuMzc5LTI0LjY2My0yMi45OTgtNDQuMzMyLTYuNzY0MiAxMy4zMi0xMS43NTkgMjMuMzExLTE0Ljk4NSAyOS45NzEtNi4xMzk4IDExLjc1OS0xMS4zNDMgMTcuNzk1LTE1LjcxNCAxOC4xMDctMi44MDk4IDAuMjA4MTMtNS4yMDMzLTIuMTg1NC03LjI4NDYtNy4xODA1LTUuMzA3My0xMy42MzMtMTEuMDMxLTM5Ljk2MS0xNy4xNzEtNzguOTg1LTAuNDE2MjYtMi43MDU3IDAuMjA4MTMtNS4wOTkyIDEuNjY1LTYuOTcyNHptMjIzLjY0IDE2LjMzOGMtMy43NDYzLTYuNTU2MS05LjI2MTgtMTAuNTExLTE2LjY1LTEyLjA3Mi0xLjk3NzItMC40MTYyNi0zLjg1MDQtMC42MjQzOS01LjYxOTUtMC42MjQzOS05Ljk5MDIgMC0xOC4xMDcgNS4yMDMzLTI0LjQ1NSAxNS42MS01LjQxMTQgOC44NDU1LTguMTE3MSAxOC42MjgtOC4xMTcxIDI5LjM0NiAwIDguMDEzIDEuNjY1IDE0Ljg4MSA0Ljk5NTEgMjAuNjA1IDMuNzQ2MyA2LjU1NjEgOS4yNjE4IDEwLjUxMSAxNi42NSAxMi4wNzIgMS45NzcyIDAuNDE2MjYgMy44NTA0IDAuNjI0MzkgNS42MTk1IDAuNjI0MzkgMTAuMDk0IDAgMTguMjExLTUuMjAzMyAyNC40NTUtMTUuNjEgNS40MTE0LTguOTQ5NiA4LjExNzEtMTguNzMyIDguMTE3MS0yOS40NSAwLjEwNDA2LTguMTE3MS0xLjY2NS0xNC44ODEtNC45OTUxLTIwLjUwMXptLTEzLjExMiAyOC44MjZjLTEuNDU2OSA2Ljg2ODMtNC4wNTg1IDExLjk2Ny03LjkwODkgMTUuNDAyLTMuMDE3OSAyLjcwNTctNS44Mjc2IDMuODUwNC04LjQyOTMgMy4zMzAxLTIuNDk3Ni0wLjUyMDMzLTQuNTc4OS0yLjcwNTctNi4xMzk4LTYuNzY0Mi0xLjI0ODgtMy4yMjYtMS44NzMyLTYuNDUyLTEuODczMi05LjQ2OTkgMC0yLjYwMTYgMC4yMDgxMy01LjIwMzMgMC43Mjg0Ni03LjU5NjcgMC45MzY1OS00LjI2NjcgMi43MDU3LTguNDI5MyA1LjUxNTQtMTIuMzg0IDMuNDM0MS01LjA5OTIgNy4wNzY0LTcuMTgwNSAxMC44MjMtNi40NTIgMi40OTc2IDAuNTIwMzMgNC41Nzg5IDIuNzA1NyA2LjEzOTggNi43NjQyIDEuMjQ4OCAzLjIyNiAxLjg3MzIgNi40NTIgMS44NzMyIDkuNDY5OSAwIDIuNzA1Ny0wLjIwODEzIDUuMzA3My0wLjcyODQ2IDcuNzAwOHptLTUyLjAzMy0yOC44MjZjLTMuNzQ2My02LjU1NjEtOS4zNjU5LTEwLjUxMS0xNi42NS0xMi4wNzItMS45NzcyLTAuNDE2MjYtMy44NTA0LTAuNjI0MzktNS42MTk1LTAuNjI0MzktOS45OTAyIDAtMTguMTA3IDUuMjAzMy0yNC40NTUgMTUuNjEtNS40MTE0IDguODQ1NS04LjExNzEgMTguNjI4LTguMTE3MSAyOS4zNDYgMCA4LjAxMyAxLjY2NSAxNC44ODEgNC45OTUxIDIwLjYwNSAzLjc0NjMgNi41NTYxIDkuMjYxOCAxMC41MTEgMTYuNjUgMTIuMDcyIDEuOTc3MiAwLjQxNjI2IDMuODUwNCAwLjYyNDM5IDUuNjE5NSAwLjYyNDM5IDEwLjA5NCAwIDE4LjIxMS01LjIwMzMgMjQuNDU1LTE1LjYxIDUuNDExNC04Ljk0OTYgOC4xMTcxLTE4LjczMiA4LjExNzEtMjkuNDUgMC04LjExNzEtMS42NjUtMTQuODgxLTQuOTk1MS0yMC41MDF6bS0xMy4yMTYgMjguODI2Yy0xLjQ1NjkgNi44NjgzLTQuMDU4NSAxMS45NjctNy45MDg5IDE1LjQwMi0zLjAxNzkgMi43MDU3LTUuODI3NiAzLjg1MDQtOC40MjkzIDMuMzMwMS0yLjQ5NzYtMC41MjAzMy00LjU3ODktMi43MDU3LTYuMTM5OC02Ljc2NDItMS4yNDg4LTMuMjI2LTEuODczMi02LjQ1Mi0xLjg3MzItOS40Njk5IDAtMi42MDE2IDAuMjA4MTMtNS4yMDMzIDAuNzI4NDYtNy41OTY3IDAuOTM2NTgtNC4yNjY3IDIuNzA1Ny04LjQyOTMgNS41MTU0LTEyLjM4NCAzLjQzNDEtNS4wOTkyIDcuMDc2NC03LjE4MDUgMTAuODIzLTYuNDUyIDIuNDk3NiAwLjUyMDMzIDQuNTc4OSAyLjcwNTcgNi4xMzk4IDYuNzY0MiAxLjI0ODggMy4yMjYgMS44NzMyIDYuNDUyIDEuODczMiA5LjQ2OTkgMC4xMDQwNiAyLjcwNTctMC4yMDgxMyA1LjMwNzMtMC43Mjg0NiA3LjcwMDh6IiBmaWxsPSIjZmZmIi8+Cjwvc3ZnPgo=" + }, + "categories": [ + { + "id": 2, + "name": "Sales" + } + ], + "displayName": "WooCommerce Trigger", + "typeVersion": 1 + } + ], + "categories": [ + { + "id": 2, + "name": "Sales" + } + ], + "image": [] + }, + { + "id": 1765, + "name": "Get Slack notifications when new product published on WooCommerce", + "views": 79, + "recentViews": 9577, + "totalViews": 79, + "createdAt": "2022-08-12T12:36:53.409Z", + "description": "This workflow let's a bot in Slack notify a specific channel when a new product in WooCommerce is published and live on the site. \n\n## Prerequisites\n\n[WooCommerce](https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.woocommercetrigger/) account\n[Slack](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.slack/) and a [Slack bot](https://slack.com/help/articles/115005265703-Create-a-bot-for-your-workspace)\n\n## How it works\n\n1. Listen for WooCommerce product creation\n2. If permalink starts with https://[your-url-here].com/product/\n3. Slack bot notifies channel that a new product has been added. \n\nPlease note, you must update the URL in the IF node to match your url. If your WooCommerce doesn't use the slug /product/, that will need to be updated too. \n", + "workflow": { + "id": 1016, + "name": "Woocommerce to slack: notify new product created", + "tags": [ + { + "id": "5", + "name": "FVF", + "createdAt": "2022-07-30T07:43:44.795Z", + "updatedAt": "2022-07-30T07:43:44.795Z" + } + ], + "nodes": [ + { + "name": "If URL has /product/", + "type": "n8n-nodes-base.if", + "position": [ + 640, + 300 + ], + "parameters": { + "conditions": { + "string": [ + { + "value1": "={{$json[\"permalink\"]}}", + "value2": "https://[add-your-url-here]/product/", + "operation": "startsWith" + } + ] + } + }, + "typeVersion": 1 + }, + { + "name": "Send message to slack", + "type": "n8n-nodes-base.slack", + "position": [ + 920, + 260 + ], + "parameters": { + "text": ":new: A new product has been added! :new:", + "channel": "newproducts", + "blocksUi": { + "blocksValues": [] + }, + "attachments": [ + { + "color": "#66FF00", + "fields": { + "item": [ + { + "short": false, + "title": "Name", + "value": "={{$json[\"name\"]}}" + }, + { + "short": true, + "title": "Price", + "value": "={{$json[\"regular_price\"]}}" + }, + { + "short": true, + "title": "Sale Price", + "value": "={{$json[\"sale_price\"]}}" + }, + { + "short": false, + "title": "Link", + "value": "={{$json[\"permalink\"]}}" + } + ] + }, + "footer": "=Added: {{$json[\"date_created\"]}}" + } + ], + "otherOptions": {} + }, + "credentials": { + "slackApi": { + "id": "21", + "name": "FVF bot" + } + }, + "typeVersion": 1 + }, + { + "name": "On product creation", + "type": "n8n-nodes-base.wooCommerceTrigger", + "position": [ + 460, + 300 + ], + "webhookId": "267c4855-6227-4d33-867e-74600097473e", + "parameters": { + "event": "product.created" + }, + "credentials": { + "wooCommerceApi": { + "id": "20", + "name": "WooCommerce account FVF" + } + }, + "typeVersion": 1 + } + ], + "active": true, + "settings": {}, + "connections": { + "On product creation": { + "main": [ + [ + { + "node": "If URL has /product/", + "type": "main", + "index": 0 + } + ] + ] + }, + "If URL has /product/": { + "main": [ + [ + { + "node": "Send message to slack", + "type": "main", + "index": 0 + } + ] + ] + } + } + }, + "workflowInfo": { + "nodeCount": 4, + "nodeTypes": { + "n8n-nodes-base.if": { + "count": 1 + }, + "n8n-nodes-base.slack": { + "count": 1 + }, + "n8n-nodes-base.wooCommerceTrigger": { + "count": 1 + } + } + }, + "user": { + "username": "n8n-team" + }, + "nodes": [ + { + "id": 20, + "icon": "fa:map-signs", + "name": "n8n-nodes-base.if", + "defaults": { + "name": "IF", + "color": "#408000" + }, + "iconData": { + "icon": "map-signs", + "type": "icon" + }, + "categories": [ + { + "id": 9, + "name": "Core Nodes" + } + ], + "displayName": "IF", + "typeVersion": 1 + }, + { + "id": 40, + "icon": "file:slack.svg", + "name": "n8n-nodes-base.slack", + "defaults": { + "name": "Slack" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTUwLjg1MiAxNTAuODUyIiBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48dXNlIHhsaW5rOmhyZWY9IiNhIiB4PSIuOTI2IiB5PSIuOTI2Ii8+PHN5bWJvbCBpZD0iYSIgb3ZlcmZsb3c9InZpc2libGUiPjxnIHN0cm9rZS13aWR0aD0iMS44NTIiPjxwYXRoIGQ9Ik00MC43NDEgOTMuNTVjMC04LjczNSA2LjYwNy0xNS43NzIgMTQuODE1LTE1Ljc3MnMxNC44MTUgNy4wMzcgMTQuODE1IDE1Ljc3MnYzOC44MjRjMCA4LjczNy02LjYwNyAxNS43NzQtMTQuODE1IDE1Ljc3NHMtMTQuODE1LTcuMDM3LTE0LjgxNS0xNS43NzJ6IiBmaWxsPSIjZTAxZTVhIiBzdHJva2U9IiNlMDFlNWEiLz48cGF0aCBkPSJNOTMuNTUgMTA3LjQwOGMtOC43MzUgMC0xNS43NzItNi42MDctMTUuNzcyLTE0LjgxNXM3LjAzNy0xNC44MTUgMTUuNzcyLTE0LjgxNWgzOC44MjZjOC43MzUgMCAxNS43NzIgNi42MDcgMTUuNzcyIDE0LjgxNXMtNy4wMzcgMTQuODE1LTE1Ljc3MiAxNC44MTV6IiBmaWxsPSIjZWNiMjJkIiBzdHJva2U9IiNlY2IyMmQiLz48cGF0aCBkPSJNNzcuNzc4IDE1Ljc3MkM3Ny43NzggNy4wMzcgODQuMzg1IDAgOTIuNTkzIDBzMTQuODE1IDcuMDM3IDE0LjgxNSAxNS43NzJ2MzguODI2YzAgOC43MzUtNi42MDcgMTUuNzcyLTE0LjgxNSAxNS43NzJzLTE0LjgxNS03LjAzNy0xNC44MTUtMTUuNzcyeiIgZmlsbD0iIzJmYjY3YyIgc3Ryb2tlPSIjMmZiNjdjIi8+PHBhdGggZD0iTTE1Ljc3MiA3MC4zNzFDNy4wMzcgNzAuMzcxIDAgNjMuNzYzIDAgNTUuNTU2czcuMDM3LTE0LjgxNSAxNS43NzItMTQuODE1aDM4LjgyNmM4LjczNSAwIDE1Ljc3MiA2LjYwNyAxNS43NzIgMTQuODE1cy03LjAzNyAxNC44MTUtMTUuNzcyIDE0LjgxNXoiIGZpbGw9IiMzNmM1ZjEiIHN0cm9rZT0iIzM2YzVmMSIvPjxnIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiPjxwYXRoIGQ9Ik03Ny43NzggMTMzLjMzM2MwIDguMjA4IDYuNjA3IDE0LjgxNSAxNC44MTUgMTQuODE1czE0LjgxNS02LjYwNyAxNC44MTUtMTQuODE1LTYuNjA3LTE0LjgxNS0xNC44MTUtMTQuODE1SDc3Ljc3OHoiIGZpbGw9IiNlY2IyMmQiIHN0cm9rZT0iI2VjYjIyZCIvPjxwYXRoIGQ9Ik0xMzMuMzM0IDcwLjM3MWgtMTQuODE1VjU1LjU1NmMwLTguMjA3IDYuNjA3LTE0LjgxNSAxNC44MTUtMTQuODE1czE0LjgxNSA2LjYwNyAxNC44MTUgMTQuODE1LTYuNjA3IDE0LjgxNS0xNC44MTUgMTQuODE1eiIgZmlsbD0iIzJmYjY3YyIgc3Ryb2tlPSIjMmZiNjdjIi8+PHBhdGggZD0iTTE0LjgxNSA3Ny43NzhIMjkuNjN2MTQuODE1YzAgOC4yMDctNi42MDcgMTQuODE1LTE0LjgxNSAxNC44MTVTMCAxMDAuOCAwIDkyLjU5M3M2LjYwNy0xNC44MTUgMTQuODE1LTE0LjgxNXoiIGZpbGw9IiNlMDFlNWEiIHN0cm9rZT0iI2UwMWU1YSIvPjxwYXRoIGQ9Ik03MC4zNzEgMTQuODE1VjI5LjYzSDU1LjU1NmMtOC4yMDcgMC0xNC44MTUtNi42MDctMTQuODE1LTE0LjgxNVM0Ny4zNDggMCA1NS41NTYgMHMxNC44MTUgNi42MDcgMTQuODE1IDE0LjgxNXoiIGZpbGw9IiMzNmM1ZjEiIHN0cm9rZT0iIzM2YzVmMSIvPjwvZz48L2c+PC9zeW1ib2w+PC9zdmc+" + }, + "categories": [ + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "Slack", + "typeVersion": 2 + }, + { + "id": 42, + "icon": "fa:play", + "name": "n8n-nodes-base.start", + "defaults": { + "name": "Start", + "color": "#00e000" + }, + "iconData": { + "icon": "play", + "type": "icon" + }, + "categories": [ + { + "id": 9, + "name": "Core Nodes" + } + ], + "displayName": "Start", + "typeVersion": 1 + }, + { + "id": 235, + "icon": "file:wooCommerce.svg", + "name": "n8n-nodes-base.wooCommerceTrigger", + "defaults": { + "name": "WooCommerce Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIC01MCAyNTYgMjUzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KPHRpdGxlPldvb0NvbW1lcmNlIExvZ288L3RpdGxlPgo8bWV0YWRhdGE+CjxyZGY6UkRGPgo8Y2M6V29yayByZGY6YWJvdXQ9IiI+CjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0Pgo8ZGM6dHlwZSByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIi8+CjxkYzp0aXRsZS8+CjwvY2M6V29yaz4KPC9yZGY6UkRGPgo8L21ldGFkYXRhPgo8cGF0aCBkPSJtMjMuNzU5IDBoMjA4LjM4YzEzLjE4NyAwIDIzLjg2MyAxMC42NzUgMjMuODYzIDIzLjg2M3Y3OS41NDJjMCAxMy4xODctMTAuNjc1IDIzLjg2My0yMy44NjMgMjMuODYzaC03NC43MjdsMTAuMjU3IDI1LjExOC00NS4xMDktMjUuMTE4aC05OC42OTVjLTEzLjE4NyAwLTIzLjg2My0xMC42NzUtMjMuODYzLTIzLjg2M3YtNzkuNTQyYy0wLjEwNDY2LTEzLjA4MyAxMC41NzEtMjMuODYzIDIzLjc1OC0yMy44NjN6IiBmaWxsPSIjN2Y1NGIzIi8+CjxwYXRoIGQ9Im0xNC41NzggMjEuNzVjMS40NTY5LTEuOTc3MiAzLjY0MjMtMy4wMTc5IDYuNTU2MS0zLjIyNiA1LjMwNzMtMC40MTYyNiA4LjMyNTIgMi4wODEzIDkuMDUzNyA3LjQ5MjcgMy4yMjYgMjEuNzUgNi43NjQyIDQwLjE2OSAxMC41MTEgNTUuMjU5bDIyLjc5LTQzLjM5NWMyLjA4MTMtMy45NTQ1IDQuNjgyOS02LjAzNTggNy44MDQ5LTYuMjQzOSA0LjU3ODktMC4zMTIyIDcuMzg4NiAyLjYwMTYgOC41MzMzIDguNzQxNSAyLjYwMTYgMTMuODQxIDUuOTMxNyAyNS42IDkuODg2MiAzNS41OSAyLjcwNTctMjYuNDMzIDcuMjg0Ni00NS40NzYgMTMuNzM3LTU3LjIzNiAxLjU2MS0yLjkxMzggMy44NTA0LTQuMzcwNyA2Ljg2ODMtNC41Nzg5IDIuMzkzNS0wLjIwODEzIDQuNTc4OSAwLjUyMDMzIDYuNTU2MSAyLjA4MTMgMS45NzcyIDEuNTYxIDMuMDE3OSAzLjUzODIgMy4yMjYgNS45MzE3IDAuMTA0MDYgMS44NzMyLTAuMjA4MTMgMy40MzQxLTEuMDQwNyA0Ljk5NTEtNC4wNTg1IDcuNDkyNy03LjM4ODYgMjAuMDg1LTEwLjA5NCAzNy41NjctMi42MDE2IDE2Ljk2My0zLjUzODIgMzAuMTc5LTIuOTEzOCAzOS42NDkgMC4yMDgxMyAyLjYwMTYtMC4yMDgxMyA0Ljg5MTEtMS4yNDg4IDYuODY4My0xLjI0ODggMi4yODk0LTMuMTIyIDMuNTM4Mi01LjUxNTQgMy43NDYzLTIuNzA1NyAwLjIwODEzLTUuNTE1NC0xLjA0MDYtOC4yMjExLTMuODUwNC05LjY3OC05Ljg4NjItMTcuMzc5LTI0LjY2My0yMi45OTgtNDQuMzMyLTYuNzY0MiAxMy4zMi0xMS43NTkgMjMuMzExLTE0Ljk4NSAyOS45NzEtNi4xMzk4IDExLjc1OS0xMS4zNDMgMTcuNzk1LTE1LjcxNCAxOC4xMDctMi44MDk4IDAuMjA4MTMtNS4yMDMzLTIuMTg1NC03LjI4NDYtNy4xODA1LTUuMzA3My0xMy42MzMtMTEuMDMxLTM5Ljk2MS0xNy4xNzEtNzguOTg1LTAuNDE2MjYtMi43MDU3IDAuMjA4MTMtNS4wOTkyIDEuNjY1LTYuOTcyNHptMjIzLjY0IDE2LjMzOGMtMy43NDYzLTYuNTU2MS05LjI2MTgtMTAuNTExLTE2LjY1LTEyLjA3Mi0xLjk3NzItMC40MTYyNi0zLjg1MDQtMC42MjQzOS01LjYxOTUtMC42MjQzOS05Ljk5MDIgMC0xOC4xMDcgNS4yMDMzLTI0LjQ1NSAxNS42MS01LjQxMTQgOC44NDU1LTguMTE3MSAxOC42MjgtOC4xMTcxIDI5LjM0NiAwIDguMDEzIDEuNjY1IDE0Ljg4MSA0Ljk5NTEgMjAuNjA1IDMuNzQ2MyA2LjU1NjEgOS4yNjE4IDEwLjUxMSAxNi42NSAxMi4wNzIgMS45NzcyIDAuNDE2MjYgMy44NTA0IDAuNjI0MzkgNS42MTk1IDAuNjI0MzkgMTAuMDk0IDAgMTguMjExLTUuMjAzMyAyNC40NTUtMTUuNjEgNS40MTE0LTguOTQ5NiA4LjExNzEtMTguNzMyIDguMTE3MS0yOS40NSAwLjEwNDA2LTguMTE3MS0xLjY2NS0xNC44ODEtNC45OTUxLTIwLjUwMXptLTEzLjExMiAyOC44MjZjLTEuNDU2OSA2Ljg2ODMtNC4wNTg1IDExLjk2Ny03LjkwODkgMTUuNDAyLTMuMDE3OSAyLjcwNTctNS44Mjc2IDMuODUwNC04LjQyOTMgMy4zMzAxLTIuNDk3Ni0wLjUyMDMzLTQuNTc4OS0yLjcwNTctNi4xMzk4LTYuNzY0Mi0xLjI0ODgtMy4yMjYtMS44NzMyLTYuNDUyLTEuODczMi05LjQ2OTkgMC0yLjYwMTYgMC4yMDgxMy01LjIwMzMgMC43Mjg0Ni03LjU5NjcgMC45MzY1OS00LjI2NjcgMi43MDU3LTguNDI5MyA1LjUxNTQtMTIuMzg0IDMuNDM0MS01LjA5OTIgNy4wNzY0LTcuMTgwNSAxMC44MjMtNi40NTIgMi40OTc2IDAuNTIwMzMgNC41Nzg5IDIuNzA1NyA2LjEzOTggNi43NjQyIDEuMjQ4OCAzLjIyNiAxLjg3MzIgNi40NTIgMS44NzMyIDkuNDY5OSAwIDIuNzA1Ny0wLjIwODEzIDUuMzA3My0wLjcyODQ2IDcuNzAwOHptLTUyLjAzMy0yOC44MjZjLTMuNzQ2My02LjU1NjEtOS4zNjU5LTEwLjUxMS0xNi42NS0xMi4wNzItMS45NzcyLTAuNDE2MjYtMy44NTA0LTAuNjI0MzktNS42MTk1LTAuNjI0MzktOS45OTAyIDAtMTguMTA3IDUuMjAzMy0yNC40NTUgMTUuNjEtNS40MTE0IDguODQ1NS04LjExNzEgMTguNjI4LTguMTE3MSAyOS4zNDYgMCA4LjAxMyAxLjY2NSAxNC44ODEgNC45OTUxIDIwLjYwNSAzLjc0NjMgNi41NTYxIDkuMjYxOCAxMC41MTEgMTYuNjUgMTIuMDcyIDEuOTc3MiAwLjQxNjI2IDMuODUwNCAwLjYyNDM5IDUuNjE5NSAwLjYyNDM5IDEwLjA5NCAwIDE4LjIxMS01LjIwMzMgMjQuNDU1LTE1LjYxIDUuNDExNC04Ljk0OTYgOC4xMTcxLTE4LjczMiA4LjExNzEtMjkuNDUgMC04LjExNzEtMS42NjUtMTQuODgxLTQuOTk1MS0yMC41MDF6bS0xMy4yMTYgMjguODI2Yy0xLjQ1NjkgNi44NjgzLTQuMDU4NSAxMS45NjctNy45MDg5IDE1LjQwMi0zLjAxNzkgMi43MDU3LTUuODI3NiAzLjg1MDQtOC40MjkzIDMuMzMwMS0yLjQ5NzYtMC41MjAzMy00LjU3ODktMi43MDU3LTYuMTM5OC02Ljc2NDItMS4yNDg4LTMuMjI2LTEuODczMi02LjQ1Mi0xLjg3MzItOS40Njk5IDAtMi42MDE2IDAuMjA4MTMtNS4yMDMzIDAuNzI4NDYtNy41OTY3IDAuOTM2NTgtNC4yNjY3IDIuNzA1Ny04LjQyOTMgNS41MTU0LTEyLjM4NCAzLjQzNDEtNS4wOTkyIDcuMDc2NC03LjE4MDUgMTAuODIzLTYuNDUyIDIuNDk3NiAwLjUyMDMzIDQuNTc4OSAyLjcwNTcgNi4xMzk4IDYuNzY0MiAxLjI0ODggMy4yMjYgMS44NzMyIDYuNDUyIDEuODczMiA5LjQ2OTkgMC4xMDQwNiAyLjcwNTctMC4yMDgxMyA1LjMwNzMtMC43Mjg0NiA3LjcwMDh6IiBmaWxsPSIjZmZmIi8+Cjwvc3ZnPgo=" + }, + "categories": [ + { + "id": 2, + "name": "Sales" + } + ], + "displayName": "WooCommerce Trigger", + "typeVersion": 1 + } + ], + "categories": [ + { + "id": 2, + "name": "Sales" + } + ], + "image": [] + }, + { + "id": 1460, + "name": "Notify on Slack when refund is registered in WooCommerce", + "views": 85, + "recentViews": 9541, + "totalViews": 85, + "createdAt": "2022-02-17T15:02:58.662Z", + "description": "This workflow uses a WooCommerce trigger that will run when an order has been updated and the status is refunded.\n\nIf the value of this is over 100 it will post it to a Slack channel.\n\nTo use this workflow you will need to set the credentials to use for the WooCommerce and Slack nodes, You will also need to pick a channel to post the message to.", + "workflow": { + "id": 82, + "name": "New WooCommerce refund to Slack", + "nodes": [ + { + "name": "Order Updated", + "type": "n8n-nodes-base.wooCommerceTrigger", + "position": [ + 320, + 500 + ], + "webhookId": "f7736be3-e978-4a17-b936-7ce9f8ccdb72", + "parameters": { + "event": "order.updated" + }, + "credentials": { + "wooCommerceApi": { + "id": "48", + "name": "WooCommerce account" + } + }, + "typeVersion": 1 + }, + { + "name": "If Refund and Over 100", + "type": "n8n-nodes-base.if", + "position": [ + 540, + 500 + ], + "parameters": { + "conditions": { + "number": [ + { + "value1": "={{$json[\"total\"]}}", + "value2": 100, + "operation": "largerEqual" + } + ], + "string": [ + { + "value1": "={{$json[\"status\"]}}", + "value2": "refunded" + } + ] + } + }, + "typeVersion": 1 + }, + { + "name": "Send to Slack", + "type": "n8n-nodes-base.slack", + "position": [ + 780, + 480 + ], + "parameters": { + "text": ":x: A refund has been issued :x:", + "channel": "woo-commerce", + "blocksUi": { + "blocksValues": [] + }, + "attachments": [ + { + "color": "#FF0000", + "fields": { + "item": [ + { + "short": true, + "title": "Order ID", + "value": "={{$json[\"id\"]}}" + }, + { + "short": true, + "title": "Status", + "value": "={{$json[\"status\"]}}" + }, + { + "short": true, + "title": "Total", + "value": "={{$json[\"currency_symbol\"]}}{{$json[\"total\"]}}" + } + ] + }, + "footer": "=*Order updated:* {{$json[\"date_modified\"]}}" + } + ], + "otherOptions": {} + }, + "credentials": { + "slackApi": { + "id": "53", + "name": "Slack Access Token" + } + }, + "typeVersion": 1 + } + ], + "active": false, + "settings": { + "saveManualExecutions": true, + "saveExecutionProgress": true, + "saveDataSuccessExecution": "all" + }, + "connections": { + "Order Updated": { + "main": [ + [ + { + "node": "If Refund and Over 100", + "type": "main", + "index": 0 + } + ] + ] + }, + "If Refund and Over 100": { + "main": [ + [ + { + "node": "Send to Slack", + "type": "main", + "index": 0 + } + ], + [] + ] + } + } + }, + "workflowInfo": { + "nodeCount": 4, + "nodeTypes": { + "n8n-nodes-base.if": { + "count": 1 + }, + "n8n-nodes-base.slack": { + "count": 1 + }, + "n8n-nodes-base.wooCommerceTrigger": { + "count": 1 + } + } + }, + "user": { + "username": "jon-n8n" + }, + "nodes": [ + { + "id": 20, + "icon": "fa:map-signs", + "name": "n8n-nodes-base.if", + "defaults": { + "name": "IF", + "color": "#408000" + }, + "iconData": { + "icon": "map-signs", + "type": "icon" + }, + "categories": [ + { + "id": 9, + "name": "Core Nodes" + } + ], + "displayName": "IF", + "typeVersion": 1 + }, + { + "id": 40, + "icon": "file:slack.svg", + "name": "n8n-nodes-base.slack", + "defaults": { + "name": "Slack" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTUwLjg1MiAxNTAuODUyIiBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48dXNlIHhsaW5rOmhyZWY9IiNhIiB4PSIuOTI2IiB5PSIuOTI2Ii8+PHN5bWJvbCBpZD0iYSIgb3ZlcmZsb3c9InZpc2libGUiPjxnIHN0cm9rZS13aWR0aD0iMS44NTIiPjxwYXRoIGQ9Ik00MC43NDEgOTMuNTVjMC04LjczNSA2LjYwNy0xNS43NzIgMTQuODE1LTE1Ljc3MnMxNC44MTUgNy4wMzcgMTQuODE1IDE1Ljc3MnYzOC44MjRjMCA4LjczNy02LjYwNyAxNS43NzQtMTQuODE1IDE1Ljc3NHMtMTQuODE1LTcuMDM3LTE0LjgxNS0xNS43NzJ6IiBmaWxsPSIjZTAxZTVhIiBzdHJva2U9IiNlMDFlNWEiLz48cGF0aCBkPSJNOTMuNTUgMTA3LjQwOGMtOC43MzUgMC0xNS43NzItNi42MDctMTUuNzcyLTE0LjgxNXM3LjAzNy0xNC44MTUgMTUuNzcyLTE0LjgxNWgzOC44MjZjOC43MzUgMCAxNS43NzIgNi42MDcgMTUuNzcyIDE0LjgxNXMtNy4wMzcgMTQuODE1LTE1Ljc3MiAxNC44MTV6IiBmaWxsPSIjZWNiMjJkIiBzdHJva2U9IiNlY2IyMmQiLz48cGF0aCBkPSJNNzcuNzc4IDE1Ljc3MkM3Ny43NzggNy4wMzcgODQuMzg1IDAgOTIuNTkzIDBzMTQuODE1IDcuMDM3IDE0LjgxNSAxNS43NzJ2MzguODI2YzAgOC43MzUtNi42MDcgMTUuNzcyLTE0LjgxNSAxNS43NzJzLTE0LjgxNS03LjAzNy0xNC44MTUtMTUuNzcyeiIgZmlsbD0iIzJmYjY3YyIgc3Ryb2tlPSIjMmZiNjdjIi8+PHBhdGggZD0iTTE1Ljc3MiA3MC4zNzFDNy4wMzcgNzAuMzcxIDAgNjMuNzYzIDAgNTUuNTU2czcuMDM3LTE0LjgxNSAxNS43NzItMTQuODE1aDM4LjgyNmM4LjczNSAwIDE1Ljc3MiA2LjYwNyAxNS43NzIgMTQuODE1cy03LjAzNyAxNC44MTUtMTUuNzcyIDE0LjgxNXoiIGZpbGw9IiMzNmM1ZjEiIHN0cm9rZT0iIzM2YzVmMSIvPjxnIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiPjxwYXRoIGQ9Ik03Ny43NzggMTMzLjMzM2MwIDguMjA4IDYuNjA3IDE0LjgxNSAxNC44MTUgMTQuODE1czE0LjgxNS02LjYwNyAxNC44MTUtMTQuODE1LTYuNjA3LTE0LjgxNS0xNC44MTUtMTQuODE1SDc3Ljc3OHoiIGZpbGw9IiNlY2IyMmQiIHN0cm9rZT0iI2VjYjIyZCIvPjxwYXRoIGQ9Ik0xMzMuMzM0IDcwLjM3MWgtMTQuODE1VjU1LjU1NmMwLTguMjA3IDYuNjA3LTE0LjgxNSAxNC44MTUtMTQuODE1czE0LjgxNSA2LjYwNyAxNC44MTUgMTQuODE1LTYuNjA3IDE0LjgxNS0xNC44MTUgMTQuODE1eiIgZmlsbD0iIzJmYjY3YyIgc3Ryb2tlPSIjMmZiNjdjIi8+PHBhdGggZD0iTTE0LjgxNSA3Ny43NzhIMjkuNjN2MTQuODE1YzAgOC4yMDctNi42MDcgMTQuODE1LTE0LjgxNSAxNC44MTVTMCAxMDAuOCAwIDkyLjU5M3M2LjYwNy0xNC44MTUgMTQuODE1LTE0LjgxNXoiIGZpbGw9IiNlMDFlNWEiIHN0cm9rZT0iI2UwMWU1YSIvPjxwYXRoIGQ9Ik03MC4zNzEgMTQuODE1VjI5LjYzSDU1LjU1NmMtOC4yMDcgMC0xNC44MTUtNi42MDctMTQuODE1LTE0LjgxNVM0Ny4zNDggMCA1NS41NTYgMHMxNC44MTUgNi42MDcgMTQuODE1IDE0LjgxNXoiIGZpbGw9IiMzNmM1ZjEiIHN0cm9rZT0iIzM2YzVmMSIvPjwvZz48L2c+PC9zeW1ib2w+PC9zdmc+" + }, + "categories": [ + { + "id": 6, + "name": "Communication" + } + ], + "displayName": "Slack", + "typeVersion": 2 + }, + { + "id": 42, + "icon": "fa:play", + "name": "n8n-nodes-base.start", + "defaults": { + "name": "Start", + "color": "#00e000" + }, + "iconData": { + "icon": "play", + "type": "icon" + }, + "categories": [ + { + "id": 9, + "name": "Core Nodes" + } + ], + "displayName": "Start", + "typeVersion": 1 + }, + { + "id": 235, + "icon": "file:wooCommerce.svg", + "name": "n8n-nodes-base.wooCommerceTrigger", + "defaults": { + "name": "WooCommerce Trigger" + }, + "iconData": { + "type": "file", + "fileBuffer": "data:image/svg+xml;base64,PHN2ZyBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIC01MCAyNTYgMjUzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KPHRpdGxlPldvb0NvbW1lcmNlIExvZ288L3RpdGxlPgo8bWV0YWRhdGE+CjxyZGY6UkRGPgo8Y2M6V29yayByZGY6YWJvdXQ9IiI+CjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0Pgo8ZGM6dHlwZSByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIi8+CjxkYzp0aXRsZS8+CjwvY2M6V29yaz4KPC9yZGY6UkRGPgo8L21ldGFkYXRhPgo8cGF0aCBkPSJtMjMuNzU5IDBoMjA4LjM4YzEzLjE4NyAwIDIzLjg2MyAxMC42NzUgMjMuODYzIDIzLjg2M3Y3OS41NDJjMCAxMy4xODctMTAuNjc1IDIzLjg2My0yMy44NjMgMjMuODYzaC03NC43MjdsMTAuMjU3IDI1LjExOC00NS4xMDktMjUuMTE4aC05OC42OTVjLTEzLjE4NyAwLTIzLjg2My0xMC42NzUtMjMuODYzLTIzLjg2M3YtNzkuNTQyYy0wLjEwNDY2LTEzLjA4MyAxMC41NzEtMjMuODYzIDIzLjc1OC0yMy44NjN6IiBmaWxsPSIjN2Y1NGIzIi8+CjxwYXRoIGQ9Im0xNC41NzggMjEuNzVjMS40NTY5LTEuOTc3MiAzLjY0MjMtMy4wMTc5IDYuNTU2MS0zLjIyNiA1LjMwNzMtMC40MTYyNiA4LjMyNTIgMi4wODEzIDkuMDUzNyA3LjQ5MjcgMy4yMjYgMjEuNzUgNi43NjQyIDQwLjE2OSAxMC41MTEgNTUuMjU5bDIyLjc5LTQzLjM5NWMyLjA4MTMtMy45NTQ1IDQuNjgyOS02LjAzNTggNy44MDQ5LTYuMjQzOSA0LjU3ODktMC4zMTIyIDcuMzg4NiAyLjYwMTYgOC41MzMzIDguNzQxNSAyLjYwMTYgMTMuODQxIDUuOTMxNyAyNS42IDkuODg2MiAzNS41OSAyLjcwNTctMjYuNDMzIDcuMjg0Ni00NS40NzYgMTMuNzM3LTU3LjIzNiAxLjU2MS0yLjkxMzggMy44NTA0LTQuMzcwNyA2Ljg2ODMtNC41Nzg5IDIuMzkzNS0wLjIwODEzIDQuNTc4OSAwLjUyMDMzIDYuNTU2MSAyLjA4MTMgMS45NzcyIDEuNTYxIDMuMDE3OSAzLjUzODIgMy4yMjYgNS45MzE3IDAuMTA0MDYgMS44NzMyLTAuMjA4MTMgMy40MzQxLTEuMDQwNyA0Ljk5NTEtNC4wNTg1IDcuNDkyNy03LjM4ODYgMjAuMDg1LTEwLjA5NCAzNy41NjctMi42MDE2IDE2Ljk2My0zLjUzODIgMzAuMTc5LTIuOTEzOCAzOS42NDkgMC4yMDgxMyAyLjYwMTYtMC4yMDgxMyA0Ljg5MTEtMS4yNDg4IDYuODY4My0xLjI0ODggMi4yODk0LTMuMTIyIDMuNTM4Mi01LjUxNTQgMy43NDYzLTIuNzA1NyAwLjIwODEzLTUuNTE1NC0xLjA0MDYtOC4yMjExLTMuODUwNC05LjY3OC05Ljg4NjItMTcuMzc5LTI0LjY2My0yMi45OTgtNDQuMzMyLTYuNzY0MiAxMy4zMi0xMS43NTkgMjMuMzExLTE0Ljk4NSAyOS45NzEtNi4xMzk4IDExLjc1OS0xMS4zNDMgMTcuNzk1LTE1LjcxNCAxOC4xMDctMi44MDk4IDAuMjA4MTMtNS4yMDMzLTIuMTg1NC03LjI4NDYtNy4xODA1LTUuMzA3My0xMy42MzMtMTEuMDMxLTM5Ljk2MS0xNy4xNzEtNzguOTg1LTAuNDE2MjYtMi43MDU3IDAuMjA4MTMtNS4wOTkyIDEuNjY1LTYuOTcyNHptMjIzLjY0IDE2LjMzOGMtMy43NDYzLTYuNTU2MS05LjI2MTgtMTAuNTExLTE2LjY1LTEyLjA3Mi0xLjk3NzItMC40MTYyNi0zLjg1MDQtMC42MjQzOS01LjYxOTUtMC42MjQzOS05Ljk5MDIgMC0xOC4xMDcgNS4yMDMzLTI0LjQ1NSAxNS42MS01LjQxMTQgOC44NDU1LTguMTE3MSAxOC42MjgtOC4xMTcxIDI5LjM0NiAwIDguMDEzIDEuNjY1IDE0Ljg4MSA0Ljk5NTEgMjAuNjA1IDMuNzQ2MyA2LjU1NjEgOS4yNjE4IDEwLjUxMSAxNi42NSAxMi4wNzIgMS45NzcyIDAuNDE2MjYgMy44NTA0IDAuNjI0MzkgNS42MTk1IDAuNjI0MzkgMTAuMDk0IDAgMTguMjExLTUuMjAzMyAyNC40NTUtMTUuNjEgNS40MTE0LTguOTQ5NiA4LjExNzEtMTguNzMyIDguMTE3MS0yOS40NSAwLjEwNDA2LTguMTE3MS0xLjY2NS0xNC44ODEtNC45OTUxLTIwLjUwMXptLTEzLjExMiAyOC44MjZjLTEuNDU2OSA2Ljg2ODMtNC4wNTg1IDExLjk2Ny03LjkwODkgMTUuNDAyLTMuMDE3OSAyLjcwNTctNS44Mjc2IDMuODUwNC04LjQyOTMgMy4zMzAxLTIuNDk3Ni0wLjUyMDMzLTQuNTc4OS0yLjcwNTctNi4xMzk4LTYuNzY0Mi0xLjI0ODgtMy4yMjYtMS44NzMyLTYuNDUyLTEuODczMi05LjQ2OTkgMC0yLjYwMTYgMC4yMDgxMy01LjIwMzMgMC43Mjg0Ni03LjU5NjcgMC45MzY1OS00LjI2NjcgMi43MDU3LTguNDI5MyA1LjUxNTQtMTIuMzg0IDMuNDM0MS01LjA5OTIgNy4wNzY0LTcuMTgwNSAxMC44MjMtNi40NTIgMi40OTc2IDAuNTIwMzMgNC41Nzg5IDIuNzA1NyA2LjEzOTggNi43NjQyIDEuMjQ4OCAzLjIyNiAxLjg3MzIgNi40NTIgMS44NzMyIDkuNDY5OSAwIDIuNzA1Ny0wLjIwODEzIDUuMzA3My0wLjcyODQ2IDcuNzAwOHptLTUyLjAzMy0yOC44MjZjLTMuNzQ2My02LjU1NjEtOS4zNjU5LTEwLjUxMS0xNi42NS0xMi4wNzItMS45NzcyLTAuNDE2MjYtMy44NTA0LTAuNjI0MzktNS42MTk1LTAuNjI0MzktOS45OTAyIDAtMTguMTA3IDUuMjAzMy0yNC40NTUgMTUuNjEtNS40MTE0IDguODQ1NS04LjExNzEgMTguNjI4LTguMTE3MSAyOS4zNDYgMCA4LjAxMyAxLjY2NSAxNC44ODEgNC45OTUxIDIwLjYwNSAzLjc0NjMgNi41NTYxIDkuMjYxOCAxMC41MTEgMTYuNjUgMTIuMDcyIDEuOTc3MiAwLjQxNjI2IDMuODUwNCAwLjYyNDM5IDUuNjE5NSAwLjYyNDM5IDEwLjA5NCAwIDE4LjIxMS01LjIwMzMgMjQuNDU1LTE1LjYxIDUuNDExNC04Ljk0OTYgOC4xMTcxLTE4LjczMiA4LjExNzEtMjkuNDUgMC04LjExNzEtMS42NjUtMTQuODgxLTQuOTk1MS0yMC41MDF6bS0xMy4yMTYgMjguODI2Yy0xLjQ1NjkgNi44NjgzLTQuMDU4NSAxMS45NjctNy45MDg5IDE1LjQwMi0zLjAxNzkgMi43MDU3LTUuODI3NiAzLjg1MDQtOC40MjkzIDMuMzMwMS0yLjQ5NzYtMC41MjAzMy00LjU3ODktMi43MDU3LTYuMTM5OC02Ljc2NDItMS4yNDg4LTMuMjI2LTEuODczMi02LjQ1Mi0xLjg3MzItOS40Njk5IDAtMi42MDE2IDAuMjA4MTMtNS4yMDMzIDAuNzI4NDYtNy41OTY3IDAuOTM2NTgtNC4yNjY3IDIuNzA1Ny04LjQyOTMgNS41MTU0LTEyLjM4NCAzLjQzNDEtNS4wOTkyIDcuMDc2NC03LjE4MDUgMTAuODIzLTYuNDUyIDIuNDk3NiAwLjUyMDMzIDQuNTc4OSAyLjcwNTcgNi4xMzk4IDYuNzY0MiAxLjI0ODggMy4yMjYgMS44NzMyIDYuNDUyIDEuODczMiA5LjQ2OTkgMC4xMDQwNiAyLjcwNTctMC4yMDgxMyA1LjMwNzMtMC43Mjg0NiA3LjcwMDh6IiBmaWxsPSIjZmZmIi8+Cjwvc3ZnPgo=" + }, + "categories": [ + { + "id": 2, + "name": "Sales" + } + ], + "displayName": "WooCommerce Trigger", + "typeVersion": 1 + } + ], + "categories": [ + { + "id": 2, + "name": "Sales" + }, + { + "id": 8, + "name": "Finance & Accounting" + } + ], + "image": [] + } + ], + "image": [] + } +} diff --git a/cypress/fixtures/Test_Template_1.json b/cypress/fixtures/Test_Template_1.json index f15970677e1..1995beca529 100644 --- a/cypress/fixtures/Test_Template_1.json +++ b/cypress/fixtures/Test_Template_1.json @@ -1,7 +1,7 @@ { "workflow": { "id": 1205, - "name": "Promote new Shopify products on Twitter and Telegram", + "name": "Promote new Shopify products", "views": 478, "recentViews": 9880, "totalViews": 478, diff --git a/cypress/fixtures/templates_search/sales_templates_search_response.json b/cypress/fixtures/templates_search/sales_templates_search_response.json index 4efbb3585b5..d4f90991b31 100644 --- a/cypress/fixtures/templates_search/sales_templates_search_response.json +++ b/cypress/fixtures/templates_search/sales_templates_search_response.json @@ -1202,7 +1202,7 @@ }, { "id": 1205, - "name": "Promote New Shopify Products on Social Media (Twitter and Telegram)", + "name": "Promote New Shopify Products", "totalViews": 219, "recentViews": 0, "user": { From 3f9127955a357bfcaff8ee6e22cdb04ab1010bd5 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Fri, 15 Nov 2024 19:11:58 -0500 Subject: [PATCH 12/18] refactor(editor): Stop using `$locale` in favor of the `i18n` composable (#11731) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ --- packages/editor-ui/src/__tests__/render.ts | 3 +- .../AskAssistant/NewAssistantSessionModal.vue | 4 +- .../src/components/BinaryDataDisplay.vue | 9 +- .../src/components/BinaryDataDisplayEmbed.vue | 7 +- .../src/components/CanvasControls.vue | 16 +- .../CodeNodeEditor/CodeNodeEditor.vue | 4 +- .../src/components/CodeNodeEditor/types.ts | 2 - .../src/components/CollectionParameter.vue | 2 +- .../CommunityPlusEnrollmentModal.vue | 2 +- .../CredentialEdit/AuthTypeSelector.vue | 7 +- .../CredentialEdit/CredentialConfig.vue | 48 +++--- .../CredentialEdit/CredentialEdit.vue | 6 +- .../CredentialEdit/GoogleAuthButton.vue | 4 +- .../components/CredentialEdit/OauthButton.vue | 4 +- .../src/components/CredentialsSelect.vue | 5 +- .../src/components/CredentialsSelectModal.vue | 10 +- .../editor-ui/src/components/GoBackButton.vue | 4 +- .../src/components/ImportCurlParameter.vue | 4 +- .../editor-ui/src/components/InputPanel.vue | 28 ++-- .../components/MainHeader/WorkflowDetails.vue | 17 ++- .../editor-ui/src/components/MainSidebar.vue | 30 ++-- .../src/components/MfaSetupModal.vue | 48 +++--- .../src/components/NDVSubConnections.vue | 6 +- .../src/components/Node/NodeCreation.vue | 6 +- .../Node/NodeCreator/Modes/NodesMode.vue | 2 +- .../Node/NodeCreator/Panel/NoResults.vue | 18 ++- .../Node/NodeCreator/Panel/NodesListPanel.vue | 4 +- .../src/components/NodeCredentials.vue | 5 +- .../src/components/NodeDetailsView.vue | 6 +- .../editor-ui/src/components/NodeSettings.vue | 14 +- .../editor-ui/src/components/NodeTitle.vue | 9 +- .../src/components/ParameterInput.vue | 12 +- .../src/components/ParameterInputExpanded.vue | 8 +- .../src/components/ParameterInputList.vue | 16 +- .../src/components/ParameterIssues.vue | 5 +- .../src/components/PersonalizationModal.vue | 6 +- .../ResourceLocator/ResourceLocator.vue | 12 +- .../ResourceLocatorDropdown.vue | 11 +- .../ResourceMapper/MappingFields.vue | 5 +- packages/editor-ui/src/components/RunData.vue | 74 +++++---- .../RunDataAi/AiRunContentBlock.vue | 2 +- .../src/components/RunDataAi/RunDataAi.vue | 8 +- .../components/RunDataAi/RunDataAiContent.vue | 16 +- .../src/components/RunDataSchema.vue | 14 +- .../editor-ui/src/components/SaveButton.vue | 2 +- .../EventSelection.ee.vue | 21 ++- packages/editor-ui/src/components/Sticky.vue | 6 +- .../src/components/TagsManager/NoTagsView.vue | 7 +- .../TagsManager/TagsView/TagsTable.vue | 27 ++-- .../TagsManager/TagsView/TagsTableHeader.vue | 7 +- .../src/components/TemplateDetails.vue | 2 +- .../editor-ui/src/components/TemplateList.vue | 5 +- .../editor-ui/src/components/TextEdit.vue | 9 +- .../editor-ui/src/components/VersionCard.vue | 10 +- .../src/components/Workers/WorkerCard.ee.vue | 5 +- .../Workers/WorkerChartsAccordion.ee.vue | 5 +- .../Workers/WorkerJobAccordion.ee.vue | 7 +- .../Workers/WorkerNetAccordion.ee.vue | 2 +- .../src/components/WorkflowSettings.vue | 44 +++--- .../elements/buttons/CanvasControlButtons.vue | 18 +-- .../nodes/render-types/CanvasNodeAddNodes.vue | 6 +- .../nodes/render-types/CanvasNodeDefault.vue | 2 +- .../parts/CanvasNodeStatusIcons.vue | 4 +- .../components/executions/ExecutionsTime.vue | 2 +- .../WorkflowExecutionAnnotationPanel.ee.vue | 14 +- .../WorkflowExecutionsInfoAccordion.vue | 13 +- .../workflow/WorkflowExecutionsSidebar.vue | 8 +- packages/editor-ui/src/main.ts | 3 +- .../editor-ui/src/plugins/i18n/docs/README.md | 2 +- packages/editor-ui/src/plugins/i18n/index.ts | 14 +- packages/editor-ui/src/shims-vue.d.ts | 1 - .../editor-ui/src/views/CanvasAddButton.vue | 7 +- packages/editor-ui/src/views/ErrorView.vue | 9 +- packages/editor-ui/src/views/NodeView.vue | 141 +++++++++--------- .../editor-ui/src/views/ProjectSettings.vue | 45 +++--- .../editor-ui/src/views/VariablesView.vue | 18 +-- packages/editor-ui/src/views/WorkerView.vue | 14 +- 77 files changed, 529 insertions(+), 474 deletions(-) diff --git a/packages/editor-ui/src/__tests__/render.ts b/packages/editor-ui/src/__tests__/render.ts index e46b6159934..3bc310f7908 100644 --- a/packages/editor-ui/src/__tests__/render.ts +++ b/packages/editor-ui/src/__tests__/render.ts @@ -1,6 +1,6 @@ import type { Plugin } from 'vue'; import { render } from '@testing-library/vue'; -import { i18nInstance, I18nPlugin } from '@/plugins/i18n'; +import { i18nInstance } from '@/plugins/i18n'; import { GlobalComponentsPlugin } from '@/plugins/components'; import { GlobalDirectivesPlugin } from '@/plugins/directives'; import { FontAwesomePlugin } from '@/plugins/icons'; @@ -32,7 +32,6 @@ const defaultOptions = { 'vue-json-pretty': vueJsonPretty, }, plugins: [ - I18nPlugin, i18nInstance, PiniaVuePlugin, FontAwesomePlugin, diff --git a/packages/editor-ui/src/components/AskAssistant/NewAssistantSessionModal.vue b/packages/editor-ui/src/components/AskAssistant/NewAssistantSessionModal.vue index 27e0054d773..4113aeecf41 100644 --- a/packages/editor-ui/src/components/AskAssistant/NewAssistantSessionModal.vue +++ b/packages/editor-ui/src/components/AskAssistant/NewAssistantSessionModal.vue @@ -66,9 +66,9 @@ const startNewSession = async () => {