2038 zapier integration 1 initialize a zapier app with a twenty related account (#2089)
* Add doc for Zapier development * Add twenty-zapier package * Install zapier packages * Update doc * Add twenty-zapier app * Update doc * Update apiKey slug * Update integration * Update create people to person * Update version * Fix lint * Remove useless comments * Update docs * Update version * Update naming * Add prettier * Simplify docs * Remove twenty related stuff from public doc * Use typescript boilerplate * Update details
This commit is contained in:
54
packages/twenty-zapier/src/authentication.ts
Normal file
54
packages/twenty-zapier/src/authentication.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { Bundle, HttpRequestOptions, ZObject } from 'zapier-platform-core';
|
||||
|
||||
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,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
type: 'custom',
|
||||
test: testAuthentication,
|
||||
fields: [
|
||||
{
|
||||
computed: false,
|
||||
key: 'apiKey',
|
||||
required: true,
|
||||
label: 'Api Key',
|
||||
type: 'string',
|
||||
helpText:
|
||||
'Create the api key in [your twenty workspace](https://app.twenty.com/settings/apis)',
|
||||
},
|
||||
],
|
||||
connectionLabel: '{{data.currentWorkspace.displayName}}',
|
||||
customConfig: {},
|
||||
};
|
||||
84
packages/twenty-zapier/src/creates/create_person.ts
Normal file
84
packages/twenty-zapier/src/creates/create_person.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { Bundle, ZObject } from 'zapier-platform-core';
|
||||
|
||||
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}}`,
|
||||
},
|
||||
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 Person in Twenty',
|
||||
hidden: false,
|
||||
label: 'Create New Person',
|
||||
},
|
||||
key: 'create_person',
|
||||
noun: 'Person',
|
||||
operation: {
|
||||
inputFields: [
|
||||
{
|
||||
key: 'firstName',
|
||||
label: 'First Name',
|
||||
type: 'string',
|
||||
required: true,
|
||||
list: false,
|
||||
altersDynamicFields: false,
|
||||
},
|
||||
{
|
||||
key: 'lastName',
|
||||
label: 'Last Name',
|
||||
type: 'string',
|
||||
required: false,
|
||||
list: false,
|
||||
altersDynamicFields: false,
|
||||
},
|
||||
{
|
||||
key: 'email',
|
||||
label: 'Email',
|
||||
type: 'string',
|
||||
required: true,
|
||||
list: false,
|
||||
altersDynamicFields: false,
|
||||
},
|
||||
{
|
||||
key: 'phone',
|
||||
label: 'Phone',
|
||||
type: 'string',
|
||||
required: false,
|
||||
list: false,
|
||||
altersDynamicFields: false,
|
||||
},
|
||||
{
|
||||
key: 'city',
|
||||
label: 'City',
|
||||
type: 'string',
|
||||
required: false,
|
||||
list: false,
|
||||
altersDynamicFields: false,
|
||||
},
|
||||
],
|
||||
sample: {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
email: 'johndoe@gmail.com',
|
||||
},
|
||||
perform,
|
||||
},
|
||||
};
|
||||
11
packages/twenty-zapier/src/index.ts
Normal file
11
packages/twenty-zapier/src/index.ts
Normal file
@ -0,0 +1,11 @@
|
||||
const { version } = require('../package.json');
|
||||
import { version as platformVersion } from 'zapier-platform-core';
|
||||
import createPerson from './creates/create_person';
|
||||
import authentication from './authentication';
|
||||
|
||||
export default {
|
||||
version,
|
||||
platformVersion,
|
||||
authentication: authentication,
|
||||
creates: { [createPerson.key]: createPerson },
|
||||
};
|
||||
75
packages/twenty-zapier/src/test/authentication.test.ts
Normal file
75
packages/twenty-zapier/src/test/authentication.test.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import App from '../index';
|
||||
import {
|
||||
Bundle,
|
||||
HttpRequestOptions,
|
||||
createAppTester,
|
||||
tools,
|
||||
ZObject,
|
||||
AppError,
|
||||
} from 'zapier-platform-core';
|
||||
const appTester = createAppTester(App);
|
||||
tools.env.inject();
|
||||
|
||||
const generateKey = async (z: ZObject, bundle: Bundle) => {
|
||||
const options = {
|
||||
url: `${process.env.SERVER_BASE_URL}/graphql`,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${bundle.authData.apiKey}`,
|
||||
},
|
||||
body: {
|
||||
query: `mutation
|
||||
CreateApiKey {
|
||||
createOneApiKey(data:{
|
||||
name:"${bundle.inputData.name}",
|
||||
expiresAt: "${bundle.inputData.expiresAt}"
|
||||
}) {token}}`,
|
||||
},
|
||||
} satisfies HttpRequestOptions;
|
||||
return z.request(options).then((response) => {
|
||||
const results = response.json;
|
||||
return results.data.createOneApiKey.token;
|
||||
});
|
||||
};
|
||||
|
||||
const apiKey = String(process.env.API_KEY);
|
||||
|
||||
describe('custom auth', () => {
|
||||
it('passes authentication and returns json', async () => {
|
||||
const bundle = { authData: { apiKey } };
|
||||
const response = await appTester(App.authentication.test, bundle);
|
||||
expect(response.data).toHaveProperty('currentWorkspace');
|
||||
expect(response.data.currentWorkspace).toHaveProperty('displayName');
|
||||
});
|
||||
|
||||
it('fails on bad auth token format', async () => {
|
||||
const bundle = { authData: { apiKey: 'bad' } };
|
||||
|
||||
try {
|
||||
await appTester(App.authentication.test, bundle);
|
||||
} catch (error: any) {
|
||||
expect(error.message).toContain('The API Key you supplied is incorrect');
|
||||
return;
|
||||
}
|
||||
throw new Error('appTester should have thrown');
|
||||
});
|
||||
|
||||
it('fails on invalid auth token', async () => {
|
||||
const bundle = {
|
||||
authData: { apiKey },
|
||||
inputData: { name: 'Test', expiresAt: '2020-01-01 10:10:10.000' },
|
||||
};
|
||||
const expiredToken = await appTester(generateKey, bundle);
|
||||
const bundleWithExpiredApiKey = {
|
||||
authData: { apiKey: expiredToken },
|
||||
};
|
||||
|
||||
try {
|
||||
await appTester(App.authentication.test, bundleWithExpiredApiKey);
|
||||
} catch (error: any) {
|
||||
expect(error.message).toContain('The API Key you supplied is incorrect');
|
||||
return;
|
||||
}
|
||||
throw new Error('appTester should have thrown');
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,25 @@
|
||||
import App from '../../index';
|
||||
import { createAppTester, tools } from 'zapier-platform-core';
|
||||
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 results = await appTester(
|
||||
App.creates.create_person.operation.perform,
|
||||
bundle,
|
||||
);
|
||||
expect(results).toBeDefined();
|
||||
expect(results.data?.createOnePerson?.id).toBeDefined();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user