From bb91917ff8ee00c3cf0ebaa925e32ddaa65f9312 Mon Sep 17 00:00:00 2001 From: martmull Date: Tue, 16 Jan 2024 15:31:09 +0100 Subject: [PATCH] Improve webhook (#3459) * Add trigger record * Merge triggers * Merge creates * Fix libraries * Fix create merged key * Rename file * Remove list Record Ids * Revert "Rename file" This reverts commit 2e72e05793ced4553eec8d9f890d31beae594c85. * Revert "Revert "Rename file"" This reverts commit e2d93fa02716093df6d4d6029af9cc324c06f06b. * Revert "Remove list Record Ids" This reverts commit 6653fb6ccd4307e3958b70923505034d92cf43bb. * Remove namePlural field * Use name singular for webhooks * Send webhook metadata * Extract resource from zapier webhook * Fix package.json * Fix package.json * Update payload * Fix package.json * Update payload * Update payload * Rename file * Use wildcard in webhook events * Fix nameSingular * Code review returns * Code review returns --- package.json | 4 +- .../jobs/call-webhook-jobs.job.ts | 41 +-- .../jobs/call-webhook.job.ts | 13 +- .../workspace-query-runner.service.ts | 2 +- packages/twenty-zapier/package.json | 9 + .../src/creates/create_record.ts | 49 ---- .../twenty-zapier/src/creates/crud_record.ts | 108 ++++++++ .../src/creates/delete_record.ts | 56 ----- .../src/creates/update_record.ts | 56 ----- packages/twenty-zapier/src/index.ts | 26 +- .../src/test/creates/create_record.test.ts | 70 ------ .../src/test/creates/crud_record.test.ts | 158 ++++++++++++ .../src/test/creates/delete_record.test.ts | 49 ---- .../src/test/creates/update_record.test.ts | 50 ---- .../triggers/find_object_names_plural.test.ts | 19 -- .../src/test/triggers/list_record_ids.test.ts | 2 +- .../src/test/triggers/trigger_record.test.ts | 235 ++++++++++++++++++ .../triggers/trigger_record_created.test.ts | 93 ------- .../triggers/trigger_record_deleted.test.ts | 95 ------- .../triggers/trigger_record_updated.test.ts | 93 ------- .../src/triggers/find_object_names_plural.ts | 27 -- .../triggers/find_object_names_singular.ts | 3 +- .../src/triggers/list_record_ids.ts | 19 +- ...er_record_updated.ts => trigger_record.ts} | 30 ++- .../src/triggers/trigger_record_created.ts | 51 ---- .../src/triggers/trigger_record_deleted.ts | 45 ---- .../twenty-zapier/src/utils/data.types.ts | 2 + packages/twenty-zapier/src/utils/getBundle.ts | 4 +- .../src/utils/handleQueryParams.ts | 6 +- packages/twenty-zapier/src/utils/requestDb.ts | 9 +- .../src/utils/triggers/triggers.utils.ts | 43 +++- yarn.lock | 5 +- 32 files changed, 637 insertions(+), 835 deletions(-) delete mode 100644 packages/twenty-zapier/src/creates/create_record.ts create mode 100644 packages/twenty-zapier/src/creates/crud_record.ts delete mode 100644 packages/twenty-zapier/src/creates/delete_record.ts delete mode 100644 packages/twenty-zapier/src/creates/update_record.ts delete mode 100644 packages/twenty-zapier/src/test/creates/create_record.test.ts create mode 100644 packages/twenty-zapier/src/test/creates/crud_record.test.ts delete mode 100644 packages/twenty-zapier/src/test/creates/delete_record.test.ts delete mode 100644 packages/twenty-zapier/src/test/creates/update_record.test.ts delete mode 100644 packages/twenty-zapier/src/test/triggers/find_object_names_plural.test.ts create mode 100644 packages/twenty-zapier/src/test/triggers/trigger_record.test.ts delete mode 100644 packages/twenty-zapier/src/test/triggers/trigger_record_created.test.ts delete mode 100644 packages/twenty-zapier/src/test/triggers/trigger_record_deleted.test.ts delete mode 100644 packages/twenty-zapier/src/test/triggers/trigger_record_updated.test.ts delete mode 100644 packages/twenty-zapier/src/triggers/find_object_names_plural.ts rename packages/twenty-zapier/src/triggers/{trigger_record_updated.ts => trigger_record.ts} (51%) delete mode 100644 packages/twenty-zapier/src/triggers/trigger_record_created.ts delete mode 100644 packages/twenty-zapier/src/triggers/trigger_record_deleted.ts create mode 100644 packages/twenty-zapier/src/utils/data.types.ts diff --git a/package.json b/package.json index d77cb5bc92..2958760404 100644 --- a/package.json +++ b/package.json @@ -152,7 +152,6 @@ "uuid": "^9.0.0", "vite-tsconfig-paths": "^4.2.1", "xlsx-ugnis": "^0.19.3", - "zapier-platform-core": "15.5.1", "zod": "^3.22.2" }, "devDependencies": { @@ -259,8 +258,7 @@ "typescript": "^5.3.3", "vite": "^5.0.0", "vite-plugin-checker": "^0.6.2", - "vite-plugin-svgr": "^4.2.0", - "zapier-platform-cli": "^15.4.1" + "vite-plugin-svgr": "^4.2.0" }, "engines": { "node": "^18.17.1", diff --git a/packages/twenty-server/src/workspace/workspace-query-runner/jobs/call-webhook-jobs.job.ts b/packages/twenty-server/src/workspace/workspace-query-runner/jobs/call-webhook-jobs.job.ts index c062e04296..844e073c3d 100644 --- a/packages/twenty-server/src/workspace/workspace-query-runner/jobs/call-webhook-jobs.job.ts +++ b/packages/twenty-server/src/workspace/workspace-query-runner/jobs/call-webhook-jobs.job.ts @@ -22,7 +22,7 @@ export enum CallWebhookJobsJobOperation { export type CallWebhookJobsJobData = { workspaceId: string; objectMetadataItem: ObjectMetadataInterface; - recordData: any; + record: any; operation: CallWebhookJobsJobOperation; }; @@ -41,11 +41,6 @@ export class CallWebhookJobsJob ) {} async handle(data: CallWebhookJobsJobData): Promise { - const objectMetadataItem = - await this.objectMetadataService.findOneOrFailWithinWorkspace( - data.workspaceId, - { where: { nameSingular: data.objectMetadataItem.nameSingular } }, - ); const dataSourceMetadata = await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( data.workspaceId, @@ -54,27 +49,45 @@ export class CallWebhookJobsJob await this.workspaceDataSourceService.connectToWorkspaceDataSource( data.workspaceId, ); - const operationName = `${data.operation}.${objectMetadataItem.namePlural}`; + const nameSingular = data.objectMetadataItem.nameSingular; + const operation = data.operation; + const eventType = `${operation}.${nameSingular}`; const webhooks: { id: string; targetUrl: string }[] = await workspaceDataSource?.query( - `SELECT * FROM ${dataSourceMetadata.schema}."webhook" WHERE operation='${operationName}'`, + ` + SELECT * FROM ${dataSourceMetadata.schema}."webhook" + WHERE operation LIKE '%${eventType}%' + OR operation LIKE '%*.${nameSingular}%' + OR operation LIKE '%${operation}.*%' + OR operation LIKE '%*.*%' + `, ); webhooks.forEach((webhook) => { this.messageQueueService.add( CallWebhookJob.name, { - recordData: data.recordData, targetUrl: webhook.targetUrl, + eventType, + objectMetadata: { + id: data.objectMetadataItem.id, + nameSingular: data.objectMetadataItem.nameSingular, + }, + workspaceId: data.workspaceId, + webhookId: webhook.id, + eventDate: new Date(), + record: data.record, }, { retryLimit: 3 }, ); }); - this.logger.log( - `CallWebhookJobsJob on operation '${operationName}' called on webhooks ids [\n"${webhooks - .map((webhook) => webhook.id) - .join('",\n"')}"\n]`, - ); + if (webhooks.length) { + this.logger.log( + `CallWebhookJobsJob on eventType '${eventType}' called on webhooks ids [\n"${webhooks + .map((webhook) => webhook.id) + .join('",\n"')}"\n]`, + ); + } } } diff --git a/packages/twenty-server/src/workspace/workspace-query-runner/jobs/call-webhook.job.ts b/packages/twenty-server/src/workspace/workspace-query-runner/jobs/call-webhook.job.ts index a4a837ee0a..597d515f64 100644 --- a/packages/twenty-server/src/workspace/workspace-query-runner/jobs/call-webhook.job.ts +++ b/packages/twenty-server/src/workspace/workspace-query-runner/jobs/call-webhook.job.ts @@ -5,7 +5,12 @@ import { MessageQueueJob } from 'src/integrations/message-queue/interfaces/messa export type CallWebhookJobData = { targetUrl: string; - recordData: any; + eventType: string; + objectMetadata: { id: string; nameSingular: string }; + workspaceId: string; + webhookId: string; + eventDate: Date; + record: any; }; @Injectable() @@ -16,11 +21,9 @@ export class CallWebhookJob implements MessageQueueJob { async handle(data: CallWebhookJobData): Promise { try { - await this.httpService.axiosRef.post(data.targetUrl, data.recordData); + await this.httpService.axiosRef.post(data.targetUrl, data); this.logger.log( - `CallWebhookJob successfully called on targetUrl '${ - data.targetUrl - }' with data: ${JSON.stringify(data.recordData)}`, + `CallWebhookJob successfully called on targetUrl '${data.targetUrl}'`, ); } catch (err) { throw new Error( diff --git a/packages/twenty-server/src/workspace/workspace-query-runner/workspace-query-runner.service.ts b/packages/twenty-server/src/workspace/workspace-query-runner/workspace-query-runner.service.ts index 0789ef77b1..3e455a39aa 100644 --- a/packages/twenty-server/src/workspace/workspace-query-runner/workspace-query-runner.service.ts +++ b/packages/twenty-server/src/workspace/workspace-query-runner/workspace-query-runner.service.ts @@ -388,7 +388,7 @@ export class WorkspaceQueryRunnerService { this.messageQueueService.add( CallWebhookJobsJob.name, { - recordData: jobData, + record: jobData, workspaceId: options.workspaceId, operation, objectMetadataItem: options.objectMetadataItem, diff --git a/packages/twenty-zapier/package.json b/packages/twenty-zapier/package.json index 255dd2e226..c34b0584b6 100644 --- a/packages/twenty-zapier/package.json +++ b/packages/twenty-zapier/package.json @@ -22,5 +22,14 @@ "private": true, "zapier": { "convertedByCLIVersion": "15.4.1" + }, + "dependencies": { + "zapier-platform-core": "15.5.1" + }, + "devDependencies": { + "zapier-platform-cli": "^15.4.1" + }, + "installConfig": { + "hoistingLimits": "dependencies" } } diff --git a/packages/twenty-zapier/src/creates/create_record.ts b/packages/twenty-zapier/src/creates/create_record.ts deleted file mode 100644 index 01d6333aa6..0000000000 --- a/packages/twenty-zapier/src/creates/create_record.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Bundle, ZObject } from 'zapier-platform-core'; - -import { findObjectNamesSingularKey } from '../triggers/find_object_names_singular'; -import { capitalize } from '../utils/capitalize'; -import { recordInputFields } from '../utils/creates/creates.utils'; -import handleQueryParams from '../utils/handleQueryParams'; -import requestDb from '../utils/requestDb'; - -const perform = async (z: ZObject, bundle: Bundle) => { - const data = bundle.inputData; - const nameSingular = data.nameSingular; - delete data.nameSingular; - const query = ` - mutation create${capitalize(nameSingular)} { - create${capitalize(nameSingular)}( - data:{${handleQueryParams(data)}} - ) - {id} - }`; - return await requestDb(z, bundle, query); -}; - -export const createRecordKey = 'create_record'; - -export default { - display: { - description: 'Create a Record in Twenty.', - hidden: false, - label: 'Create Record', - }, - key: createRecordKey, - noun: 'Record', - operation: { - inputFields: [ - { - key: 'nameSingular', - required: true, - label: 'Record Name', - dynamic: `${findObjectNamesSingularKey}.nameSingular`, - altersDynamicFields: true, - }, - recordInputFields, - ], - sample: { - id: '179ed459-79cf-41d9-ab85-96397fa8e936', - }, - perform, - }, -}; diff --git a/packages/twenty-zapier/src/creates/crud_record.ts b/packages/twenty-zapier/src/creates/crud_record.ts new file mode 100644 index 0000000000..6a18befc9e --- /dev/null +++ b/packages/twenty-zapier/src/creates/crud_record.ts @@ -0,0 +1,108 @@ +import { Bundle, ZObject } from 'zapier-platform-core'; + +import { findObjectNamesSingularKey } from '../triggers/find_object_names_singular'; +import { listRecordIdsKey } from '../triggers/list_record_ids'; +import { capitalize } from '../utils/capitalize'; +import { recordInputFields } from '../utils/creates/creates.utils'; +import { InputData } from '../utils/data.types'; +import handleQueryParams from '../utils/handleQueryParams'; +import requestDb from '../utils/requestDb'; +import { Operation } from '../utils/triggers/triggers.utils'; + +const computeFields = async (z: ZObject, bundle: Bundle) => { + const operation = bundle.inputData.crudZapierOperation; + switch (operation) { + case Operation.delete: + return [ + { + key: 'id', + label: 'Id', + type: 'string', + dynamic: `${listRecordIdsKey}.id`, + required: true, + }, + ]; + case Operation.update: + return recordInputFields(z, bundle, true); + case Operation.create: + return recordInputFields(z, bundle, false); + default: + return []; + } +}; + +const computeQueryParameters = ( + operation: Operation, + data: InputData, +): string => { + switch (operation) { + case Operation.create: + return `data:{${handleQueryParams(data)}}`; + case Operation.update: + return ` + data:{${handleQueryParams(data)}}, + id: "${data.id}" + `; + case Operation.delete: + return ` + id: "${data.id}" + `; + default: + return ''; + } +}; + +const perform = async (z: ZObject, bundle: Bundle) => { + const data = bundle.inputData; + const operation = data.crudZapierOperation; + const nameSingular = data.nameSingular; + delete data.nameSingular; + delete data.crudZapierOperation; + const query = ` + mutation ${operation}${capitalize(nameSingular)} { + ${operation}${capitalize(nameSingular)}( + ${computeQueryParameters(operation, data)} + ) + {id} + }`; + return await requestDb(z, bundle, query); +}; + +export const crudRecordKey = 'crud_record'; + +export default { + display: { + description: 'Create, Update or Delete a Record in Twenty.', + hidden: false, + label: 'Create, Update or Delete Record', + }, + key: crudRecordKey, + noun: 'Record', + operation: { + inputFields: [ + { + key: 'nameSingular', + required: true, + label: 'Record Name', + dynamic: `${findObjectNamesSingularKey}.nameSingular`, + altersDynamicFields: true, + }, + { + key: 'crudZapierOperation', + required: true, + label: 'Operation', + choices: { + [Operation.create]: Operation.create, + [Operation.update]: Operation.update, + [Operation.delete]: Operation.delete, + }, + altersDynamicFields: true, + }, + computeFields, + ], + sample: { + id: '179ed459-79cf-41d9-ab85-96397fa8e936', + }, + perform, + }, +}; diff --git a/packages/twenty-zapier/src/creates/delete_record.ts b/packages/twenty-zapier/src/creates/delete_record.ts deleted file mode 100644 index 274d129600..0000000000 --- a/packages/twenty-zapier/src/creates/delete_record.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Bundle, ZObject } from 'zapier-platform-core'; - -import { findObjectNamesPluralKey } from '../triggers/find_object_names_plural'; -import { listRecordIdsKey } from '../triggers/list_record_ids'; -import { capitalize } from '../utils/capitalize'; -import requestDb from '../utils/requestDb'; - -export const deleteRecordKey = 'delete_record'; - -const perform = async (z: ZObject, bundle: Bundle) => { - const data = bundle.inputData; - const nameSingular = data.nameSingular; - const id = data.id; - delete data.nameSingular; - delete data.id; - const query = ` - mutation delete${capitalize(nameSingular)} { - delete${capitalize(nameSingular)}( - id: "${id}" - ) - {id} - }`; - return await requestDb(z, bundle, query); -}; - -export default { - display: { - description: 'Delete a Record in Twenty.', - hidden: false, - label: 'Delete Record', - }, - key: deleteRecordKey, - noun: 'Record', - operation: { - inputFields: [ - { - key: 'namePlural', - label: 'Record Name', - dynamic: `${findObjectNamesPluralKey}.namePlural`, - required: true, - altersDynamicFields: true, - }, - { - key: 'id', - label: 'Id', - type: 'string', - dynamic: `${listRecordIdsKey}.id`, - required: true, - }, - ], - sample: { - id: '179ed459-79cf-41d9-ab85-96397fa8e936', - }, - perform, - }, -}; diff --git a/packages/twenty-zapier/src/creates/update_record.ts b/packages/twenty-zapier/src/creates/update_record.ts deleted file mode 100644 index 7e80aa5cfd..0000000000 --- a/packages/twenty-zapier/src/creates/update_record.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Bundle, ZObject } from 'zapier-platform-core'; - -import { findObjectNamesSingularKey } from '../triggers/find_object_names_singular'; -import { capitalize } from '../utils/capitalize'; -import { recordInputFields } from '../utils/creates/creates.utils'; -import handleQueryParams from '../utils/handleQueryParams'; -import requestDb from '../utils/requestDb'; - -export const updateRecordKey = 'update_record'; - -const perform = async (z: ZObject, bundle: Bundle) => { - const data = bundle.inputData; - const nameSingular = data.nameSingular; - const id = data.id; - delete data.nameSingular; - delete data.id; - const query = ` - mutation update${capitalize(nameSingular)} { - update${capitalize(nameSingular)}( - data:{${handleQueryParams(data)}}, - id: "${id}" - ) - {id} - }`; - return await requestDb(z, bundle, query); -}; - -const updateRecordInputFields = async (z: ZObject, bundle: Bundle) => { - return recordInputFields(z, bundle, true); -}; - -export default { - display: { - description: 'Update a Record in Twenty.', - hidden: false, - label: 'Update Record', - }, - key: updateRecordKey, - noun: 'Record', - operation: { - inputFields: [ - { - key: 'nameSingular', - required: true, - label: 'Record Name', - dynamic: `${findObjectNamesSingularKey}.nameSingular`, - altersDynamicFields: true, - }, - updateRecordInputFields, - ], - sample: { - id: '179ed459-79cf-41d9-ab85-96397fa8e936', - }, - perform, - }, -}; diff --git a/packages/twenty-zapier/src/index.ts b/packages/twenty-zapier/src/index.ts index c11d765354..fd458353ce 100644 --- a/packages/twenty-zapier/src/index.ts +++ b/packages/twenty-zapier/src/index.ts @@ -4,25 +4,12 @@ import 'dotenv/config'; const { version } = require('../package.json'); -import createRecord, { createRecordKey } from './creates/create_record'; -import deleteRecord, { deleteRecordKey } from './creates/delete_record'; -import updateRecord, { updateRecordKey } from './creates/update_record'; -import findObjectNamesPlural, { - findObjectNamesPluralKey, -} from './triggers/find_object_names_plural'; +import crudRecord, { crudRecordKey } from './creates/crud_record'; import findObjectNamesSingular, { findObjectNamesSingularKey, } from './triggers/find_object_names_singular'; import listRecordIds, { listRecordIdsKey } from './triggers/list_record_ids'; -import triggerRecordCreated, { - triggerRecordCreatedKey, -} from './triggers/trigger_record_created'; -import triggerRecordDeleted, { - triggerRecordDeletedKey, -} from './triggers/trigger_record_deleted'; -import triggerRecordUpdated, { - triggerRecordUpdatedKey, -} from './triggers/trigger_record_updated'; +import triggerRecord, { triggerRecordKey } from './triggers/trigger_record'; import authentication from './authentication'; export default { @@ -31,15 +18,10 @@ export default { authentication: authentication, triggers: { [findObjectNamesSingularKey]: findObjectNamesSingular, - [findObjectNamesPluralKey]: findObjectNamesPlural, [listRecordIdsKey]: listRecordIds, - [triggerRecordCreatedKey]: triggerRecordCreated, - [triggerRecordUpdatedKey]: triggerRecordUpdated, - [triggerRecordDeletedKey]: triggerRecordDeleted, + [triggerRecordKey]: triggerRecord, }, creates: { - [createRecordKey]: createRecord, - [updateRecordKey]: updateRecord, - [deleteRecordKey]: deleteRecord, + [crudRecordKey]: crudRecord, }, }; diff --git a/packages/twenty-zapier/src/test/creates/create_record.test.ts b/packages/twenty-zapier/src/test/creates/create_record.test.ts deleted file mode 100644 index 1a922281c9..0000000000 --- a/packages/twenty-zapier/src/test/creates/create_record.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Bundle, createAppTester, tools, ZObject } from 'zapier-platform-core'; - -import { createRecordKey } from '../../creates/create_record'; -import App from '../../index'; -import getBundle from '../../utils/getBundle'; -import requestDb from '../../utils/requestDb'; -const appTester = createAppTester(App); -tools.env.inject(); - -describe('creates.create_company', () => { - test('should run to create a Company Record', async () => { - const bundle = getBundle({ - nameSingular: 'Company', - name: 'Company Name', - address: 'Company Address', - domainName: 'Company Domain Name', - linkedinLink: { url: '/linkedin_url', label: 'Test linkedinUrl' }, - xLink: { url: '/x_url', label: 'Test xUrl' }, - annualRecurringRevenue: { - amountMicros: 100000000000, - currencyCode: 'USD', - }, - idealCustomerProfile: true, - employees: 25, - }); - const result = await appTester( - App.creates[createRecordKey].operation.perform, - bundle, - ); - expect(result).toBeDefined(); - expect(result.data?.createCompany?.id).toBeDefined(); - const checkDbResult = await appTester( - (z: ZObject, bundle: Bundle) => - requestDb( - z, - bundle, - `query findCompany {company(filter: {id: {eq: "${result.data.createCompany.id}"}}){id annualRecurringRevenue{amountMicros currencyCode}}}`, - ), - bundle, - ); - expect( - checkDbResult.data.company.annualRecurringRevenue.amountMicros, - ).toEqual(100000000000); - }); - test('should run to create a Person Record', async () => { - const bundle = getBundle({ - nameSingular: 'Person', - name: { firstName: 'John', lastName: 'Doe' }, - email: 'johndoe@gmail.com', - phone: '+33610203040', - city: 'Paris', - }); - const result = await appTester( - App.creates[createRecordKey].operation.perform, - bundle, - ); - expect(result).toBeDefined(); - expect(result.data?.createPerson?.id).toBeDefined(); - const checkDbResult = await appTester( - (z: ZObject, bundle: Bundle) => - requestDb( - z, - bundle, - `query findPerson {person(filter: {id: {eq: "${result.data.createPerson.id}"}}){phone}}`, - ), - bundle, - ); - expect(checkDbResult.data.person.phone).toEqual('+33610203040'); - }); -}); diff --git a/packages/twenty-zapier/src/test/creates/crud_record.test.ts b/packages/twenty-zapier/src/test/creates/crud_record.test.ts new file mode 100644 index 0000000000..f1c177233f --- /dev/null +++ b/packages/twenty-zapier/src/test/creates/crud_record.test.ts @@ -0,0 +1,158 @@ +import { Bundle, createAppTester, tools, ZObject } from 'zapier-platform-core'; + +import { crudRecordKey } from '../../creates/crud_record'; +import App from '../../index'; +import getBundle from '../../utils/getBundle'; +import requestDb from '../../utils/requestDb'; +import { Operation } from '../../utils/triggers/triggers.utils'; +const appTester = createAppTester(App); +tools.env.inject(); + +describe('creates.create_company', () => { + test('should run to create a Company Record', async () => { + const bundle = getBundle({ + nameSingular: 'Company', + crudZapierOperation: Operation.create, + name: 'Company Name', + address: 'Company Address', + domainName: 'Company Domain Name', + linkedinLink: { url: '/linkedin_url', label: 'Test linkedinUrl' }, + xLink: { url: '/x_url', label: 'Test xUrl' }, + annualRecurringRevenue: { + amountMicros: 100000000000, + currencyCode: 'USD', + }, + idealCustomerProfile: true, + employees: 25, + }); + const result = await appTester( + App.creates[crudRecordKey].operation.perform, + bundle, + ); + expect(result).toBeDefined(); + expect(result.data?.createCompany?.id).toBeDefined(); + const checkDbResult = await appTester( + (z: ZObject, bundle: Bundle) => + requestDb( + z, + bundle, + `query findCompany {company(filter: {id: {eq: "${result.data.createCompany.id}"}}){id annualRecurringRevenue{amountMicros currencyCode}}}`, + ), + bundle, + ); + expect( + checkDbResult.data.company.annualRecurringRevenue.amountMicros, + ).toEqual(100000000000); + }); + test('should run to create a Person Record', async () => { + const bundle = getBundle({ + nameSingular: 'Person', + crudZapierOperation: Operation.create, + name: { firstName: 'John', lastName: 'Doe' }, + email: 'johndoe@gmail.com', + phone: '+33610203040', + city: 'Paris', + }); + const result = await appTester( + App.creates[crudRecordKey].operation.perform, + bundle, + ); + expect(result).toBeDefined(); + expect(result.data?.createPerson?.id).toBeDefined(); + const checkDbResult = await appTester( + (z: ZObject, bundle: Bundle) => + requestDb( + z, + bundle, + `query findPerson {person(filter: {id: {eq: "${result.data.createPerson.id}"}}){phone}}`, + ), + bundle, + ); + expect(checkDbResult.data.person.phone).toEqual('+33610203040'); + }); +}); + +describe('creates.update_company', () => { + test('should run to update a Company record', async () => { + const createBundle = getBundle({ + nameSingular: 'Company', + crudZapierOperation: Operation.create, + name: 'Company Name', + employees: 25, + }); + + const createResult = await appTester( + App.creates[crudRecordKey].operation.perform, + createBundle, + ); + + const companyId = createResult.data?.createCompany?.id; + + const updateBundle = getBundle({ + nameSingular: 'Company', + crudZapierOperation: Operation.update, + id: companyId, + name: 'Updated Company Name', + }); + + const updateResult = await appTester( + App.creates[crudRecordKey].operation.perform, + updateBundle, + ); + + expect(updateResult).toBeDefined(); + expect(updateResult.data?.updateCompany?.id).toBeDefined(); + const checkDbResult = await appTester( + (z: ZObject, bundle: Bundle) => + requestDb( + z, + bundle, + `query findCompany {company(filter: {id: {eq: "${companyId}"}}){id name}}`, + ), + updateBundle, + ); + expect(checkDbResult.data.company.name).toEqual('Updated Company Name'); + }); +}); + +describe('creates.delete_company', () => { + test('should run to delete a Company record', async () => { + const createBundle = getBundle({ + nameSingular: 'Company', + crudZapierOperation: Operation.create, + name: 'Delete Company Name', + employees: 25, + }); + + const createResult = await appTester( + App.creates[crudRecordKey].operation.perform, + createBundle, + ); + + const companyId = createResult.data?.createCompany?.id; + + const deleteBundle = getBundle({ + nameSingular: 'Company', + crudZapierOperation: Operation.delete, + id: companyId, + }); + + const deleteResult = await appTester( + App.creates[crudRecordKey].operation.perform, + deleteBundle, + ); + + expect(deleteResult).toBeDefined(); + expect(deleteResult.data?.deleteCompany?.id).toBeDefined(); + const checkDbResult = await appTester( + (z: ZObject, bundle: Bundle) => + requestDb( + z, + bundle, + `query findCompanies {companies(filter: {id: {eq: "${companyId}"}}){edges{node{id}}}}`, + ), + deleteBundle, + ); + expect(checkDbResult.data.companies.edges.length).toEqual(0); + }); +}); diff --git a/packages/twenty-zapier/src/test/creates/delete_record.test.ts b/packages/twenty-zapier/src/test/creates/delete_record.test.ts deleted file mode 100644 index 902e7643e5..0000000000 --- a/packages/twenty-zapier/src/test/creates/delete_record.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Bundle, createAppTester, tools, ZObject } from 'zapier-platform-core'; - -import { createRecordKey } from '../../creates/create_record'; -import { deleteRecordKey } from '../../creates/delete_record'; -import App from '../../index'; -import getBundle from '../../utils/getBundle'; -import requestDb from '../../utils/requestDb'; -const appTester = createAppTester(App); - -tools.env.inject(); -describe('creates.delete_company', () => { - test('should run to delete a Company record', async () => { - const createBundle = getBundle({ - nameSingular: 'Company', - name: 'Delete Company Name', - employees: 25, - }); - - const createResult = await appTester( - App.creates[createRecordKey].operation.perform, - createBundle, - ); - - const companyId = createResult.data?.createCompany?.id; - - const deleteBundle = getBundle({ - nameSingular: 'Company', - id: companyId, - }); - - const deleteResult = await appTester( - App.creates[deleteRecordKey].operation.perform, - deleteBundle, - ); - - expect(deleteResult).toBeDefined(); - expect(deleteResult.data?.deleteCompany?.id).toBeDefined(); - const checkDbResult = await appTester( - (z: ZObject, bundle: Bundle) => - requestDb( - z, - bundle, - `query findCompanies {companies(filter: {id: {eq: "${companyId}"}}){edges{node{id}}}}`, - ), - deleteBundle, - ); - expect(checkDbResult.data.companies.edges.length).toEqual(0); - }); -}); diff --git a/packages/twenty-zapier/src/test/creates/update_record.test.ts b/packages/twenty-zapier/src/test/creates/update_record.test.ts deleted file mode 100644 index fafab1b06b..0000000000 --- a/packages/twenty-zapier/src/test/creates/update_record.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Bundle, createAppTester, tools, ZObject } from 'zapier-platform-core'; - -import { createRecordKey } from '../../creates/create_record'; -import { updateRecordKey } from '../../creates/update_record'; -import App from '../../index'; -import getBundle from '../../utils/getBundle'; -import requestDb from '../../utils/requestDb'; -const appTester = createAppTester(App); - -tools.env.inject(); -describe('creates.update_company', () => { - test('should run to update a Company record', async () => { - const createBundle = getBundle({ - nameSingular: 'Company', - name: 'Company Name', - employees: 25, - }); - - const createResult = await appTester( - App.creates[createRecordKey].operation.perform, - createBundle, - ); - - const companyId = createResult.data?.createCompany?.id; - - const updateBundle = getBundle({ - nameSingular: 'Company', - id: companyId, - name: 'Updated Company Name', - }); - - const updateResult = await appTester( - App.creates[updateRecordKey].operation.perform, - updateBundle, - ); - - expect(updateResult).toBeDefined(); - expect(updateResult.data?.updateCompany?.id).toBeDefined(); - const checkDbResult = await appTester( - (z: ZObject, bundle: Bundle) => - requestDb( - z, - bundle, - `query findCompany {company(filter: {id: {eq: "${companyId}"}}){id name}}`, - ), - updateBundle, - ); - expect(checkDbResult.data.company.name).toEqual('Updated Company Name'); - }); -}); diff --git a/packages/twenty-zapier/src/test/triggers/find_object_names_plural.test.ts b/packages/twenty-zapier/src/test/triggers/find_object_names_plural.test.ts deleted file mode 100644 index f9837a50bf..0000000000 --- a/packages/twenty-zapier/src/test/triggers/find_object_names_plural.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { createAppTester, tools } from 'zapier-platform-core'; -import getBundle from '../../utils/getBundle'; -import App from '../../index'; -import { findObjectNamesPluralKey } from '../../triggers/find_object_names_plural'; -tools.env.inject(); - -const appTester = createAppTester(App); -describe('triggers.find_object_names_plural', () => { - test('should run', async () => { - const bundle = getBundle({}); - const result = await appTester( - App.triggers[findObjectNamesPluralKey].operation.perform, - bundle, - ); - expect(result).toBeDefined(); - expect(result.length).toBeGreaterThan(1); - expect(result[0].namePlural).toBeDefined(); - }); -}); diff --git a/packages/twenty-zapier/src/test/triggers/list_record_ids.test.ts b/packages/twenty-zapier/src/test/triggers/list_record_ids.test.ts index d9e4f1b3dd..a0ca7e084a 100644 --- a/packages/twenty-zapier/src/test/triggers/list_record_ids.test.ts +++ b/packages/twenty-zapier/src/test/triggers/list_record_ids.test.ts @@ -8,7 +8,7 @@ tools.env.inject(); const appTester = createAppTester(App); describe('triggers.list_record_ids', () => { test('should run', async () => { - const bundle = getBundle({ namePlural: 'companies' }); + const bundle = getBundle({ nameSingular: 'company' }); const result = await appTester( App.triggers[listRecordIdsKey].operation.perform, bundle, diff --git a/packages/twenty-zapier/src/test/triggers/trigger_record.test.ts b/packages/twenty-zapier/src/test/triggers/trigger_record.test.ts new file mode 100644 index 0000000000..962fe91111 --- /dev/null +++ b/packages/twenty-zapier/src/test/triggers/trigger_record.test.ts @@ -0,0 +1,235 @@ +import { Bundle, createAppTester, ZObject } from 'zapier-platform-core'; + +import App from '../../index'; +import { triggerRecordKey } from '../../triggers/trigger_record'; +import getBundle from '../../utils/getBundle'; +import requestDb from '../../utils/requestDb'; +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 = 'create'; + 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 operation}}}}`, + ), + bundle, + ); + expect(checkDbResult.data.webhooks.edges[0].node.operation).toEqual( + 'create.company', + ); + }); + test('should succeed to unsubscribe', async () => { + const bundle = getBundle({}); + bundle.inputData.nameSingular = 'company'; + bundle.inputData.operation = 'create'; + 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 operation}}}}`, + ), + bundle, + ); + expect(checkDbResult.data.webhooks.edges.length).toEqual(0); + }); + test('should load company from webhook', async () => { + const bundle = { + cleanedRequest: { + record: { + id: 'd6ccb1d1-a90b-4822-a992-a0dd946592c9', + name: '', + domainName: '', + createdAt: '2023-10-19 10:10:12.490', + address: '', + employees: null, + linkedinUrl: null, + xUrl: null, + annualRecurringRevenue: null, + idealCustomerProfile: false, + }, + }, + }; + const results = await appTester( + App.triggers[triggerRecordKey].operation.perform, + bundle, + ); + expect(results.length).toEqual(1); + const company = results[0]; + expect(company.id).toEqual('d6ccb1d1-a90b-4822-a992-a0dd946592c9'); + }); + it('should load companies from list', async () => { + const bundle = getBundle({}); + bundle.inputData.nameSingular = 'company'; + bundle.inputData.operation = 'create'; + const results = await appTester( + App.triggers[triggerRecordKey].operation.performList, + bundle, + ); + expect(results.length).toBeGreaterThan(1); + const firstCompany = results[0]; + expect(firstCompany).toBeDefined(); + }); +}); + +describe('triggers.trigger_record.update', () => { + test('should succeed to subscribe', async () => { + const bundle = getBundle({}); + bundle.inputData.nameSingular = 'company'; + bundle.inputData.operation = 'update'; + 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 operation}}}}`, + ), + bundle, + ); + expect(checkDbResult.data.webhooks.edges[0].node.operation).toEqual( + 'update.company', + ); + }); + test('should succeed to unsubscribe', async () => { + const bundle = getBundle({}); + bundle.inputData.nameSingular = 'company'; + bundle.inputData.operation = 'update'; + 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 operation}}}}`, + ), + bundle, + ); + expect(checkDbResult.data.webhooks.edges.length).toEqual(0); + }); + it('should load companies from list', async () => { + const bundle = getBundle({}); + bundle.inputData.nameSingular = 'company'; + bundle.inputData.operation = 'update'; + const results = await appTester( + App.triggers[triggerRecordKey].operation.performList, + bundle, + ); + expect(results.length).toBeGreaterThan(1); + const firstCompany = results[0]; + expect(firstCompany).toBeDefined(); + }); +}); + +describe('triggers.trigger_record.delete', () => { + test('should succeed to subscribe', async () => { + const bundle = getBundle({}); + bundle.inputData.nameSingular = 'company'; + bundle.inputData.operation = 'delete'; + 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 operation}}}}`, + ), + bundle, + ); + expect(checkDbResult.data.webhooks.edges[0].node.operation).toEqual( + 'delete.company', + ); + }); + test('should succeed to unsubscribe', async () => { + const bundle = getBundle({}); + bundle.inputData.nameSingular = 'company'; + bundle.inputData.operation = 'delete'; + 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 operation}}}}`, + ), + bundle, + ); + expect(checkDbResult.data.webhooks.edges.length).toEqual(0); + }); + it('should load companies from list', async () => { + const bundle = getBundle({}); + bundle.inputData.nameSingular = 'company'; + bundle.inputData.operation = 'delete'; + const results = await appTester( + App.triggers[triggerRecordKey].operation.performList, + bundle, + ); + expect(results.length).toBeGreaterThan(1); + const firstCompany = results[0]; + expect(firstCompany).toBeDefined(); + expect(firstCompany.id).toBeDefined(); + expect(Object.keys(firstCompany).length).toEqual(1); + }); +}); diff --git a/packages/twenty-zapier/src/test/triggers/trigger_record_created.test.ts b/packages/twenty-zapier/src/test/triggers/trigger_record_created.test.ts deleted file mode 100644 index 6400759acc..0000000000 --- a/packages/twenty-zapier/src/test/triggers/trigger_record_created.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { Bundle, createAppTester, ZObject } from 'zapier-platform-core'; -import App from '../../index'; -import getBundle from '../../utils/getBundle'; -import requestDb from '../../utils/requestDb'; -import { triggerRecordCreatedKey } from '../../triggers/trigger_record_created'; -const appTester = createAppTester(App); - -describe('triggers.trigger_record_created', () => { - test('should succeed to subscribe', async () => { - const bundle = getBundle({}); - bundle.inputData.namePlural = 'companies'; - bundle.targetUrl = 'https://test.com'; - const result = await appTester( - App.triggers[triggerRecordCreatedKey].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 operation}}}}`, - ), - bundle, - ); - expect(checkDbResult.data.webhooks.edges[0].node.operation).toEqual( - 'create.companies', - ); - }); - test('should succeed to unsubscribe', async () => { - const bundle = getBundle({}); - bundle.inputData.namePlural = 'companies'; - bundle.targetUrl = 'https://test.com'; - const result = await appTester( - App.triggers[triggerRecordCreatedKey].operation.performSubscribe, - bundle, - ); - const unsubscribeBundle = getBundle({}); - unsubscribeBundle.subscribeData = { id: result.id }; - const unsubscribeResult = await appTester( - App.triggers[triggerRecordCreatedKey].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 operation}}}}`, - ), - bundle, - ); - expect(checkDbResult.data.webhooks.edges.length).toEqual(0); - }); - test('should load company from webhook', async () => { - const bundle = { - cleanedRequest: { - id: 'd6ccb1d1-a90b-4822-a992-a0dd946592c9', - name: '', - domainName: '', - createdAt: '2023-10-19 10:10:12.490', - address: '', - employees: null, - linkedinUrl: null, - xUrl: null, - annualRecurringRevenue: null, - idealCustomerProfile: false, - }, - }; - const results = await appTester( - App.triggers[triggerRecordCreatedKey].operation.perform, - bundle, - ); - expect(results.length).toEqual(1); - const company = results[0]; - expect(company.id).toEqual('d6ccb1d1-a90b-4822-a992-a0dd946592c9'); - }); - it('should load companies from list', async () => { - const bundle = getBundle({}); - bundle.inputData.namePlural = 'companies'; - const results = await appTester( - App.triggers[triggerRecordCreatedKey].operation.performList, - bundle, - ); - expect(results.length).toBeGreaterThan(1); - const firstCompany = results[0]; - expect(firstCompany).toBeDefined(); - }); -}); diff --git a/packages/twenty-zapier/src/test/triggers/trigger_record_deleted.test.ts b/packages/twenty-zapier/src/test/triggers/trigger_record_deleted.test.ts deleted file mode 100644 index 1bb3e8fde9..0000000000 --- a/packages/twenty-zapier/src/test/triggers/trigger_record_deleted.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Bundle, createAppTester, ZObject } from 'zapier-platform-core'; -import App from '../../index'; -import getBundle from '../../utils/getBundle'; -import requestDb from '../../utils/requestDb'; -import { triggerRecordDeletedKey } from '../../triggers/trigger_record_deleted'; -const appTester = createAppTester(App); - -describe('triggers.trigger_record_deleted', () => { - test('should succeed to subscribe', async () => { - const bundle = getBundle({}); - bundle.inputData.namePlural = 'companies'; - bundle.targetUrl = 'https://test.com'; - const result = await appTester( - App.triggers[triggerRecordDeletedKey].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 operation}}}}`, - ), - bundle, - ); - expect(checkDbResult.data.webhooks.edges[0].node.operation).toEqual( - 'delete.companies', - ); - }); - test('should succeed to unsubscribe', async () => { - const bundle = getBundle({}); - bundle.inputData.namePlural = 'companies'; - bundle.targetUrl = 'https://test.com'; - const result = await appTester( - App.triggers[triggerRecordDeletedKey].operation.performSubscribe, - bundle, - ); - const unsubscribeBundle = getBundle({}); - unsubscribeBundle.subscribeData = { id: result.id }; - const unsubscribeResult = await appTester( - App.triggers[triggerRecordDeletedKey].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 operation}}}}`, - ), - bundle, - ); - expect(checkDbResult.data.webhooks.edges.length).toEqual(0); - }); - test('should load company from webhook', async () => { - const bundle = { - cleanedRequest: { - id: 'd6ccb1d1-a90b-4822-a992-a0dd946592c9', - name: '', - domainName: '', - createdAt: '2023-10-19 10:10:12.490', - address: '', - employees: null, - linkedinUrl: null, - xUrl: null, - annualRecurringRevenue: null, - idealCustomerProfile: false, - }, - }; - const results = await appTester( - App.triggers[triggerRecordDeletedKey].operation.perform, - bundle, - ); - expect(results.length).toEqual(1); - const company = results[0]; - expect(company.id).toEqual('d6ccb1d1-a90b-4822-a992-a0dd946592c9'); - }); - it('should load companies from list', async () => { - const bundle = getBundle({}); - bundle.inputData.namePlural = 'companies'; - const results = await appTester( - App.triggers[triggerRecordDeletedKey].operation.performList, - bundle, - ); - expect(results.length).toBeGreaterThan(1); - const firstCompany = results[0]; - expect(firstCompany).toBeDefined(); - expect(firstCompany.id).toBeDefined(); - expect(Object.keys(firstCompany).length).toEqual(1); - }); -}); diff --git a/packages/twenty-zapier/src/test/triggers/trigger_record_updated.test.ts b/packages/twenty-zapier/src/test/triggers/trigger_record_updated.test.ts deleted file mode 100644 index fe53153cb1..0000000000 --- a/packages/twenty-zapier/src/test/triggers/trigger_record_updated.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { Bundle, createAppTester, ZObject } from 'zapier-platform-core'; -import App from '../../index'; -import getBundle from '../../utils/getBundle'; -import requestDb from '../../utils/requestDb'; -import { triggerRecordUpdatedKey } from '../../triggers/trigger_record_updated'; -const appTester = createAppTester(App); - -describe('triggers.trigger_record_updated', () => { - test('should succeed to subscribe', async () => { - const bundle = getBundle({}); - bundle.inputData.namePlural = 'companies'; - bundle.targetUrl = 'https://test.com'; - const result = await appTester( - App.triggers[triggerRecordUpdatedKey].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 operation}}}}`, - ), - bundle, - ); - expect(checkDbResult.data.webhooks.edges[0].node.operation).toEqual( - 'update.companies', - ); - }); - test('should succeed to unsubscribe', async () => { - const bundle = getBundle({}); - bundle.inputData.namePlural = 'companies'; - bundle.targetUrl = 'https://test.com'; - const result = await appTester( - App.triggers[triggerRecordUpdatedKey].operation.performSubscribe, - bundle, - ); - const unsubscribeBundle = getBundle({}); - unsubscribeBundle.subscribeData = { id: result.id }; - const unsubscribeResult = await appTester( - App.triggers[triggerRecordUpdatedKey].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 operation}}}}`, - ), - bundle, - ); - expect(checkDbResult.data.webhooks.edges.length).toEqual(0); - }); - test('should load company from webhook', async () => { - const bundle = { - cleanedRequest: { - id: 'd6ccb1d1-a90b-4822-a992-a0dd946592c9', - name: '', - domainName: '', - createdAt: '2023-10-19 10:10:12.490', - address: '', - employees: null, - linkedinUrl: null, - xUrl: null, - annualRecurringRevenue: null, - idealCustomerProfile: false, - }, - }; - const results = await appTester( - App.triggers[triggerRecordUpdatedKey].operation.perform, - bundle, - ); - expect(results.length).toEqual(1); - const company = results[0]; - expect(company.id).toEqual('d6ccb1d1-a90b-4822-a992-a0dd946592c9'); - }); - it('should load companies from list', async () => { - const bundle = getBundle({}); - bundle.inputData.namePlural = 'companies'; - const results = await appTester( - App.triggers[triggerRecordUpdatedKey].operation.performList, - bundle, - ); - expect(results.length).toBeGreaterThan(1); - const firstCompany = results[0]; - expect(firstCompany).toBeDefined(); - }); -}); diff --git a/packages/twenty-zapier/src/triggers/find_object_names_plural.ts b/packages/twenty-zapier/src/triggers/find_object_names_plural.ts deleted file mode 100644 index 5b9614a6f4..0000000000 --- a/packages/twenty-zapier/src/triggers/find_object_names_plural.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Bundle, ZObject } from 'zapier-platform-core'; -import { requestSchema } from '../utils/requestDb'; - -const objectNamesPluralListRequest = async (z: ZObject, bundle: Bundle) => { - const schema = await requestSchema(z, bundle); - const tags: { name: string }[] = schema.tags; - return Object.values(tags) - .filter((tag) => tag.name !== 'General') - .map((tag) => { - return { id: tag.name, namePlural: tag.name }; - }); -}; - -export const findObjectNamesPluralKey = 'find_object_names_plural'; - -export default { - display: { - description: 'Find objects', - label: 'Find objects', - hidden: true, - }, - key: findObjectNamesPluralKey, - noun: 'Object', - operation: { - perform: objectNamesPluralListRequest, - }, -}; diff --git a/packages/twenty-zapier/src/triggers/find_object_names_singular.ts b/packages/twenty-zapier/src/triggers/find_object_names_singular.ts index 23a5617e2f..35d4fcf07e 100644 --- a/packages/twenty-zapier/src/triggers/find_object_names_singular.ts +++ b/packages/twenty-zapier/src/triggers/find_object_names_singular.ts @@ -1,10 +1,11 @@ import { Bundle, ZObject } from 'zapier-platform-core'; + import { requestSchema } from '../utils/requestDb'; const objectListRequest = async (z: ZObject, bundle: Bundle) => { const schema = await requestSchema(z, bundle); return Object.keys(schema.components.schemas).map((schema) => { - return { id: schema, nameSingular: schema }; + return { id: schema, nameSingular: schema.toLowerCase() }; }); }; diff --git a/packages/twenty-zapier/src/triggers/list_record_ids.ts b/packages/twenty-zapier/src/triggers/list_record_ids.ts index 17a120a8c6..8f6345f48a 100644 --- a/packages/twenty-zapier/src/triggers/list_record_ids.ts +++ b/packages/twenty-zapier/src/triggers/list_record_ids.ts @@ -1,24 +1,13 @@ import { Bundle, ZObject } from 'zapier-platform-core'; -import { capitalize } from '../utils/capitalize'; -import requestDb from '../utils/requestDb'; +import { ObjectData } from '../utils/data.types'; +import { listSample } from '../utils/triggers/triggers.utils'; const listRecordIdsRequest = async ( z: ZObject, bundle: Bundle, -): Promise<{ id: string }[]> => { - const data = bundle.inputData; - const namePlural = data.namePlural; - const query = ` - query List${capitalize(namePlural)}Ids { - ${namePlural}{edges{node{id}}} - }`; - const result = await requestDb(z, bundle, query); - return result.data[namePlural]['edges'].map((edge: any) => { - return { - id: edge.node.id, - }; - }); +): Promise => { + return listSample(z, bundle, true); }; export const listRecordIdsKey = 'list_record_ids'; diff --git a/packages/twenty-zapier/src/triggers/trigger_record_updated.ts b/packages/twenty-zapier/src/triggers/trigger_record.ts similarity index 51% rename from packages/twenty-zapier/src/triggers/trigger_record_updated.ts rename to packages/twenty-zapier/src/triggers/trigger_record.ts index 1577f711c8..2046dd3348 100644 --- a/packages/twenty-zapier/src/triggers/trigger_record_updated.ts +++ b/packages/twenty-zapier/src/triggers/trigger_record.ts @@ -1,6 +1,6 @@ import { Bundle, ZObject } from 'zapier-platform-core'; -import { findObjectNamesPluralKey } from '../triggers/find_object_names_plural'; +import { findObjectNamesSingularKey } from '../triggers/find_object_names_singular'; import { listSample, Operation, @@ -9,26 +9,38 @@ import { subscribe, } from '../utils/triggers/triggers.utils'; -export const triggerRecordUpdatedKey = 'trigger_record_updated'; +export const triggerRecordKey = 'trigger_record'; const performSubscribe = (z: ZObject, bundle: Bundle) => - subscribe(z, bundle, Operation.update); -const performList = (z: ZObject, bundle: Bundle) => listSample(z, bundle); + subscribe(z, bundle, bundle.inputData.operation); +const performList = (z: ZObject, bundle: Bundle) => + listSample(z, bundle, bundle.inputData.operation === Operation.delete); export default { - key: triggerRecordUpdatedKey, + key: triggerRecordKey, noun: 'Record', display: { - label: 'Record Trigger Updated', - description: 'Triggers when a Record is updated.', + label: 'Record Trigger', + description: 'Triggers when a Record is created, updated or deleted.', }, operation: { inputFields: [ { - key: 'namePlural', + key: 'nameSingular', required: true, label: 'Record Name', - dynamic: `${findObjectNamesPluralKey}.namePlural`, + dynamic: `${findObjectNamesSingularKey}.nameSingular`, + altersDynamicFields: true, + }, + { + key: 'operation', + required: true, + label: 'Operation', + choices: { + [Operation.create]: Operation.create, + [Operation.update]: Operation.update, + [Operation.delete]: Operation.delete, + }, altersDynamicFields: true, }, ], diff --git a/packages/twenty-zapier/src/triggers/trigger_record_created.ts b/packages/twenty-zapier/src/triggers/trigger_record_created.ts deleted file mode 100644 index 145c9fb571..0000000000 --- a/packages/twenty-zapier/src/triggers/trigger_record_created.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Bundle, ZObject } from 'zapier-platform-core'; - -import { findObjectNamesPluralKey } from '../triggers/find_object_names_plural'; -import { - listSample, - Operation, - perform, - performUnsubscribe, - subscribe, -} from '../utils/triggers/triggers.utils'; - -export const triggerRecordCreatedKey = 'trigger_record_created'; - -const performSubscribe = (z: ZObject, bundle: Bundle) => - subscribe(z, bundle, Operation.create); -const performList = (z: ZObject, bundle: Bundle) => listSample(z, bundle); - -export default { - key: triggerRecordCreatedKey, - noun: 'Record', - display: { - label: 'Record Trigger Created', - description: 'Triggers when a Record is created.', - }, - operation: { - inputFields: [ - { - key: 'namePlural', - required: true, - label: 'Record Name', - dynamic: `${findObjectNamesPluralKey}.namePlural`, - altersDynamicFields: true, - }, - ], - type: 'hook', - performSubscribe, - performUnsubscribe, - perform, - performList, - sample: { - id: 'f75f6b2e-9442-4c72-aa95-47d8e5ec8cb3', - createdAt: '2023-10-19T07:37:25.306Z', - workspaceId: 'c8b070fc-c969-4ca5-837a-e7c3735734d2', - }, - outputFields: [ - { key: 'id', label: 'ID' }, - { key: 'createdAt', label: 'Created At' }, - { key: 'workspaceId', label: 'Workspace ID' }, - ], - }, -}; diff --git a/packages/twenty-zapier/src/triggers/trigger_record_deleted.ts b/packages/twenty-zapier/src/triggers/trigger_record_deleted.ts deleted file mode 100644 index dfb4218b7c..0000000000 --- a/packages/twenty-zapier/src/triggers/trigger_record_deleted.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Bundle, ZObject } from 'zapier-platform-core'; - -import { findObjectNamesPluralKey } from '../triggers/find_object_names_plural'; -import { - listSample, - Operation, - perform, - performUnsubscribe, - subscribe, -} from '../utils/triggers/triggers.utils'; - -export const triggerRecordDeletedKey = 'trigger_record_deleted'; - -const performSubscribe = (z: ZObject, bundle: Bundle) => - subscribe(z, bundle, Operation.delete); -const performList = (z: ZObject, bundle: Bundle) => listSample(z, bundle, true); - -export default { - key: triggerRecordDeletedKey, - noun: 'Record', - display: { - label: 'Record Trigger Deleted', - description: 'Triggers when a Record is deleted.', - }, - operation: { - inputFields: [ - { - key: 'namePlural', - required: true, - label: 'Record Name', - dynamic: `${findObjectNamesPluralKey}.namePlural`, - altersDynamicFields: true, - }, - ], - type: 'hook', - performSubscribe, - performUnsubscribe, - perform, - performList, - sample: { - id: 'f75f6b2e-9442-4c72-aa95-47d8e5ec8cb3', - }, - outputFields: [{ key: 'id', label: 'ID' }], - }, -}; diff --git a/packages/twenty-zapier/src/utils/data.types.ts b/packages/twenty-zapier/src/utils/data.types.ts new file mode 100644 index 0000000000..02bcb7bbbe --- /dev/null +++ b/packages/twenty-zapier/src/utils/data.types.ts @@ -0,0 +1,2 @@ +export type InputData = { [x: string]: any }; +export type ObjectData = { id: string } | { [x: string]: any }; diff --git a/packages/twenty-zapier/src/utils/getBundle.ts b/packages/twenty-zapier/src/utils/getBundle.ts index b20ae832a0..6bb2620ab9 100644 --- a/packages/twenty-zapier/src/utils/getBundle.ts +++ b/packages/twenty-zapier/src/utils/getBundle.ts @@ -1,6 +1,8 @@ import { Bundle } from 'zapier-platform-core'; -const getBundle = (inputData?: { [x: string]: any }): Bundle => { +import { InputData } from '../utils/data.types'; + +const getBundle = (inputData?: InputData): Bundle => { return { authData: { apiKey: String(process.env.API_KEY) }, inputData: inputData || {}, diff --git a/packages/twenty-zapier/src/utils/handleQueryParams.ts b/packages/twenty-zapier/src/utils/handleQueryParams.ts index ce4971f487..5bbb588156 100644 --- a/packages/twenty-zapier/src/utils/handleQueryParams.ts +++ b/packages/twenty-zapier/src/utils/handleQueryParams.ts @@ -1,5 +1,7 @@ -const handleQueryParams = (inputData: { [x: string]: any }): string => { - const formattedInputData: { [x: string]: any } = {}; +import { InputData } from '../utils/data.types'; + +const handleQueryParams = (inputData: InputData): string => { + const formattedInputData: InputData = {}; Object.keys(inputData).forEach((key) => { if (key.includes('__')) { const [objectKey, nestedObjectKey] = key.split('__'); diff --git a/packages/twenty-zapier/src/utils/requestDb.ts b/packages/twenty-zapier/src/utils/requestDb.ts index 9e655b1d9f..4123684fbe 100644 --- a/packages/twenty-zapier/src/utils/requestDb.ts +++ b/packages/twenty-zapier/src/utils/requestDb.ts @@ -14,9 +14,14 @@ export const requestSchema = async (z: ZObject, bundle: Bundle) => { return z.request(options).then((response) => response.json); }; -const requestDb = async (z: ZObject, bundle: Bundle, query: string) => { +const requestDb = async ( + z: ZObject, + bundle: Bundle, + query: string, + endpoint = 'graphql', +) => { const options = { - url: `${process.env.SERVER_BASE_URL}/graphql`, + url: `${process.env.SERVER_BASE_URL}/${endpoint}`, method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/packages/twenty-zapier/src/utils/triggers/triggers.utils.ts b/packages/twenty-zapier/src/utils/triggers/triggers.utils.ts index e1a4567d21..8cb4a656f5 100644 --- a/packages/twenty-zapier/src/utils/triggers/triggers.utils.ts +++ b/packages/twenty-zapier/src/utils/triggers/triggers.utils.ts @@ -1,5 +1,6 @@ import { Bundle, ZObject } from 'zapier-platform-core'; +import { ObjectData } from '../../utils/data.types'; import handleQueryParams from '../../utils/handleQueryParams'; import requestDb, { requestDbViaRestApi } from '../../utils/requestDb'; @@ -16,7 +17,7 @@ export const subscribe = async ( ) => { const data = { targetUrl: bundle.targetUrl, - operation: `${operation}.${bundle.inputData.namePlural}`, + operation: `${operation}.${bundle.inputData.nameSingular}`, }; const result = await requestDb( z, @@ -39,18 +40,52 @@ export const performUnsubscribe = async (z: ZObject, bundle: Bundle) => { }; export const perform = (z: ZObject, bundle: Bundle) => { - return [bundle.cleanedRequest]; + return [bundle.cleanedRequest.record]; +}; + +const getNamePluralFromNameSingular = async ( + z: ZObject, + bundle: Bundle, + nameSingular: string, +): Promise => { + const result = await requestDb( + z, + bundle, + `query GetObjects { + objects(paging: {first: 1000}) { + edges { + node { + nameSingular + namePlural + } + } + } + }`, + 'metadata', + ); + for (const object of result.data.objects.edges) { + if (object.node.nameSingular === nameSingular) { + return object.node.namePlural; + } + } + throw new Error(`Unknown Object Name Singular ${nameSingular}`); }; export const listSample = async ( z: ZObject, bundle: Bundle, onlyIds = false, -) => { +): Promise => { + const nameSingular = bundle.inputData.nameSingular; + const namePlural = await getNamePluralFromNameSingular( + z, + bundle, + nameSingular, + ); const result: { [key: string]: string }[] = await requestDbViaRestApi( z, bundle, - bundle.inputData.namePlural, + namePlural, ); if (onlyIds) { diff --git a/yarn.lock b/yarn.lock index 2f1c18f15d..8340ee3418 100644 --- a/yarn.lock +++ b/yarn.lock @@ -42347,6 +42347,9 @@ __metadata: "twenty-zapier@workspace:packages/twenty-zapier": version: 0.0.0-use.local resolution: "twenty-zapier@workspace:packages/twenty-zapier" + dependencies: + zapier-platform-cli: "npm:^15.4.1" + zapier-platform-core: "npm:15.5.1" languageName: unknown linkType: soft @@ -42605,8 +42608,6 @@ __metadata: vite-plugin-svgr: "npm:^4.2.0" vite-tsconfig-paths: "npm:^4.2.1" xlsx-ugnis: "npm:^0.19.3" - zapier-platform-cli: "npm:^15.4.1" - zapier-platform-core: "npm:15.5.1" zod: "npm:^3.22.2" languageName: unknown linkType: soft