diff --git a/eslint.config.mjs b/eslint.config.mjs index 454dfda53d..83fb705010 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -75,10 +75,6 @@ export default [ sourceTag: 'scope:frontend', onlyDependOnLibsWithTags: ['scope:shared', 'scope:frontend'], }, - { - sourceTag: 'scope:zapier', - onlyDependOnLibsWithTags: ['scope:shared'], - }, ], }, ], diff --git a/packages/twenty-zapier/CHANGELOG.md b/packages/twenty-zapier/CHANGELOG.md new file mode 100644 index 0000000000..da45e65e14 --- /dev/null +++ b/packages/twenty-zapier/CHANGELOG.md @@ -0,0 +1,13 @@ +## 2.1.1 +* Add changelog + +## 2.1.0 +* Fix some authentication issues +* Remove position from input fields +* Fix required boolean fields that should not be + +## 2.0.0 +* First release + +## 1.0.0 +Initial release to public. diff --git a/packages/twenty-zapier/package.json b/packages/twenty-zapier/package.json index 5a11533c65..c9e3f11c7e 100644 --- a/packages/twenty-zapier/package.json +++ b/packages/twenty-zapier/package.json @@ -1,6 +1,6 @@ { "name": "twenty-zapier", - "version": "2.0.2", + "version": "2.1.1", "description": "Effortlessly sync Twenty with 3000+ apps. Automate tasks, boost productivity, and supercharge your customer relationships!", "main": "src/index.ts", "engines": { diff --git a/packages/twenty-zapier/project.json b/packages/twenty-zapier/project.json index 825ab801b6..f016527de2 100644 --- a/packages/twenty-zapier/project.json +++ b/packages/twenty-zapier/project.json @@ -8,9 +8,9 @@ "executor": "nx:run-commands", "options": { "cwd": "{projectRoot}", - "commands": ["nx run twenty-zapier:clean && tsc"] + "commands": ["tsc"] }, - "dependsOn": ["^build"] + "dependsOn": ["clean","^build"] }, "format": { "executor": "nx:run-commands", @@ -24,14 +24,17 @@ "options": { "cwd": "{projectRoot}", "commands": ["NODE_ENV=test && nx run twenty-zapier:build && jest --testTimeout 10000 --rootDir ./lib/test"] - } + }, + "dependsOn": ["build"] }, + "typecheck": {}, "validate": { "executor": "nx:run-commands", "options": { "cwd": "{projectRoot}", "commands": ["nx run twenty-zapier:build && zapier validate"] - } + }, + "dependsOn": ["build"] }, "versions": { "executor": "nx:run-commands", @@ -45,7 +48,8 @@ "options": { "cwd": "{projectRoot}", "commands": ["nx run twenty-zapier:clean && npx tsc --watch"] - } + }, + "dependsOn": ["build"] }, "clean": { "executor": "nx:run-commands", @@ -59,9 +63,20 @@ "options": { "cwd": "{projectRoot}", "commands": [ - "nx run twenty-zapier:build && cp -r ../twenty-shared/ node_modules/twenty-shared && zapier push --skip-npm-install" + "nx run twenty-zapier:build && zapier push --disable-dependency-detection" ] - } + }, + "dependsOn": ["build"] + }, + "promote": { + "executor": "nx:run-commands", + "options": { + "cwd": "{projectRoot}", + "commands": [ + "zapier promote" + ] + }, + "dependsOn": ["build"] } } } diff --git a/packages/twenty-zapier/src/authentication.ts b/packages/twenty-zapier/src/authentication.ts index 5502f986ca..486c5aedb6 100644 --- a/packages/twenty-zapier/src/authentication.ts +++ b/packages/twenty-zapier/src/authentication.ts @@ -1,4 +1,4 @@ -import { type Bundle, type ZObject } from 'zapier-platform-core'; +import type { Bundle, ZObject } from 'zapier-platform-core'; import requestDb from './utils/requestDb'; diff --git a/packages/twenty-zapier/src/creates/crud_record.ts b/packages/twenty-zapier/src/creates/crud_record.ts index 78e85626bd..900f3a4e13 100644 --- a/packages/twenty-zapier/src/creates/crud_record.ts +++ b/packages/twenty-zapier/src/creates/crud_record.ts @@ -1,12 +1,18 @@ -import { Bundle, ZObject } from 'zapier-platform-core'; +import type { Bundle, ZObject } from 'zapier-platform-core'; import { findObjectNamesSingularKey } from '../triggers/find_object_names_singular'; import { listRecordIdsKey } from '../triggers/list_record_ids'; import { computeInputFields } from '../utils/computeInputFields'; -import { InputData } from '../utils/data.types'; +import { type InputData } from '../utils/data.types'; import handleQueryParams from '../utils/handleQueryParams'; import requestDb, { requestSchema } from '../utils/requestDb'; import { DatabaseEventAction } from '../utils/triggers/triggers.utils'; -import { capitalize } from 'twenty-shared/utils'; +import { isNonEmptyString } from '@sniptt/guards'; + +const capitalize = (stringToCapitalize: string) => { + if (!isNonEmptyString(stringToCapitalize)) return ''; + + return stringToCapitalize[0].toUpperCase() + stringToCapitalize.slice(1); +}; export const recordInputFields = async ( z: ZObject, diff --git a/packages/twenty-zapier/src/test/authentication.test.ts b/packages/twenty-zapier/src/test/authentication.test.ts index 66844af5e8..fa8fe0a631 100644 --- a/packages/twenty-zapier/src/test/authentication.test.ts +++ b/packages/twenty-zapier/src/test/authentication.test.ts @@ -1,34 +1,10 @@ -import { Bundle, createAppTester, tools, ZObject } from 'zapier-platform-core'; +import { createAppTester, tools } from 'zapier-platform-core'; import App from '../index'; import getBundle from '../utils/getBundle'; -import handleQueryParams from '../utils/handleQueryParams'; -import requestDb from '../utils/requestDb'; const appTester = createAppTester(App); tools.env.inject(); -const createApiKey = async (z: ZObject, bundle: Bundle) => { - const query = ` - mutation createApiKey { - createApiKey( - data:{${handleQueryParams(bundle.inputData)}} - ) - {id} - }`; - return (await requestDb(z, bundle, query)).data.createApiKey.id; -}; - -const generateApiKeyToken = async (z: ZObject, bundle: Bundle) => { - const query = ` - mutation generateApiKeyToken { - generateApiKeyToken( - ${handleQueryParams(bundle.inputData)} - ) - {token} - }`; - return (await requestDb(z, bundle, query)).data.generateApiKeyToken.token; -}; - describe('custom auth', () => { it('passes authentication and returns json', async () => { const bundle = getBundle(); @@ -78,31 +54,4 @@ describe('custom auth', () => { } throw new Error('appTester should have thrown'); }); - - it('fails on invalid auth token', async () => { - const expiresAt = '2020-01-01 10:10:10.000'; - const apiKeyBundle = getBundle({ - name: 'Test', - expiresAt, - }); - const apiKeyId = await appTester(createApiKey, apiKeyBundle); - const generateTokenBundle = getBundle({ - apiKeyId: apiKeyId, - expiresAt, - }); - const expiredToken = await appTester( - generateApiKeyToken, - generateTokenBundle, - ); - const bundleWithExpiredApiKey = getBundle({}); - bundleWithExpiredApiKey.authData.apiKey = expiredToken; - - try { - await appTester(App.authentication.test, bundleWithExpiredApiKey); - } catch (error: any) { - expect(error.message).toContain('UNAUTHENTICATED'); - return; - } - throw new Error('appTester should have thrown'); - }); }); diff --git a/packages/twenty-zapier/src/test/creates/crud_record.test.ts b/packages/twenty-zapier/src/test/creates/crud_record.test.ts index ff4dfc46dd..d6707137c3 100644 --- a/packages/twenty-zapier/src/test/creates/crud_record.test.ts +++ b/packages/twenty-zapier/src/test/creates/crud_record.test.ts @@ -1,4 +1,9 @@ -import { Bundle, createAppTester, tools, ZObject } from 'zapier-platform-core'; +import { + type Bundle, + createAppTester, + tools, + type ZObject, +} from 'zapier-platform-core'; import { crudRecordKey } from '../../creates/crud_record'; import App from '../../index'; @@ -16,16 +21,18 @@ describe('creates.create_company', () => { name: 'Company Name', address: { addressCity: 'Paris' }, linkedinLink: { - primaryLinkUrl: '/linkedin_url', + primaryLinkUrl: 'http://test.com/linkedin_url', primaryLinkLabel: 'Test linkedinUrl', secondaryLinks: [ - '{ url: "/linkedin_url2", label: "Test linkedinUrl2" }', + '{ url: "http://test.com/linkedin_url2", label: "Test linkedinUrl2" }', ], }, xLink: { - primaryLinkUrl: '/x_url', + primaryLinkUrl: 'http://test.com/x_url', primaryLinkLabel: 'Test xUrl', - secondaryLinks: ['{ url: "/x_url2", label: "Test xUrl2" }'], + secondaryLinks: [ + '{ url: "http://test.com/x_url2", label: "Test xUrl2" }', + ], }, annualRecurringRevenue: { amountMicros: 100000000000, 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 4352561454..2f5fbf4710 100644 --- a/packages/twenty-zapier/src/test/triggers/trigger_record.test.ts +++ b/packages/twenty-zapier/src/test/triggers/trigger_record.test.ts @@ -1,4 +1,8 @@ -import { Bundle, createAppTester, ZObject } from 'zapier-platform-core'; +import { + type Bundle, + createAppTester, + type ZObject, +} from 'zapier-platform-core'; import App from '../../index'; import { triggerRecordKey } from '../../triggers/trigger_record'; @@ -57,8 +61,7 @@ describe('triggers.trigger_record.created', () => { unsubscribeBundle, ); - expect(unsubscribeResult).toBeDefined(); - expect(unsubscribeResult.id).toEqual(result.id); + expect(unsubscribeResult).toBeTruthy(); const checkDbResult = await appTester( (z: ZObject, bundle: Bundle) => @@ -146,9 +149,7 @@ describe('triggers.trigger_record.update', () => { bundle, ); - expect(checkDbResult.data.webhooks.operations[0]).toEqual( - 'company.updated', - ); + expect(checkDbResult.data.webhook.operations[0]).toEqual('company.updated'); }); test('should succeed to unsubscribe', async () => { const bundle = getBundle({}); @@ -171,8 +172,7 @@ describe('triggers.trigger_record.update', () => { unsubscribeBundle, ); - expect(unsubscribeResult).toBeDefined(); - expect(unsubscribeResult.id).toEqual(result.id); + expect(unsubscribeResult).toBeTruthy(); const checkDbResult = await appTester( (z: ZObject, bundle: Bundle) => @@ -231,9 +231,7 @@ describe('triggers.trigger_record.delete', () => { bundle, ); - expect(checkDbResult.data.webhooks.operations[0]).toEqual( - 'company.deleted', - ); + expect(checkDbResult.data.webhook.operations[0]).toEqual('company.deleted'); }); test('should succeed to unsubscribe', async () => { const bundle = getBundle({}); @@ -256,8 +254,7 @@ describe('triggers.trigger_record.delete', () => { unsubscribeBundle, ); - expect(unsubscribeResult).toBeDefined(); - expect(unsubscribeResult.id).toEqual(result.id); + expect(unsubscribeResult).toBeTruthy(); const checkDbResult = await appTester( (z: ZObject, bundle: Bundle) => diff --git a/packages/twenty-zapier/src/test/utils/computeInputFields.test.ts b/packages/twenty-zapier/src/test/utils/computeInputFields.test.ts index 2d794e2985..7239d03cda 100644 --- a/packages/twenty-zapier/src/test/utils/computeInputFields.test.ts +++ b/packages/twenty-zapier/src/test/utils/computeInputFields.test.ts @@ -1,6 +1,6 @@ +import { FieldMetadataType } from '../../types/FieldMetadataType'; import { computeInputFields } from '../../utils/computeInputFields'; -import { InputField } from '../../utils/data.types'; -import { FieldMetadataType } from 'twenty-shared/types'; +import { type InputField } from '../../utils/data.types'; describe('computeInputFields', () => { test('should create Person input fields properly', () => { @@ -110,6 +110,16 @@ describe('computeInputFields', () => { defaultValue: null, }, }, + { + node: { + type: FieldMetadataType.BOOLEAN, + name: 'ICP', + label: 'ICP', + description: 'ICP', + isNullable: false, + defaultValue: false, + }, + }, ], }, }; @@ -215,6 +225,15 @@ describe('computeInputFields', () => { list: false, placeholder: undefined, }, + { + key: 'ICP', + label: 'ICP', + type: 'boolean', + helpText: 'ICP', + required: false, + list: false, + placeholder: undefined, + }, ]; const idInputField: InputField = { key: 'id', 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 a0b5dcf757..77c9107695 100644 --- a/packages/twenty-zapier/src/triggers/find_object_names_singular.ts +++ b/packages/twenty-zapier/src/triggers/find_object_names_singular.ts @@ -1,4 +1,4 @@ -import { Bundle, ZObject } from 'zapier-platform-core'; +import type { Bundle, ZObject } from 'zapier-platform-core'; import { requestSchema } from '../utils/requestDb'; diff --git a/packages/twenty-zapier/src/types/FieldMetadataType.ts b/packages/twenty-zapier/src/types/FieldMetadataType.ts new file mode 100644 index 0000000000..287d2c59d6 --- /dev/null +++ b/packages/twenty-zapier/src/types/FieldMetadataType.ts @@ -0,0 +1,27 @@ +export enum FieldMetadataType { + UUID = 'UUID', + TEXT = 'TEXT', + PHONES = 'PHONES', + EMAILS = 'EMAILS', + DATE_TIME = 'DATE_TIME', + DATE = 'DATE', + BOOLEAN = 'BOOLEAN', + NUMBER = 'NUMBER', + NUMERIC = 'NUMERIC', + LINKS = 'LINKS', + CURRENCY = 'CURRENCY', + FULL_NAME = 'FULL_NAME', + RATING = 'RATING', + SELECT = 'SELECT', + MULTI_SELECT = 'MULTI_SELECT', + RELATION = 'RELATION', + MORPH_RELATION = 'MORPH_RELATION', + POSITION = 'POSITION', + ADDRESS = 'ADDRESS', + RAW_JSON = 'RAW_JSON', + RICH_TEXT = 'RICH_TEXT', + RICH_TEXT_V2 = 'RICH_TEXT_V2', + ACTOR = 'ACTOR', + ARRAY = 'ARRAY', + TS_VECTOR = 'TS_VECTOR', +} diff --git a/packages/twenty-zapier/src/utils/computeInputFields.ts b/packages/twenty-zapier/src/utils/computeInputFields.ts index 87ec13973c..186f7c85f6 100644 --- a/packages/twenty-zapier/src/utils/computeInputFields.ts +++ b/packages/twenty-zapier/src/utils/computeInputFields.ts @@ -1,5 +1,9 @@ -import { InputField, Node, NodeField } from '../utils/data.types'; -import { FieldMetadataType } from 'twenty-shared/types'; +import { FieldMetadataType } from '../types/FieldMetadataType'; +import { + type InputField, + type Node, + type NodeField, +} from '../utils/data.types'; const getListFromFieldMetadataType = (fieldMetadataType: FieldMetadataType) => { return fieldMetadataType === FieldMetadataType.ARRAY; @@ -206,7 +210,7 @@ const get_subfieldsFromField = (nodeField: NodeField): NodeField[] => { }; const isFieldRequired = (nodeField: NodeField): boolean => { - return !nodeField.isNullable && !nodeField.defaultValue; + return !nodeField.isNullable && nodeField.defaultValue === null; }; export const computeInputFields = ( @@ -244,7 +248,6 @@ export const computeInputFields = ( case FieldMetadataType.BOOLEAN: case FieldMetadataType.NUMBER: case FieldMetadataType.NUMERIC: - case FieldMetadataType.POSITION: case FieldMetadataType.ARRAY: case FieldMetadataType.RATING: { const nodeFieldType = getTypeFromFieldMetadataType(nodeField.type); diff --git a/packages/twenty-zapier/src/utils/data.types.ts b/packages/twenty-zapier/src/utils/data.types.ts index d6aacf8a3e..ac9ff0c52e 100644 --- a/packages/twenty-zapier/src/utils/data.types.ts +++ b/packages/twenty-zapier/src/utils/data.types.ts @@ -1,4 +1,5 @@ -import { FieldMetadataType } from 'twenty-shared/types'; +import { type FieldMetadataType } from '../types/FieldMetadataType'; + export type InputData = { [x: string]: any }; export type NodeField = { @@ -7,7 +8,7 @@ export type NodeField = { label: string; description: string | null; isNullable: boolean; - defaultValue: object | null; + defaultValue: boolean | object | null; list?: boolean; placeholder?: string; }; diff --git a/packages/twenty-zapier/src/utils/getBundle.ts b/packages/twenty-zapier/src/utils/getBundle.ts index 6bb2620ab9..ed31f7aff3 100644 --- a/packages/twenty-zapier/src/utils/getBundle.ts +++ b/packages/twenty-zapier/src/utils/getBundle.ts @@ -1,6 +1,6 @@ -import { Bundle } from 'zapier-platform-core'; +import { type Bundle } from 'zapier-platform-core'; -import { InputData } from '../utils/data.types'; +import { type InputData } from '../utils/data.types'; const getBundle = (inputData?: InputData): Bundle => { return { diff --git a/packages/twenty-zapier/src/utils/handleQueryParams.ts b/packages/twenty-zapier/src/utils/handleQueryParams.ts index 15039e9e35..e94c835371 100644 --- a/packages/twenty-zapier/src/utils/handleQueryParams.ts +++ b/packages/twenty-zapier/src/utils/handleQueryParams.ts @@ -1,4 +1,4 @@ -import { InputData } from '../utils/data.types'; +import { type InputData } from '../utils/data.types'; const OBJECT_SUBFIELD_NAMES = ['secondaryLinks', 'additionalPhones']; diff --git a/packages/twenty-zapier/src/utils/requestDb.ts b/packages/twenty-zapier/src/utils/requestDb.ts index 5e55820498..e5e4a9374a 100644 --- a/packages/twenty-zapier/src/utils/requestDb.ts +++ b/packages/twenty-zapier/src/utils/requestDb.ts @@ -1,6 +1,6 @@ -import { Bundle, HttpRequestOptions, ZObject } from 'zapier-platform-core'; +import type { Bundle, HttpRequestOptions, ZObject } from 'zapier-platform-core'; -import { Schema } from '../utils/data.types'; +import { type Schema } from '../utils/data.types'; export const requestSchema = async ( z: ZObject, diff --git a/packages/twenty-zapier/src/utils/triggers/triggers.utils.ts b/packages/twenty-zapier/src/utils/triggers/triggers.utils.ts index 2383d4c38c..e428de3f5c 100644 --- a/packages/twenty-zapier/src/utils/triggers/triggers.utils.ts +++ b/packages/twenty-zapier/src/utils/triggers/triggers.utils.ts @@ -31,12 +31,15 @@ export const performSubscribe = async (z: ZObject, bundle: Bundle) => { return result.data.createWebhook; }; -export const performUnsubscribe = async (z: ZObject, bundle: Bundle) => { +export const performUnsubscribe = async ( + z: ZObject, + bundle: Bundle, +): Promise => { const data = { id: bundle.subscribeData?.id }; const result = await requestDb( z, bundle, - `mutation deleteWebhook {deleteWebhook(${handleQueryParams(data)})}`, + `mutation deleteWebhook {deleteWebhook(input: {${handleQueryParams(data)}})}`, 'metadata', ); return result.data.deleteWebhook;