6071 return only updated fields of records in zapier update trigger (#8193)

- move webhook triggers into `entity-events-to-db.listener.ts`
- refactor event management
- add a `@OnDatabaseEvent` decorator to manage database events
- add updatedFields in updated events
- update openApi webhooks docs
- update zapier integration
This commit is contained in:
martmull
2024-11-04 17:44:36 +01:00
committed by GitHub
parent 741020fbb0
commit 695991881f
62 changed files with 547 additions and 578 deletions

View File

@ -7,7 +7,7 @@ import { computeInputFields } from '../utils/computeInputFields';
import { InputData } from '../utils/data.types';
import handleQueryParams from '../utils/handleQueryParams';
import requestDb, { requestSchema } from '../utils/requestDb';
import { Operation } from '../utils/triggers/triggers.utils';
import { DatabaseEventAction } from '../utils/triggers/triggers.utils';
export const recordInputFields = async (
z: ZObject,
@ -24,7 +24,7 @@ export const recordInputFields = async (
const computeFields = async (z: ZObject, bundle: Bundle) => {
const operation = bundle.inputData.crudZapierOperation;
switch (operation) {
case Operation.delete:
case DatabaseEventAction.DELETED:
return [
{
key: 'id',
@ -34,9 +34,9 @@ const computeFields = async (z: ZObject, bundle: Bundle) => {
required: true,
},
];
case Operation.update:
case DatabaseEventAction.UPDATED:
return recordInputFields(z, bundle, true);
case Operation.create:
case DatabaseEventAction.CREATED:
return recordInputFields(z, bundle, false);
default:
return [];
@ -44,18 +44,18 @@ const computeFields = async (z: ZObject, bundle: Bundle) => {
};
const computeQueryParameters = (
operation: Operation,
operation: DatabaseEventAction,
data: InputData,
): string => {
switch (operation) {
case Operation.create:
case DatabaseEventAction.CREATED:
return `data:{${handleQueryParams(data)}}`;
case Operation.update:
case DatabaseEventAction.UPDATED:
return `
data:{${handleQueryParams(data)}},
id: "${data.id}"
`;
case Operation.delete:
case DatabaseEventAction.DELETED:
return `
id: "${data.id}"
`;
@ -104,9 +104,9 @@ export default {
required: true,
label: 'Operation',
choices: {
[Operation.create]: Operation.create,
[Operation.update]: Operation.update,
[Operation.delete]: Operation.delete,
[DatabaseEventAction.CREATED]: DatabaseEventAction.CREATED,
[DatabaseEventAction.UPDATED]: DatabaseEventAction.UPDATED,
[DatabaseEventAction.DELETED]: DatabaseEventAction.DELETED,
},
altersDynamicFields: true,
},

View File

@ -4,7 +4,7 @@ import { crudRecordKey } from '../../creates/crud_record';
import App from '../../index';
import getBundle from '../../utils/getBundle';
import requestDb from '../../utils/requestDb';
import { Operation } from '../../utils/triggers/triggers.utils';
import { DatabaseEventAction } from '../../utils/triggers/triggers.utils';
const appTester = createAppTester(App);
tools.env.inject();
@ -12,7 +12,7 @@ describe('creates.create_company', () => {
test('should run to create a Company Record', async () => {
const bundle = getBundle({
nameSingular: 'Company',
crudZapierOperation: Operation.create,
crudZapierOperation: DatabaseEventAction.CREATED,
name: 'Company Name',
address: { addressCity: 'Paris' },
linkedinLink: {
@ -56,7 +56,7 @@ describe('creates.create_company', () => {
test('should run to create a Person Record', async () => {
const bundle = getBundle({
nameSingular: 'Person',
crudZapierOperation: Operation.create,
crudZapierOperation: DatabaseEventAction.CREATED,
name: { firstName: 'John', lastName: 'Doe' },
phones: {
primaryPhoneNumber: '610203040',
@ -90,7 +90,7 @@ describe('creates.update_company', () => {
test('should run to update a Company record', async () => {
const createBundle = getBundle({
nameSingular: 'Company',
crudZapierOperation: Operation.create,
crudZapierOperation: DatabaseEventAction.CREATED,
name: 'Company Name',
employees: 25,
});
@ -104,7 +104,7 @@ describe('creates.update_company', () => {
const updateBundle = getBundle({
nameSingular: 'Company',
crudZapierOperation: Operation.update,
crudZapierOperation: DatabaseEventAction.UPDATED,
id: companyId,
name: 'Updated Company Name',
});
@ -133,7 +133,7 @@ describe('creates.delete_company', () => {
test('should run to delete a Company record', async () => {
const createBundle = getBundle({
nameSingular: 'Company',
crudZapierOperation: Operation.create,
crudZapierOperation: DatabaseEventAction.CREATED,
name: 'Delete Company Name',
employees: 25,
});
@ -147,7 +147,7 @@ describe('creates.delete_company', () => {
const deleteBundle = getBundle({
nameSingular: 'Company',
crudZapierOperation: Operation.delete,
crudZapierOperation: DatabaseEventAction.DELETED,
id: companyId,
});

View File

@ -4,13 +4,14 @@ import App from '../../index';
import { triggerRecordKey } from '../../triggers/trigger_record';
import getBundle from '../../utils/getBundle';
import requestDb from '../../utils/requestDb';
import { DatabaseEventAction } from '../../utils/triggers/triggers.utils';
const appTester = createAppTester(App);
describe('triggers.trigger_record.created', () => {
test('should succeed to subscribe', async () => {
const bundle = getBundle({});
bundle.inputData.nameSingular = 'company';
bundle.inputData.operation = 'create';
bundle.inputData.operation = DatabaseEventAction.CREATED;
bundle.targetUrl = 'https://test.com';
const result = await appTester(
App.triggers[triggerRecordKey].operation.performSubscribe,

View File

@ -1,20 +1,21 @@
import { Bundle, ZObject } from 'zapier-platform-core';
import { findObjectNamesSingularKey } from '../triggers/find_object_names_singular';
import {
listSample,
Operation,
perform,
performSubscribe,
performUnsubscribe,
subscribe,
perform,
performList,
DatabaseEventAction,
} from '../utils/triggers/triggers.utils';
export const triggerRecordKey = 'trigger_record';
const performSubscribe = (z: ZObject, bundle: Bundle) =>
subscribe(z, bundle, bundle.inputData.operation);
const performList = (z: ZObject, bundle: Bundle) =>
listSample(z, bundle, bundle.inputData.operation === Operation.delete);
const choices = Object.values(DatabaseEventAction).reduce(
(acc, action) => {
acc[action] = action;
return acc;
},
{} as Record<DatabaseEventAction, DatabaseEventAction>,
);
export default {
key: triggerRecordKey,
@ -37,12 +38,7 @@ export default {
key: 'operation',
required: true,
label: 'Operation',
choices: {
[Operation.create]: Operation.create,
[Operation.update]: Operation.update,
[Operation.delete]: Operation.delete,
[Operation.destroy]: Operation.destroy,
},
choices,
altersDynamicFields: true,
},
],

View File

@ -79,7 +79,7 @@ export const requestDbViaRestApi = (
z: ZObject,
bundle: Bundle,
objectNamePlural: string,
) => {
): Promise<Record<string, any>[]> => {
const options = {
url: `${
bundle.authData.apiUrl || process.env.SERVER_BASE_URL

View File

@ -7,48 +7,28 @@ import requestDb, {
requestSchema,
} from '../../utils/requestDb';
export enum Operation {
create = 'create',
update = 'update',
delete = 'delete',
destroy = 'destroy',
export enum DatabaseEventAction {
CREATED = 'created',
UPDATED = 'updated',
DELETED = 'deleted',
DESTROYED = 'destroyed',
}
export const subscribe = async (
z: ZObject,
bundle: Bundle,
operation: Operation,
) => {
try {
const data = {
targetUrl: bundle.targetUrl,
operations: [`${bundle.inputData.nameSingular}.${operation}`],
};
const result = await requestDb(
z,
bundle,
`mutation createWebhook {createWebhook(data:{${handleQueryParams(
data,
)}}) {id}}`,
);
return result.data.createWebhook;
} catch (e) {
// Remove that catch code when VERSION 0.32 is deployed
// probably removable after 01/11/2024
// (ie: when operations column exists in all active workspace schemas)
const data = {
targetUrl: bundle.targetUrl,
operation: `${bundle.inputData.nameSingular}.${operation}`,
};
const result = await requestDb(
z,
bundle,
`mutation createWebhook {createWebhook(data:{${handleQueryParams(
data,
)}}) {id}}`,
);
return result.data.createWebhook;
}
export const performSubscribe = async (z: ZObject, bundle: Bundle) => {
const data = {
targetUrl: bundle.targetUrl,
operations: [
`${bundle.inputData.nameSingular}.${bundle.inputData.operation}`,
],
};
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) => {
@ -62,20 +42,26 @@ export const performUnsubscribe = async (z: ZObject, bundle: Bundle) => {
};
export const perform = (z: ZObject, bundle: Bundle) => {
const record = bundle.cleanedRequest.record;
if (record.createdAt) {
record.createdAt = record.createdAt + 'Z';
const data = {
record: bundle.cleanedRequest.record,
...(bundle.cleanedRequest.updatedFields && {
updatedFields: bundle.cleanedRequest.updatedFields,
}),
};
if (data.record.createdAt) {
data.record.createdAt = data.record.createdAt + 'Z';
}
if (record.updatedAt) {
record.updatedAt = record.updatedAt + 'Z';
if (data.record.updatedAt) {
data.record.updatedAt = data.record.updatedAt + 'Z';
}
if (record.revokedAt) {
record.revokedAt = record.revokedAt + 'Z';
if (data.record.revokedAt) {
data.record.revokedAt = data.record.revokedAt + 'Z';
}
if (record.expiresAt) {
record.expiresAt = record.expiresAt + 'Z';
if (data.record.expiresAt) {
data.record.expiresAt = data.record.expiresAt + 'Z';
}
return [record];
return [data];
};
const getNamePluralFromNameSingular = async (
@ -92,10 +78,9 @@ const getNamePluralFromNameSingular = async (
throw new Error(`Unknown Object Name Singular ${nameSingular}`);
};
export const listSample = async (
export const performList = async (
z: ZObject,
bundle: Bundle,
onlyIds = false,
): Promise<ObjectData[]> => {
const nameSingular = bundle.inputData.nameSingular;
const namePlural = await getNamePluralFromNameSingular(
@ -103,19 +88,13 @@ export const listSample = async (
bundle,
nameSingular,
);
const result: { [key: string]: string }[] = await requestDbViaRestApi(
z,
bundle,
namePlural,
);
if (onlyIds) {
return result.map((res) => {
return {
id: res.id,
};
});
}
return result;
const results = await requestDbViaRestApi(z, bundle, namePlural);
return results.map((result) => ({
record: result,
...(bundle.inputData.operation === DatabaseEventAction.UPDATED && {
updatedFields: Object.keys(result).filter((key) => key !== 'id')?.[0] || [
'updatedField',
],
}),
}));
};