2052 zapier integration 5 deploy twenty zapier app into the public repository (#2101)
* Add create_company Zap action * Add testing for that action * Core review returns
This commit is contained in:
@ -1,38 +1,12 @@
|
|||||||
import { Bundle, HttpRequestOptions, ZObject } from 'zapier-platform-core';
|
import { Bundle, ZObject } from 'zapier-platform-core';
|
||||||
|
import requestDb from './utils/requestDb';
|
||||||
|
|
||||||
const testAuthentication = async (z: ZObject, bundle: Bundle) => {
|
const testAuthentication = async (z: ZObject, bundle: Bundle) => {
|
||||||
const options = {
|
return await requestDb(
|
||||||
url: `${process.env.SERVER_BASE_URL}/graphql`,
|
z,
|
||||||
method: 'POST',
|
bundle,
|
||||||
headers: {
|
'query currentWorkspace {currentWorkspace {id displayName}}',
|
||||||
Authorization: `Bearer ${bundle.authData.apiKey}`,
|
);
|
||||||
},
|
|
||||||
body: {
|
|
||||||
query: 'query currentWorkspace {currentWorkspace {id displayName}}',
|
|
||||||
},
|
|
||||||
} satisfies HttpRequestOptions;
|
|
||||||
|
|
||||||
return z
|
|
||||||
.request(options)
|
|
||||||
.then((response) => {
|
|
||||||
const results = response.json;
|
|
||||||
if (results.errors) {
|
|
||||||
throw new z.errors.Error(
|
|
||||||
'The API Key you supplied is incorrect',
|
|
||||||
'AuthenticationError',
|
|
||||||
results.errors,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
response.throwForStatus();
|
|
||||||
return results;
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
throw new z.errors.Error(
|
|
||||||
'The API Key you supplied is incorrect',
|
|
||||||
'AuthenticationError',
|
|
||||||
err.message,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
106
packages/twenty-zapier/src/creates/create_company.ts
Normal file
106
packages/twenty-zapier/src/creates/create_company.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import { Bundle, ZObject } from 'zapier-platform-core';
|
||||||
|
import handleQueryParams from '../utils/handleQueryParams';
|
||||||
|
|
||||||
|
const perform = async (z: ZObject, bundle: Bundle) => {
|
||||||
|
const response = await z.request({
|
||||||
|
body: {
|
||||||
|
query: `
|
||||||
|
mutation CreateCompany {
|
||||||
|
createOneCompany(
|
||||||
|
data:{${handleQueryParams(bundle.inputData)}}
|
||||||
|
)
|
||||||
|
{id}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Accept: 'application/json',
|
||||||
|
Authorization: `Bearer ${bundle.authData.apiKey}`,
|
||||||
|
},
|
||||||
|
method: 'POST',
|
||||||
|
url: `${process.env.SERVER_BASE_URL}/graphql`,
|
||||||
|
});
|
||||||
|
return response.json;
|
||||||
|
};
|
||||||
|
export default {
|
||||||
|
display: {
|
||||||
|
description: 'Creates a new Company in Twenty',
|
||||||
|
hidden: false,
|
||||||
|
label: 'Create New Company',
|
||||||
|
},
|
||||||
|
key: 'create_company',
|
||||||
|
noun: 'Company',
|
||||||
|
operation: {
|
||||||
|
inputFields: [
|
||||||
|
{
|
||||||
|
key: 'name',
|
||||||
|
label: 'Company Name',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
list: false,
|
||||||
|
altersDynamicFields: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'address',
|
||||||
|
label: 'Address',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
list: false,
|
||||||
|
altersDynamicFields: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'domainName',
|
||||||
|
label: 'Url',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
list: false,
|
||||||
|
altersDynamicFields: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'linkedinUrl',
|
||||||
|
label: 'Linkedin',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
list: false,
|
||||||
|
altersDynamicFields: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'xUrl',
|
||||||
|
label: 'Twitter',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
list: false,
|
||||||
|
altersDynamicFields: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'annualRecurringRevenue',
|
||||||
|
label: 'ARR (Annual Recurring Revenue)',
|
||||||
|
type: 'number',
|
||||||
|
required: false,
|
||||||
|
list: false,
|
||||||
|
altersDynamicFields: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'idealCustomerProfile',
|
||||||
|
label: 'ICP (Ideal Customer Profile)',
|
||||||
|
type: 'boolean',
|
||||||
|
required: false,
|
||||||
|
list: false,
|
||||||
|
altersDynamicFields: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'employees',
|
||||||
|
label: 'Employees (number of)',
|
||||||
|
type: 'number',
|
||||||
|
required: false,
|
||||||
|
list: false,
|
||||||
|
altersDynamicFields: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sample: {
|
||||||
|
name: 'Apple',
|
||||||
|
address: 'Cupertino',
|
||||||
|
},
|
||||||
|
perform,
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -1,17 +1,16 @@
|
|||||||
import { Bundle, ZObject } from 'zapier-platform-core';
|
import { Bundle, ZObject } from 'zapier-platform-core';
|
||||||
|
import handleQueryParams from '../utils/handleQueryParams';
|
||||||
|
|
||||||
const perform = async (z: ZObject, bundle: Bundle) => {
|
const perform = async (z: ZObject, bundle: Bundle) => {
|
||||||
const response = await z.request({
|
const response = await z.request({
|
||||||
body: {
|
body: {
|
||||||
query: `mutation
|
query: `
|
||||||
CreatePerson {
|
mutation CreatePerson {
|
||||||
createOnePerson(data:{
|
createOnePerson(
|
||||||
firstName: "${bundle.inputData.firstName}",
|
data:{${handleQueryParams(bundle.inputData)}}
|
||||||
lastName: "${bundle.inputData.lastName}",
|
)
|
||||||
email: "${bundle.inputData.email}",
|
{id}
|
||||||
phone: "${bundle.inputData.phone}",
|
}`,
|
||||||
city: "${bundle.inputData.city}"
|
|
||||||
}){id}}`,
|
|
||||||
},
|
},
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
@ -1,11 +1,15 @@
|
|||||||
const { version } = require('../package.json');
|
const { version } = require('../package.json');
|
||||||
import { version as platformVersion } from 'zapier-platform-core';
|
import { version as platformVersion } from 'zapier-platform-core';
|
||||||
import createPerson from './creates/create_person';
|
import createPerson from './creates/create_person';
|
||||||
|
import createCompany from './creates/create_company';
|
||||||
import authentication from './authentication';
|
import authentication from './authentication';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
version,
|
version,
|
||||||
platformVersion,
|
platformVersion,
|
||||||
authentication: authentication,
|
authentication: authentication,
|
||||||
creates: { [createPerson.key]: createPerson },
|
creates: {
|
||||||
|
[createPerson.key]: createPerson,
|
||||||
|
[createCompany.key]: createCompany,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,8 +5,8 @@ import {
|
|||||||
createAppTester,
|
createAppTester,
|
||||||
tools,
|
tools,
|
||||||
ZObject,
|
ZObject,
|
||||||
AppError,
|
|
||||||
} from 'zapier-platform-core';
|
} from 'zapier-platform-core';
|
||||||
|
import getBundle from '../utils/getBundle';
|
||||||
const appTester = createAppTester(App);
|
const appTester = createAppTester(App);
|
||||||
tools.env.inject();
|
tools.env.inject();
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ const apiKey = String(process.env.API_KEY);
|
|||||||
|
|
||||||
describe('custom auth', () => {
|
describe('custom auth', () => {
|
||||||
it('passes authentication and returns json', async () => {
|
it('passes authentication and returns json', async () => {
|
||||||
const bundle = { authData: { apiKey } };
|
const bundle = getBundle();
|
||||||
const response = await appTester(App.authentication.test, bundle);
|
const response = await appTester(App.authentication.test, bundle);
|
||||||
expect(response.data).toHaveProperty('currentWorkspace');
|
expect(response.data).toHaveProperty('currentWorkspace');
|
||||||
expect(response.data.currentWorkspace).toHaveProperty('displayName');
|
expect(response.data.currentWorkspace).toHaveProperty('displayName');
|
||||||
@ -55,10 +55,10 @@ describe('custom auth', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('fails on invalid auth token', async () => {
|
it('fails on invalid auth token', async () => {
|
||||||
const bundle = {
|
const bundle = getBundle({
|
||||||
authData: { apiKey },
|
name: 'Test',
|
||||||
inputData: { name: 'Test', expiresAt: '2020-01-01 10:10:10.000' },
|
expiresAt: '2020-01-01 10:10:10.000',
|
||||||
};
|
});
|
||||||
const expiredToken = await appTester(generateKey, bundle);
|
const expiredToken = await appTester(generateKey, bundle);
|
||||||
const bundleWithExpiredApiKey = {
|
const bundleWithExpiredApiKey = {
|
||||||
authData: { apiKey: expiredToken },
|
authData: { apiKey: expiredToken },
|
||||||
|
|||||||
@ -0,0 +1,68 @@
|
|||||||
|
import App from '../../index';
|
||||||
|
import { Bundle, createAppTester, tools, ZObject } from 'zapier-platform-core';
|
||||||
|
import getBundle from '../../utils/getBundle';
|
||||||
|
import requestDb from '../../utils/requestDb';
|
||||||
|
const appTester = createAppTester(App);
|
||||||
|
tools.env.inject;
|
||||||
|
|
||||||
|
describe('creates.create_company', () => {
|
||||||
|
test('should run', async () => {
|
||||||
|
const bundle = getBundle({
|
||||||
|
name: 'Company Name',
|
||||||
|
address: 'Company Address',
|
||||||
|
domainName: 'Company Domain Name',
|
||||||
|
linkedinUrl: 'Test linkedinUrl',
|
||||||
|
xUrl: 'Test xUrl',
|
||||||
|
annualRecurringRevenue: 100000,
|
||||||
|
idealCustomerProfile: true,
|
||||||
|
employees: 25,
|
||||||
|
});
|
||||||
|
const result = await appTester(
|
||||||
|
App.creates.create_company.operation.perform,
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result.data?.createOneCompany?.id).toBeDefined();
|
||||||
|
const checkDbResult = await appTester(
|
||||||
|
(z: ZObject, bundle: Bundle) =>
|
||||||
|
requestDb(
|
||||||
|
z,
|
||||||
|
bundle,
|
||||||
|
`query findCompany {findUniqueCompany(where: {id: "${result.data.createOneCompany.id}"}){id, annualRecurringRevenue}}`,
|
||||||
|
),
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(checkDbResult.data.findUniqueCompany.annualRecurringRevenue).toEqual(
|
||||||
|
100000,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
test('should run with not required missing params', async () => {
|
||||||
|
const bundle = getBundle({
|
||||||
|
name: 'Company Name',
|
||||||
|
address: 'Company Address',
|
||||||
|
domainName: 'Company Domain Name',
|
||||||
|
linkedinUrl: 'Test linkedinUrl',
|
||||||
|
xUrl: 'Test xUrl',
|
||||||
|
idealCustomerProfile: true,
|
||||||
|
employees: 25,
|
||||||
|
});
|
||||||
|
const result = await appTester(
|
||||||
|
App.creates.create_company.operation.perform,
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result.data?.createOneCompany?.id).toBeDefined();
|
||||||
|
const checkDbResult = await appTester(
|
||||||
|
(z: ZObject, bundle: Bundle) =>
|
||||||
|
requestDb(
|
||||||
|
z,
|
||||||
|
bundle,
|
||||||
|
`query findCompany {findUniqueCompany(where: {id: "${result.data.createOneCompany.id}"}){id, annualRecurringRevenue}}`,
|
||||||
|
),
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(checkDbResult.data.findUniqueCompany.annualRecurringRevenue).toEqual(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,25 +1,59 @@
|
|||||||
import App from '../../index';
|
import App from '../../index';
|
||||||
import { createAppTester, tools } from 'zapier-platform-core';
|
import { Bundle, createAppTester, tools, ZObject } from 'zapier-platform-core';
|
||||||
|
import getBundle from '../../utils/getBundle';
|
||||||
|
import requestDb from '../../utils/requestDb';
|
||||||
const appTester = createAppTester(App);
|
const appTester = createAppTester(App);
|
||||||
tools.env.inject();
|
tools.env.inject();
|
||||||
|
|
||||||
describe('creates.create_person', () => {
|
describe('creates.create_person', () => {
|
||||||
test('should run', async () => {
|
test('should run', async () => {
|
||||||
const bundle = {
|
const bundle = getBundle({
|
||||||
authData: { apiKey: String(process.env.API_KEY) },
|
firstName: 'John',
|
||||||
inputData: {
|
lastName: 'Doe',
|
||||||
firstName: 'John',
|
email: 'johndoe@gmail.com',
|
||||||
lastName: 'Doe',
|
phone: '+33610203040',
|
||||||
email: 'johndoe@gmail.com',
|
city: 'Paris',
|
||||||
phone: '+33610203040',
|
});
|
||||||
city: 'Paris',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const results = await appTester(
|
const results = await appTester(
|
||||||
App.creates.create_person.operation.perform,
|
App.creates.create_person.operation.perform,
|
||||||
bundle,
|
bundle,
|
||||||
);
|
);
|
||||||
expect(results).toBeDefined();
|
expect(results).toBeDefined();
|
||||||
expect(results.data?.createOnePerson?.id).toBeDefined();
|
expect(results.data?.createOnePerson?.id).toBeDefined();
|
||||||
|
const checkDbResult = await appTester(
|
||||||
|
(z: ZObject, bundle: Bundle) =>
|
||||||
|
requestDb(
|
||||||
|
z,
|
||||||
|
bundle,
|
||||||
|
`query findPerson {findUniquePerson(id: "${results.data.createOnePerson.id}"){id, phone}}`,
|
||||||
|
),
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(checkDbResult.data.findUniquePerson.phone).toEqual('+33610203040');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should run with not required missing params', async () => {
|
||||||
|
const bundle = getBundle({
|
||||||
|
firstName: 'John',
|
||||||
|
lastName: 'Doe',
|
||||||
|
email: 'johndoe@gmail.com',
|
||||||
|
city: 'Paris',
|
||||||
|
});
|
||||||
|
const results = await appTester(
|
||||||
|
App.creates.create_person.operation.perform,
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(results).toBeDefined();
|
||||||
|
expect(results.data?.createOnePerson?.id).toBeDefined();
|
||||||
|
const checkDbResult = await appTester(
|
||||||
|
(z: ZObject, bundle: Bundle) =>
|
||||||
|
requestDb(
|
||||||
|
z,
|
||||||
|
bundle,
|
||||||
|
`query findPerson {findUniquePerson(id: "${results.data.createOnePerson.id}"){id, phone}}`,
|
||||||
|
),
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(checkDbResult.data.findUniquePerson.phone).toEqual(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1,33 @@
|
|||||||
|
import handleQueryParams from '../../utils/handleQueryParams';
|
||||||
|
|
||||||
|
describe('utils.handleQueryParams', () => {
|
||||||
|
test('should handle empty values', () => {
|
||||||
|
const inputData = {};
|
||||||
|
const result = handleQueryParams(inputData);
|
||||||
|
const expectedResult = '';
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
});
|
||||||
|
test('should format', async () => {
|
||||||
|
const inputData = {
|
||||||
|
name: 'Company Name',
|
||||||
|
address: 'Company Address',
|
||||||
|
domainName: 'Company Domain Name',
|
||||||
|
linkedinUrl: 'Test linkedinUrl',
|
||||||
|
xUrl: 'Test xUrl',
|
||||||
|
annualRecurringRevenue: 100000,
|
||||||
|
idealCustomerProfile: true,
|
||||||
|
employees: 25,
|
||||||
|
};
|
||||||
|
const result = handleQueryParams(inputData);
|
||||||
|
const expectedResult =
|
||||||
|
'name: "Company Name", ' +
|
||||||
|
'address: "Company Address", ' +
|
||||||
|
'domainName: "Company Domain Name", ' +
|
||||||
|
'linkedinUrl: "Test linkedinUrl", ' +
|
||||||
|
'xUrl: "Test xUrl", ' +
|
||||||
|
'annualRecurringRevenue: 100000, ' +
|
||||||
|
'idealCustomerProfile: true, ' +
|
||||||
|
'employees: 25';
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
});
|
||||||
|
});
|
||||||
7
packages/twenty-zapier/src/utils/getBundle.ts
Normal file
7
packages/twenty-zapier/src/utils/getBundle.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
const getBundle = (inputData?: object) => {
|
||||||
|
return {
|
||||||
|
authData: { apiKey: String(process.env.API_KEY) },
|
||||||
|
inputData,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export default getBundle;
|
||||||
11
packages/twenty-zapier/src/utils/handleQueryParams.ts
Normal file
11
packages/twenty-zapier/src/utils/handleQueryParams.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
const handleQueryParams = (inputData: { [x: string]: any }): string => {
|
||||||
|
let result = '';
|
||||||
|
Object.keys(inputData).forEach((key) => {
|
||||||
|
let quote = '';
|
||||||
|
if (typeof inputData[key] === 'string') quote = '"';
|
||||||
|
result = result.concat(`${key}: ${quote}${inputData[key]}${quote}, `);
|
||||||
|
});
|
||||||
|
if (result.length) result = result.slice(0, -2); // Remove the last ', '
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
export default handleQueryParams;
|
||||||
38
packages/twenty-zapier/src/utils/requestDb.ts
Normal file
38
packages/twenty-zapier/src/utils/requestDb.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Bundle, HttpRequestOptions, ZObject } from 'zapier-platform-core';
|
||||||
|
|
||||||
|
const requestDb = async (z: ZObject, bundle: Bundle, query: string) => {
|
||||||
|
const options = {
|
||||||
|
url: `${process.env.SERVER_BASE_URL}/graphql`,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${bundle.authData.apiKey}`,
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
query,
|
||||||
|
},
|
||||||
|
} satisfies HttpRequestOptions;
|
||||||
|
|
||||||
|
return z
|
||||||
|
.request(options)
|
||||||
|
.then((response) => {
|
||||||
|
const results = response.json;
|
||||||
|
if (results.errors) {
|
||||||
|
throw new z.errors.Error(
|
||||||
|
'The API Key you supplied is incorrect',
|
||||||
|
'AuthenticationError',
|
||||||
|
results.errors,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
response.throwForStatus();
|
||||||
|
return results;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
throw new z.errors.Error(
|
||||||
|
'The API Key you supplied is incorrect',
|
||||||
|
'AuthenticationError',
|
||||||
|
err.message,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default requestDb;
|
||||||
Reference in New Issue
Block a user