2248 zapier integration implement typeorm eventsubscribers (#3122)
* Add new queue to twenty-server * Add triggers to zapier * Rename webhook operation * Use find one or fail * Use logger * Fix typescript templating * Add dedicated call webhook job * Update logging * Fix error handling
This commit is contained in:
@ -1,3 +1,3 @@
|
||||
export const capitalize = (word: string): string => {
|
||||
return word.charAt(0).toUpperCase() + word.slice(1)
|
||||
}
|
||||
return word.charAt(0).toUpperCase() + word.slice(1);
|
||||
};
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
import { labelling } from "../utils/labelling";
|
||||
import { labelling } from '../utils/labelling';
|
||||
|
||||
type Infos = {
|
||||
properties: {
|
||||
[field: string]: {
|
||||
type: string;
|
||||
properties?: { [field: string]: { type: string } }
|
||||
items?: { [$ref: string]: string }
|
||||
}
|
||||
},
|
||||
example: object,
|
||||
required: string[]
|
||||
}
|
||||
properties?: { [field: string]: { type: string } };
|
||||
items?: { [$ref: string]: string };
|
||||
};
|
||||
};
|
||||
example: object;
|
||||
required: string[];
|
||||
};
|
||||
|
||||
export const computeInputFields = (infos: Infos): object[] => {
|
||||
const result = []
|
||||
const result = [];
|
||||
|
||||
for (const fieldName of Object.keys(infos.properties)) {
|
||||
switch (infos.properties[fieldName].type) {
|
||||
@ -23,17 +23,19 @@ export const computeInputFields = (infos: Infos): object[] => {
|
||||
if (!infos.properties[fieldName].properties) {
|
||||
break;
|
||||
}
|
||||
for (const subFieldName of Object.keys(infos.properties[fieldName].properties || {})) {
|
||||
for (const subFieldName of Object.keys(
|
||||
infos.properties[fieldName].properties || {},
|
||||
)) {
|
||||
const field = {
|
||||
key: `${fieldName}__${subFieldName}`,
|
||||
label: `${labelling(fieldName)}: ${labelling(subFieldName)}`,
|
||||
type: infos.properties[fieldName].properties?.[subFieldName].type,
|
||||
required: false,
|
||||
}
|
||||
};
|
||||
if (infos.required?.includes(fieldName)) {
|
||||
field.required = true
|
||||
field.required = true;
|
||||
}
|
||||
result.push(field)
|
||||
result.push(field);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@ -42,13 +44,13 @@ export const computeInputFields = (infos: Infos): object[] => {
|
||||
label: labelling(fieldName),
|
||||
type: infos.properties[fieldName].type,
|
||||
required: false,
|
||||
}
|
||||
};
|
||||
if (infos.required?.includes(fieldName)) {
|
||||
field.required = true
|
||||
field.required = true;
|
||||
}
|
||||
result.push(field)
|
||||
result.push(field);
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
@ -1,25 +1,29 @@
|
||||
const handleQueryParams = (inputData: { [x: string]: any }): string => {
|
||||
const formattedInputData: {[x:string]: any} = {};
|
||||
const formattedInputData: { [x: string]: any } = {};
|
||||
Object.keys(inputData).forEach((key) => {
|
||||
if(key.includes('__')) {
|
||||
const [objectKey, nestedObjectKey] = key.split('__')
|
||||
if (key.includes('__')) {
|
||||
const [objectKey, nestedObjectKey] = key.split('__');
|
||||
if (formattedInputData[objectKey]) {
|
||||
formattedInputData[objectKey][nestedObjectKey] = inputData[key]
|
||||
formattedInputData[objectKey][nestedObjectKey] = inputData[key];
|
||||
} else {
|
||||
formattedInputData[objectKey] = {[nestedObjectKey]: inputData[key]}
|
||||
formattedInputData[objectKey] = { [nestedObjectKey]: inputData[key] };
|
||||
}
|
||||
} else {
|
||||
formattedInputData[key]=inputData[key]
|
||||
formattedInputData[key] = inputData[key];
|
||||
}
|
||||
})
|
||||
});
|
||||
let result = '';
|
||||
Object.keys(formattedInputData).forEach((key) => {
|
||||
let quote = '';
|
||||
if (typeof formattedInputData[key]==='object') {
|
||||
result=result.concat(`${key}: {${handleQueryParams(formattedInputData[key])}}, `)
|
||||
if (typeof formattedInputData[key] === 'object') {
|
||||
result = result.concat(
|
||||
`${key}: {${handleQueryParams(formattedInputData[key])}}, `,
|
||||
);
|
||||
} else {
|
||||
if (typeof formattedInputData[key] === 'string') quote = '"';
|
||||
result = result.concat(`${key}: ${quote}${formattedInputData[key]}${quote}, `);
|
||||
if (typeof formattedInputData[key] === 'string') quote = '"';
|
||||
result = result.concat(
|
||||
`${key}: ${quote}${formattedInputData[key]}${quote}, `,
|
||||
);
|
||||
}
|
||||
});
|
||||
if (result.length) result = result.slice(0, -2); // Remove the last ', '
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { capitalize } from "../utils/capitalize";
|
||||
import { capitalize } from '../utils/capitalize';
|
||||
|
||||
export const labelling = (str: string): string => {
|
||||
return str
|
||||
.replace(/[A-Z]/g, letter => ` ${letter.toLowerCase()}`)
|
||||
.replace(/[A-Z]/g, (letter) => ` ${letter.toLowerCase()}`)
|
||||
.split(' ')
|
||||
.map((word)=> capitalize(word))
|
||||
.map((word) => capitalize(word))
|
||||
.join(' ');
|
||||
}
|
||||
};
|
||||
|
||||
@ -2,18 +2,17 @@ import { Bundle, HttpRequestOptions, ZObject } from 'zapier-platform-core';
|
||||
|
||||
export const requestSchema = async (z: ZObject, bundle: Bundle) => {
|
||||
const options = {
|
||||
url: `${process.env.SERVER_BASE_URL}/open-api`,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${bundle.authData.apiKey}`,
|
||||
},
|
||||
url: `${process.env.SERVER_BASE_URL}/open-api`,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${bundle.authData.apiKey}`,
|
||||
},
|
||||
} satisfies HttpRequestOptions;
|
||||
|
||||
return z.request(options)
|
||||
.then((response) => response.json)
|
||||
}
|
||||
return z.request(options).then((response) => response.json);
|
||||
};
|
||||
|
||||
const requestDb = async (z: ZObject, bundle: Bundle, query: string) => {
|
||||
const options = {
|
||||
@ -37,7 +36,7 @@ const requestDb = async (z: ZObject, bundle: Bundle, query: string) => {
|
||||
throw new z.errors.Error(
|
||||
`query: ${query}, error: ${JSON.stringify(results.errors)}`,
|
||||
'ApiError',
|
||||
response.status
|
||||
response.status,
|
||||
);
|
||||
}
|
||||
response.throwForStatus();
|
||||
@ -47,9 +46,32 @@ const requestDb = async (z: ZObject, bundle: Bundle, query: string) => {
|
||||
throw new z.errors.Error(
|
||||
`query: ${query}, error: ${err.message}`,
|
||||
'Error',
|
||||
err.status
|
||||
err.status,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const requestDbViaRestApi = (
|
||||
z: ZObject,
|
||||
bundle: Bundle,
|
||||
objectNamePlural: string,
|
||||
) => {
|
||||
const options = {
|
||||
url: `${process.env.SERVER_BASE_URL}/rest/${objectNamePlural}?limit:3`,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${bundle.authData.apiKey}`,
|
||||
},
|
||||
} satisfies HttpRequestOptions;
|
||||
|
||||
return z
|
||||
.request(options)
|
||||
.then((response) => response.json.data[objectNamePlural])
|
||||
.catch((err) => {
|
||||
throw new z.errors.Error(`Error: ${err.message}`, 'Error', err.status);
|
||||
});
|
||||
};
|
||||
|
||||
export default requestDb;
|
||||
|
||||
64
packages/twenty-zapier/src/utils/triggers.utils.ts
Normal file
64
packages/twenty-zapier/src/utils/triggers.utils.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { Bundle, ZObject } from 'zapier-platform-core';
|
||||
import requestDb, { requestDbViaRestApi } from '../utils/requestDb';
|
||||
import handleQueryParams from '../utils/handleQueryParams';
|
||||
|
||||
export enum Operation {
|
||||
create = 'create',
|
||||
update = 'update',
|
||||
delete = 'delete',
|
||||
}
|
||||
|
||||
export const subscribe = async (
|
||||
z: ZObject,
|
||||
bundle: Bundle,
|
||||
operation: Operation,
|
||||
) => {
|
||||
const data = {
|
||||
targetUrl: bundle.targetUrl,
|
||||
operation: `${operation}.${bundle.inputData.namePlural}`,
|
||||
};
|
||||
const result = await requestDb(
|
||||
z,
|
||||
bundle,
|
||||
`mutation createWebhook {createWebhook(data:{${handleQueryParams(
|
||||
data,
|
||||
)}}) {id}}`,
|
||||
);
|
||||
return result.data.createWebhook;
|
||||
};
|
||||
|
||||
export const performUnsubscribe = async (z: ZObject, bundle: Bundle) => {
|
||||
const data = { id: bundle.subscribeData?.id };
|
||||
const result = await requestDb(
|
||||
z,
|
||||
bundle,
|
||||
`mutation deleteWebhook {deleteWebhook(${handleQueryParams(data)}) {id}}`,
|
||||
);
|
||||
return result.data.deleteWebhook;
|
||||
};
|
||||
|
||||
export const perform = (z: ZObject, bundle: Bundle) => {
|
||||
return [bundle.cleanedRequest];
|
||||
};
|
||||
|
||||
export const listSample = async (
|
||||
z: ZObject,
|
||||
bundle: Bundle,
|
||||
onlyIds = false,
|
||||
) => {
|
||||
const result: { [key: string]: string }[] = await requestDbViaRestApi(
|
||||
z,
|
||||
bundle,
|
||||
bundle.inputData.namePlural,
|
||||
);
|
||||
|
||||
if (onlyIds) {
|
||||
return result.map((res) => {
|
||||
return {
|
||||
id: res.id,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
Reference in New Issue
Block a user