From 1fc087aeac76b91d2b5331b861d5335571d440e1 Mon Sep 17 00:00:00 2001 From: martmull Date: Wed, 16 Jul 2025 16:23:35 +0200 Subject: [PATCH] 13233 zapier update route to create workflow apikey etc (#13239) Fix webhook creation utils and some tests --- packages/twenty-zapier/package.json | 15 +-- packages/twenty-zapier/project.json | 35 ++++++ .../src/test/triggers/trigger_record.test.ts | 107 +++++++++++++----- .../src/test/utils/computeInputFields.test.ts | 27 ----- .../src/test/utils/handleQueryParams.test.ts | 2 +- .../src/utils/triggers/triggers.utils.ts | 7 +- yarn.lock | 3 + 7 files changed, 130 insertions(+), 66 deletions(-) diff --git a/packages/twenty-zapier/package.json b/packages/twenty-zapier/package.json index 7c826bf46..851407bcb 100644 --- a/packages/twenty-zapier/package.json +++ b/packages/twenty-zapier/package.json @@ -1,16 +1,8 @@ { "name": "twenty-zapier", - "version": "2.0.1", + "version": "2.0.2", "description": "Effortlessly sync Twenty with 3000+ apps. Automate tasks, boost productivity, and supercharge your customer relationships!", "main": "src/index.ts", - "scripts": { - "nx": "NX_DEFAULT_PROJECT=twenty-zapier node ../../node_modules/nx/bin/nx.js", - "format": "prettier . --write \"!build\"", - "test": "yarn build && jest --testTimeout 10000 --rootDir ./lib/test", - "validate": "yarn build && zapier validate", - "versions": "yarn build && zapier versions", - "watch": "yarn clean && npx tsc --watch" - }, "engines": { "node": "^22.12.0", "npm": "please-use-yarn", @@ -21,8 +13,11 @@ "convertedByCLIVersion": "15.4.1" }, "dependencies": { + "@sniptt/guards": "^0.2.0", "dotenv": "^16.4.5", - "zapier-platform-core": "15.5.1" + "libphonenumber-js": "^1.10.26", + "zapier-platform-core": "15.5.1", + "zod": "3.23.8" }, "devDependencies": { "jest": "29.7.0", diff --git a/packages/twenty-zapier/project.json b/packages/twenty-zapier/project.json index b8e858dc8..825ab801b 100644 --- a/packages/twenty-zapier/project.json +++ b/packages/twenty-zapier/project.json @@ -12,6 +12,41 @@ }, "dependsOn": ["^build"] }, + "format": { + "executor": "nx:run-commands", + "options": { + "cwd": "{projectRoot}", + "commands": ["prettier . --write \"!build\""] + } + }, + "test": { + "executor": "nx:run-commands", + "options": { + "cwd": "{projectRoot}", + "commands": ["NODE_ENV=test && nx run twenty-zapier:build && jest --testTimeout 10000 --rootDir ./lib/test"] + } + }, + "validate": { + "executor": "nx:run-commands", + "options": { + "cwd": "{projectRoot}", + "commands": ["nx run twenty-zapier:build && zapier validate"] + } + }, + "versions": { + "executor": "nx:run-commands", + "options": { + "cwd": "{projectRoot}", + "commands": ["nx run twenty-zapier:build && zapier versions"] + } + }, + "watch":{ + "executor": "nx:run-commands", + "options": { + "cwd": "{projectRoot}", + "commands": ["nx run twenty-zapier:clean && npx tsc --watch"] + } + }, "clean": { "executor": "nx:run-commands", "options": { diff --git a/packages/twenty-zapier/src/test/triggers/trigger_record.test.ts b/packages/twenty-zapier/src/test/triggers/trigger_record.test.ts index ec5d1d73e..435256145 100644 --- a/packages/twenty-zapier/src/test/triggers/trigger_record.test.ts +++ b/packages/twenty-zapier/src/test/triggers/trigger_record.test.ts @@ -10,56 +10,68 @@ const appTester = createAppTester(App); describe('triggers.trigger_record.created', () => { test('should succeed to subscribe', async () => { const bundle = getBundle({}); + bundle.inputData.nameSingular = 'company'; bundle.inputData.operation = DatabaseEventAction.CREATED; bundle.targetUrl = 'https://test.com'; + const result = await appTester( App.triggers[triggerRecordKey].operation.performSubscribe, bundle, ); + expect(result).toBeDefined(); expect(result.id).toBeDefined(); + const checkDbResult = await appTester( (z: ZObject, bundle: Bundle) => requestDb( z, bundle, - `query webhook {webhooks(filter: {id: {eq: "${result.id}"}}){edges {node {id operations}}}}`, + `query webhook {webhook(input: {id: "${result.id}"}){id operations}}`, ), bundle, ); - expect(checkDbResult.data.webhooks.edges[0].node.operations[0]).toEqual( - 'company.created', - ); + + expect(checkDbResult.data.webhook.operations[0]).toEqual('company.created'); }); + test('should succeed to unsubscribe', async () => { const bundle = getBundle({}); + bundle.inputData.nameSingular = 'company'; bundle.inputData.operation = DatabaseEventAction.CREATED; bundle.targetUrl = 'https://test.com'; + const result = await appTester( App.triggers[triggerRecordKey].operation.performSubscribe, bundle, ); + const unsubscribeBundle = getBundle({}); + unsubscribeBundle.subscribeData = { id: result.id }; + const unsubscribeResult = await appTester( App.triggers[triggerRecordKey].operation.performUnsubscribe, unsubscribeBundle, ); + expect(unsubscribeResult).toBeDefined(); expect(unsubscribeResult.id).toEqual(result.id); + const checkDbResult = await appTester( (z: ZObject, bundle: Bundle) => - requestDb( - z, - bundle, - `query webhook {webhooks(filter: {id: {eq: "${result.id}"}}){edges {node {id operations}}}}`, - ), + requestDb(z, bundle, `query webhook {webhooks {id}}`), bundle, ); - expect(checkDbResult.data.webhooks.edges.length).toEqual(0); + expect( + // @ts-expect-error legacy noImplicitAny + checkDbResult.data.webhooks.filter((webhook) => webhook.id === result.id) + .length, + ).toEqual(0); }); + test('should load company from webhook', async () => { const bundle = { cleanedRequest: { @@ -77,24 +89,33 @@ describe('triggers.trigger_record.created', () => { }, }, }; + const results = await appTester( App.triggers[triggerRecordKey].operation.perform, bundle, ); + expect(results.length).toEqual(1); + const company = results[0]; + expect(company.record.id).toEqual('d6ccb1d1-a90b-4822-a992-a0dd946592c9'); }); it('should load companies from list', async () => { const bundle = getBundle({}); + bundle.inputData.nameSingular = 'company'; bundle.inputData.operation = DatabaseEventAction.CREATED; + const results = await appTester( App.triggers[triggerRecordKey].operation.performList, bundle, ); + expect(results.length).toBeGreaterThan(1); + const firstCompany = results[0]; + expect(firstCompany.record).toBeDefined(); }); }); @@ -102,66 +123,83 @@ describe('triggers.trigger_record.created', () => { describe('triggers.trigger_record.update', () => { test('should succeed to subscribe', async () => { const bundle = getBundle({}); + bundle.inputData.nameSingular = 'company'; bundle.inputData.operation = DatabaseEventAction.UPDATED; bundle.targetUrl = 'https://test.com'; + const result = await appTester( App.triggers[triggerRecordKey].operation.performSubscribe, bundle, ); + expect(result).toBeDefined(); expect(result.id).toBeDefined(); + const checkDbResult = await appTester( (z: ZObject, bundle: Bundle) => requestDb( z, bundle, - `query webhook {webhooks(filter: {id: {eq: "${result.id}"}}){edges {node {id operations}}}}`, + `query webhook {webhook(input: {id: "${result.id}"}){id operations}}`, ), bundle, ); - expect(checkDbResult.data.webhooks.edges[0].node.operations[0]).toEqual( + + expect(checkDbResult.data.webhooks.operations[0]).toEqual( 'company.updated', ); }); test('should succeed to unsubscribe', async () => { const bundle = getBundle({}); + bundle.inputData.nameSingular = 'company'; bundle.inputData.operation = DatabaseEventAction.UPDATED; bundle.targetUrl = 'https://test.com'; + const result = await appTester( App.triggers[triggerRecordKey].operation.performSubscribe, bundle, ); + const unsubscribeBundle = getBundle({}); + unsubscribeBundle.subscribeData = { id: result.id }; + const unsubscribeResult = await appTester( App.triggers[triggerRecordKey].operation.performUnsubscribe, unsubscribeBundle, ); + expect(unsubscribeResult).toBeDefined(); expect(unsubscribeResult.id).toEqual(result.id); + const checkDbResult = await appTester( (z: ZObject, bundle: Bundle) => - requestDb( - z, - bundle, - `query webhook {webhooks(filter: {id: {eq: "${result.id}"}}){edges {node {id operations}}}}`, - ), + requestDb(z, bundle, `query webhook {webhooks {id}}`), bundle, ); - expect(checkDbResult.data.webhooks.edges.length).toEqual(0); + expect( + // @ts-expect-error legacy noImplicitAny + checkDbResult.data.webhooks.filter((webhook) => webhook.id === result.id) + .length, + ).toEqual(0); }); it('should load companies from list', async () => { const bundle = getBundle({}); + bundle.inputData.nameSingular = 'company'; bundle.inputData.operation = DatabaseEventAction.UPDATED; + const results = await appTester( App.triggers[triggerRecordKey].operation.performList, bundle, ); + expect(results.length).toBeGreaterThan(1); + const firstCompany = results[0]; + expect(firstCompany.record).toBeDefined(); expect(firstCompany.updatedFields).toBeDefined(); }); @@ -170,66 +208,83 @@ describe('triggers.trigger_record.update', () => { describe('triggers.trigger_record.delete', () => { test('should succeed to subscribe', async () => { const bundle = getBundle({}); + bundle.inputData.nameSingular = 'company'; bundle.inputData.operation = DatabaseEventAction.DELETED; bundle.targetUrl = 'https://test.com'; + const result = await appTester( App.triggers[triggerRecordKey].operation.performSubscribe, bundle, ); + expect(result).toBeDefined(); expect(result.id).toBeDefined(); + const checkDbResult = await appTester( (z: ZObject, bundle: Bundle) => requestDb( z, bundle, - `query webhook {webhooks(filter: {id: {eq: "${result.id}"}}){edges {node {id operations}}}}`, + `query webhook {webhook(input: {id: "${result.id}"}){id operations}}`, ), bundle, ); - expect(checkDbResult.data.webhooks.edges[0].node.operations[0]).toEqual( + + expect(checkDbResult.data.webhooks.operations[0]).toEqual( 'company.deleted', ); }); test('should succeed to unsubscribe', async () => { const bundle = getBundle({}); + bundle.inputData.nameSingular = 'company'; bundle.inputData.operation = DatabaseEventAction.DELETED; bundle.targetUrl = 'https://test.com'; + const result = await appTester( App.triggers[triggerRecordKey].operation.performSubscribe, bundle, ); + const unsubscribeBundle = getBundle({}); + unsubscribeBundle.subscribeData = { id: result.id }; + const unsubscribeResult = await appTester( App.triggers[triggerRecordKey].operation.performUnsubscribe, unsubscribeBundle, ); + expect(unsubscribeResult).toBeDefined(); expect(unsubscribeResult.id).toEqual(result.id); + const checkDbResult = await appTester( (z: ZObject, bundle: Bundle) => - requestDb( - z, - bundle, - `query webhook {webhooks(filter: {id: {eq: "${result.id}"}}){edges {node {id operations}}}}`, - ), + requestDb(z, bundle, `query webhook {webhooks {id}}`), bundle, ); - expect(checkDbResult.data.webhooks.edges.length).toEqual(0); + expect( + // @ts-expect-error legacy noImplicitAny + checkDbResult.data.webhooks.filter((webhook) => webhook.id === result.id) + .length, + ).toEqual(0); }); it('should load companies from list', async () => { const bundle = getBundle({}); + bundle.inputData.nameSingular = 'company'; bundle.inputData.operation = DatabaseEventAction.DELETED; + const results = await appTester( App.triggers[triggerRecordKey].operation.performList, bundle, ); + expect(results.length).toBeGreaterThan(1); + const firstCompany = results[0]; + expect(firstCompany).toBeDefined(); expect(firstCompany.record.id).toBeDefined(); expect(Object.keys(firstCompany).length).toEqual(1); diff --git a/packages/twenty-zapier/src/test/utils/computeInputFields.test.ts b/packages/twenty-zapier/src/test/utils/computeInputFields.test.ts index 007791e4a..2d794e298 100644 --- a/packages/twenty-zapier/src/test/utils/computeInputFields.test.ts +++ b/packages/twenty-zapier/src/test/utils/computeInputFields.test.ts @@ -179,24 +179,6 @@ describe('computeInputFields', () => { list: false, placeholder: undefined, }, - { - key: 'xLink__url', - label: 'X: Url', - type: 'string', - helpText: 'Contact’s X/Twitter account: Link Url', - required: false, - list: false, - placeholder: undefined, - }, - { - key: 'xLink__label', - label: 'X: Label', - type: 'string', - helpText: 'Contact’s X/Twitter account: Link Label', - required: false, - list: false, - placeholder: undefined, - }, { key: 'whatsapp__primaryLinkLabel', label: 'Whatsapp: Primary Link Label', @@ -224,15 +206,6 @@ describe('computeInputFields', () => { list: true, placeholder: '{ url: "", label: "" }', }, - { - key: 'email', - label: 'Email', - type: 'string', - helpText: 'Contact’s Email', - required: false, - list: false, - placeholder: undefined, - }, { key: 'companyId', label: 'Company id (foreign key)', diff --git a/packages/twenty-zapier/src/test/utils/handleQueryParams.test.ts b/packages/twenty-zapier/src/test/utils/handleQueryParams.test.ts index 6453b906f..ac5f319ce 100644 --- a/packages/twenty-zapier/src/test/utils/handleQueryParams.test.ts +++ b/packages/twenty-zapier/src/test/utils/handleQueryParams.test.ts @@ -45,7 +45,7 @@ describe('utils.handleQueryParams', () => { 'linkedinUrl: {url: "/linkedin_url", label: "Test linkedinUrl"}, ' + 'whatsapp: {primaryLinkUrl: "/whatsapp_url", primaryLinkLabel: "Whatsapp Link", secondaryLinks: [{url: \'/secondary_whatsapp_url\',label: \'Secondary Whatsapp Link\'}]}, ' + 'emails: {primaryEmail: "primary@email.com", additionalEmails: ["secondary@email.com"]}, ' + - 'phones: {primaryPhoneNumber: "322110011", primaryPhoneCountryCode: "FR", primaryPhoneCallingCode: "+33", additionalPhones: [{ phoneNumber: \'322110012\', countryCode: \'+33\' }]}, ' + + 'phones: {primaryPhoneNumber: "322110011", primaryPhoneCountryCode: "FR", primaryPhoneCallingCode: "+33", additionalPhones: [{ phoneNumber: \'322110012\', countryCode: \'FR\', callingCode: \'+33\' }]}, ' + 'xUrl: {url: "/x_url", label: "Test xUrl"}, ' + 'annualRecurringRevenue: 100000, ' + 'idealCustomerProfile: true, ' + diff --git a/packages/twenty-zapier/src/utils/triggers/triggers.utils.ts b/packages/twenty-zapier/src/utils/triggers/triggers.utils.ts index cc0e8c83a..89f01f479 100644 --- a/packages/twenty-zapier/src/utils/triggers/triggers.utils.ts +++ b/packages/twenty-zapier/src/utils/triggers/triggers.utils.ts @@ -18,13 +18,15 @@ export const performSubscribe = async (z: ZObject, bundle: Bundle) => { operations: [ `${bundle.inputData.nameSingular}.${bundle.inputData.operation}`, ], + secret: '', }; const result = await requestDb( z, bundle, - `mutation createWebhook {createWebhook(data:{${handleQueryParams( + `mutation createWebhook {createWebhook(input:{${handleQueryParams( data, )}}) {id}}`, + 'metadata', ); return result.data.createWebhook; }; @@ -34,7 +36,8 @@ export const performUnsubscribe = async (z: ZObject, bundle: Bundle) => { const result = await requestDb( z, bundle, - `mutation deleteWebhook {deleteWebhook(${handleQueryParams(data)}) {id}}`, + `mutation deleteWebhook {deleteWebhook(${handleQueryParams(data)})}`, + 'metadata', ); return result.data.deleteWebhook; }; diff --git a/yarn.lock b/yarn.lock index 9545ecff2..bcb8fb3ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -57174,11 +57174,14 @@ __metadata: version: 0.0.0-use.local resolution: "twenty-zapier@workspace:packages/twenty-zapier" dependencies: + "@sniptt/guards": "npm:^0.2.0" dotenv: "npm:^16.4.5" jest: "npm:29.7.0" + libphonenumber-js: "npm:^1.10.26" rimraf: "npm:^3.0.2" zapier-platform-cli: "npm:^15.4.1" zapier-platform-core: "npm:15.5.1" + zod: "npm:3.23.8" languageName: unknown linkType: soft