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:
martmull 2023-10-18 17:56:40 +02:00 committed by GitHub
parent 547a17b145
commit 51a06b3ebd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 334 additions and 60 deletions

View File

@ -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 options = {
url: `${process.env.SERVER_BASE_URL}/graphql`,
method: 'POST',
headers: {
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,
);
});
return await requestDb(
z,
bundle,
'query currentWorkspace {currentWorkspace {id displayName}}',
);
};
export default {

View 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,
},
};

View File

@ -1,17 +1,16 @@
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
CreatePerson {
createOnePerson(data:{
firstName: "${bundle.inputData.firstName}",
lastName: "${bundle.inputData.lastName}",
email: "${bundle.inputData.email}",
phone: "${bundle.inputData.phone}",
city: "${bundle.inputData.city}"
}){id}}`,
query: `
mutation CreatePerson {
createOnePerson(
data:{${handleQueryParams(bundle.inputData)}}
)
{id}
}`,
},
headers: {
'Content-Type': 'application/json',

View File

@ -1,11 +1,15 @@
const { version } = require('../package.json');
import { version as platformVersion } from 'zapier-platform-core';
import createPerson from './creates/create_person';
import createCompany from './creates/create_company';
import authentication from './authentication';
export default {
version,
platformVersion,
authentication: authentication,
creates: { [createPerson.key]: createPerson },
creates: {
[createPerson.key]: createPerson,
[createCompany.key]: createCompany,
},
};

View File

@ -5,8 +5,8 @@ import {
createAppTester,
tools,
ZObject,
AppError,
} from 'zapier-platform-core';
import getBundle from '../utils/getBundle';
const appTester = createAppTester(App);
tools.env.inject();
@ -36,7 +36,7 @@ const apiKey = String(process.env.API_KEY);
describe('custom auth', () => {
it('passes authentication and returns json', async () => {
const bundle = { authData: { apiKey } };
const bundle = getBundle();
const response = await appTester(App.authentication.test, bundle);
expect(response.data).toHaveProperty('currentWorkspace');
expect(response.data.currentWorkspace).toHaveProperty('displayName');
@ -55,10 +55,10 @@ describe('custom auth', () => {
});
it('fails on invalid auth token', async () => {
const bundle = {
authData: { apiKey },
inputData: { name: 'Test', expiresAt: '2020-01-01 10:10:10.000' },
};
const bundle = getBundle({
name: 'Test',
expiresAt: '2020-01-01 10:10:10.000',
});
const expiredToken = await appTester(generateKey, bundle);
const bundleWithExpiredApiKey = {
authData: { apiKey: expiredToken },

View File

@ -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,
);
});
});

View File

@ -1,25 +1,59 @@
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);
tools.env.inject();
describe('creates.create_person', () => {
test('should run', async () => {
const bundle = {
authData: { apiKey: String(process.env.API_KEY) },
inputData: {
firstName: 'John',
lastName: 'Doe',
email: 'johndoe@gmail.com',
phone: '+33610203040',
city: 'Paris',
},
};
const bundle = getBundle({
firstName: 'John',
lastName: 'Doe',
email: 'johndoe@gmail.com',
phone: '+33610203040',
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('+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);
});
});

View File

@ -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);
});
});

View File

@ -0,0 +1,7 @@
const getBundle = (inputData?: object) => {
return {
authData: { apiKey: String(process.env.API_KEY) },
inputData,
};
};
export default getBundle;

View 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;

View 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;