@@ -75,10 +75,6 @@ export default [
|
||||
sourceTag: 'scope:frontend',
|
||||
onlyDependOnLibsWithTags: ['scope:shared', 'scope:frontend'],
|
||||
},
|
||||
{
|
||||
sourceTag: 'scope:zapier',
|
||||
onlyDependOnLibsWithTags: ['scope:shared'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -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.
|
||||
@@ -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": {
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Bundle, ZObject } from 'zapier-platform-core';
|
||||
import type { Bundle, ZObject } from 'zapier-platform-core';
|
||||
|
||||
import { requestSchema } from '../utils/requestDb';
|
||||
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InputData } from '../utils/data.types';
|
||||
import { type InputData } from '../utils/data.types';
|
||||
|
||||
const OBJECT_SUBFIELD_NAMES = ['secondaryLinks', 'additionalPhones'];
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<boolean> => {
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user