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

@ -0,0 +1,18 @@
import { OnEvent } from '@nestjs/event-emitter';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
export function OnDatabaseEvent(
object: string,
action: DatabaseEventAction,
): MethodDecorator {
const event = `${object}.${action}`;
return (
target: object,
propertyKey: string,
descriptor: PropertyDescriptor,
) => {
OnEvent(event)(target, propertyKey, descriptor);
};
}

View File

@ -0,0 +1,6 @@
export enum DatabaseEventAction {
CREATED = 'created',
UPDATED = 'updated',
DELETED = 'deleted',
DESTROYED = 'destroyed',
}

View File

@ -31,15 +31,7 @@ import { GraphqlQueryResolverFactory } from 'src/engine/api/graphql/graphql-quer
import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service';
import { QueryResultGettersFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/query-result-getters.factory';
import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory';
import {
CallWebhookJobsJob,
CallWebhookJobsJobData,
CallWebhookJobsJobOperation,
} from 'src/engine/api/graphql/workspace-query-runner/jobs/call-webhook-jobs.job';
import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service';
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
import { LogExecutionTime } from 'src/engine/decorators/observability/log-execution-time.decorator';
import { capitalize } from 'src/utils/capitalize';
@ -49,8 +41,6 @@ export class GraphqlQueryRunnerService {
private readonly workspaceQueryHookService: WorkspaceQueryHookService,
private readonly queryRunnerArgsFactory: QueryRunnerArgsFactory,
private readonly queryResultGettersFactory: QueryResultGettersFactory,
@InjectMessageQueue(MessageQueue.webhookQueue)
private readonly messageQueueService: MessageQueueService,
private readonly graphqlQueryResolverFactory: GraphqlQueryResolverFactory,
private readonly apiEventEmitterService: ApiEventEmitterService,
) {}
@ -312,7 +302,7 @@ export class GraphqlQueryRunnerService {
args: RestoreManyResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<ObjectRecord> {
const result = await this.executeQuery<
return await this.executeQuery<
UpdateManyResolverArgs<Partial<ObjectRecord>>,
ObjectRecord
>(
@ -323,8 +313,6 @@ export class GraphqlQueryRunnerService {
},
options,
);
return result;
}
private async executeQuery<Input extends ResolverArgs, Response>(
@ -372,54 +360,6 @@ export class GraphqlQueryRunnerService {
resultWithGettersArray,
);
const jobOperation = this.operationNameToJobOperation(operationName);
if (jobOperation) {
await this.triggerWebhooks(resultWithGettersArray, jobOperation, options);
}
return resultWithGetters;
}
private operationNameToJobOperation(
operationName: WorkspaceResolverBuilderMethodNames,
): CallWebhookJobsJobOperation | undefined {
switch (operationName) {
case 'createOne':
case 'createMany':
return CallWebhookJobsJobOperation.create;
case 'updateOne':
case 'updateMany':
case 'restoreMany':
return CallWebhookJobsJobOperation.update;
case 'deleteOne':
case 'deleteMany':
return CallWebhookJobsJobOperation.delete;
case 'destroyOne':
return CallWebhookJobsJobOperation.destroy;
default:
return undefined;
}
}
private async triggerWebhooks<T>(
jobsData: T[] | undefined,
operation: CallWebhookJobsJobOperation,
options: WorkspaceQueryRunnerOptions,
): Promise<void> {
if (!jobsData || !Array.isArray(jobsData)) return;
jobsData.forEach((jobData) => {
this.messageQueueService.add<CallWebhookJobsJobData>(
CallWebhookJobsJob.name,
{
record: jobData,
workspaceId: options.authContext.workspace.id,
operation,
objectMetadataItem: options.objectMetadataItem,
},
{ retryLimit: 3 },
);
});
}
}

View File

@ -5,6 +5,8 @@ import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metad
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-values';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
@Injectable()
export class ApiEventEmitterService {
@ -16,7 +18,7 @@ export class ApiEventEmitterService {
objectMetadataItem: ObjectMetadataInterface,
): void {
this.workspaceEventEmitter.emit(
`${objectMetadataItem.nameSingular}.created`,
`${objectMetadataItem.nameSingular}.${DatabaseEventAction.CREATED}`,
records.map((record) => ({
userId: authContext.user?.id,
recordId: record.id,
@ -46,20 +48,28 @@ export class ApiEventEmitterService {
);
this.workspaceEventEmitter.emit(
`${objectMetadataItem.nameSingular}.updated`,
`${objectMetadataItem.nameSingular}.${DatabaseEventAction.UPDATED}`,
records.map((record) => {
const before = this.removeGraphQLAndNestedProperties(
mappedExistingRecords[record.id],
);
const after = this.removeGraphQLAndNestedProperties(record);
const diff = objectRecordChangedValues(
before,
after,
updatedFields,
objectMetadataItem,
);
return {
userId: authContext.user?.id,
recordId: record.id,
objectMetadata: objectMetadataItem,
properties: {
before: mappedExistingRecords[record.id]
? this.removeGraphQLAndNestedProperties(
mappedExistingRecords[record.id],
)
: undefined,
after: this.removeGraphQLAndNestedProperties(record),
before,
after,
updatedFields,
diff,
},
};
}),
@ -73,7 +83,7 @@ export class ApiEventEmitterService {
objectMetadataItem: ObjectMetadataInterface,
): void {
this.workspaceEventEmitter.emit(
`${objectMetadataItem.nameSingular}.deleted`,
`${objectMetadataItem.nameSingular}.${DatabaseEventAction.DELETED}`,
records.map((record) => {
return {
userId: authContext.user?.id,
@ -95,7 +105,7 @@ export class ApiEventEmitterService {
objectMetadataItem: ObjectMetadataInterface,
): void {
this.workspaceEventEmitter.emit(
`${objectMetadataItem.nameSingular}.destroyed`,
`${objectMetadataItem.nameSingular}.${DatabaseEventAction.DESTROYED}`,
records.map((record) => {
return {
userId: authContext.user?.id,

View File

@ -0,0 +1,9 @@
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
export const checkStringIsDatabaseEventAction = (
value: string,
): value is DatabaseEventAction => {
return Object.values(DatabaseEventAction).includes(
value as DatabaseEventAction,
);
};