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:
martmull
2023-10-17 21:00:20 +02:00
committed by GitHub
parent 01e9545a59
commit 54735c4880
15 changed files with 2842 additions and 1 deletions

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

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

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

View 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');
});
});

View File

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