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,14 +1,34 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { ModuleRef } from '@nestjs/core';
|
import { ModuleRef } from '@nestjs/core';
|
||||||
|
import { HttpModule } from '@nestjs/axios';
|
||||||
|
|
||||||
import { FetchMessagesJob } from 'src/workspace/messaging/jobs/fetch-messages.job';
|
import { FetchMessagesJob } from 'src/workspace/messaging/jobs/fetch-messages.job';
|
||||||
|
import { CallWebhookJobsJob } from 'src/workspace/workspace-query-runner/jobs/call-webhook-jobs.job';
|
||||||
|
import { CallWebhookJob } from 'src/workspace/workspace-query-runner/jobs/call-webhook.job';
|
||||||
|
import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module';
|
||||||
|
import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metadata.module';
|
||||||
|
import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
imports: [
|
||||||
|
WorkspaceDataSourceModule,
|
||||||
|
ObjectMetadataModule,
|
||||||
|
DataSourceModule,
|
||||||
|
HttpModule,
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
provide: FetchMessagesJob.name,
|
provide: FetchMessagesJob.name,
|
||||||
useClass: FetchMessagesJob,
|
useClass: FetchMessagesJob,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: CallWebhookJobsJob.name,
|
||||||
|
useClass: CallWebhookJobsJob,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: CallWebhookJob.name,
|
||||||
|
useClass: CallWebhookJob,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class JobsModule {
|
export class JobsModule {
|
||||||
|
|||||||
@ -3,4 +3,5 @@ export const QUEUE_DRIVER = Symbol('QUEUE_DRIVER');
|
|||||||
export enum MessageQueue {
|
export enum MessageQueue {
|
||||||
taskAssignedQueue = 'task-assigned-queue',
|
taskAssignedQueue = 'task-assigned-queue',
|
||||||
messagingQueue = 'messaging-queue',
|
messagingQueue = 'messaging-queue',
|
||||||
|
webhookQueue = 'webhook-queue',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,7 +55,11 @@ export class MessageQueueModule {
|
|||||||
module: MessageQueueModule,
|
module: MessageQueueModule,
|
||||||
imports: [JobsModule, ...(options.imports || [])],
|
imports: [JobsModule, ...(options.imports || [])],
|
||||||
providers,
|
providers,
|
||||||
exports: [MessageQueue.taskAssignedQueue, MessageQueue.messagingQueue],
|
exports: [
|
||||||
|
MessageQueue.taskAssignedQueue,
|
||||||
|
MessageQueue.messagingQueue,
|
||||||
|
MessageQueue.webhookQueue,
|
||||||
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -326,6 +326,24 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async findOneOrFailWithinWorkspace(
|
||||||
|
workspaceId: string,
|
||||||
|
options: FindOneOptions<ObjectMetadataEntity>,
|
||||||
|
): Promise<ObjectMetadataEntity> {
|
||||||
|
return this.objectMetadataRepository.findOneOrFail({
|
||||||
|
relations: [
|
||||||
|
'fields',
|
||||||
|
'fields.fromRelationMetadata',
|
||||||
|
'fields.toRelationMetadata',
|
||||||
|
],
|
||||||
|
...options,
|
||||||
|
where: {
|
||||||
|
...options.where,
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public async findManyWithinWorkspace(
|
public async findManyWithinWorkspace(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
options?: FindManyOptions<ObjectMetadataEntity>,
|
options?: FindManyOptions<ObjectMetadataEntity>,
|
||||||
|
|||||||
@ -0,0 +1,79 @@
|
|||||||
|
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { MessageQueueJob } from 'src/integrations/message-queue/interfaces/message-queue-job.interface';
|
||||||
|
|
||||||
|
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
|
||||||
|
import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metadata.service';
|
||||||
|
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
|
||||||
|
import { MessageQueueService } from 'src/integrations/message-queue/services/message-queue.service';
|
||||||
|
import { MessageQueue } from 'src/integrations/message-queue/message-queue.constants';
|
||||||
|
import {
|
||||||
|
CallWebhookJob,
|
||||||
|
CallWebhookJobData,
|
||||||
|
} from 'src/workspace/workspace-query-runner/jobs/call-webhook.job';
|
||||||
|
|
||||||
|
export enum CallWebhookJobsJobOperation {
|
||||||
|
create = 'create',
|
||||||
|
update = 'update',
|
||||||
|
delete = 'delete',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CallWebhookJobsJobData = {
|
||||||
|
workspaceId: string;
|
||||||
|
objectNameSingular: string;
|
||||||
|
recordData: any;
|
||||||
|
operation: CallWebhookJobsJobOperation;
|
||||||
|
};
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CallWebhookJobsJob
|
||||||
|
implements MessageQueueJob<CallWebhookJobsJobData>
|
||||||
|
{
|
||||||
|
private readonly logger = new Logger(CallWebhookJobsJob.name);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||||
|
private readonly objectMetadataService: ObjectMetadataService,
|
||||||
|
private readonly dataSourceService: DataSourceService,
|
||||||
|
@Inject(MessageQueue.webhookQueue)
|
||||||
|
private readonly messageQueueService: MessageQueueService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async handle(data: CallWebhookJobsJobData): Promise<void> {
|
||||||
|
const objectMetadataItem =
|
||||||
|
await this.objectMetadataService.findOneOrFailWithinWorkspace(
|
||||||
|
data.workspaceId,
|
||||||
|
{ where: { nameSingular: data.objectNameSingular } },
|
||||||
|
);
|
||||||
|
const dataSourceMetadata =
|
||||||
|
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||||
|
data.workspaceId,
|
||||||
|
);
|
||||||
|
const workspaceDataSource =
|
||||||
|
await this.workspaceDataSourceService.connectToWorkspaceDataSource(
|
||||||
|
data.workspaceId,
|
||||||
|
);
|
||||||
|
const operationName = `${data.operation}.${objectMetadataItem.namePlural}`;
|
||||||
|
const webhooks: { id: string; targetUrl: string }[] =
|
||||||
|
await workspaceDataSource?.query(
|
||||||
|
`SELECT * FROM ${dataSourceMetadata.schema}."webhook" WHERE operation='${operationName}'`,
|
||||||
|
);
|
||||||
|
|
||||||
|
webhooks.forEach((webhook) => {
|
||||||
|
this.messageQueueService.add<CallWebhookJobData>(
|
||||||
|
CallWebhookJob.name,
|
||||||
|
{
|
||||||
|
recordData: data.recordData,
|
||||||
|
targetUrl: webhook.targetUrl,
|
||||||
|
},
|
||||||
|
{ retryLimit: 3 },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.log(
|
||||||
|
`CallWebhookJobsJob on operation '${operationName}' called on webhooks ids [\n"${webhooks
|
||||||
|
.map((webhook) => webhook.id)
|
||||||
|
.join('",\n"')}"\n]`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { HttpService } from '@nestjs/axios';
|
||||||
|
|
||||||
|
import { MessageQueueJob } from 'src/integrations/message-queue/interfaces/message-queue-job.interface';
|
||||||
|
|
||||||
|
export type CallWebhookJobData = {
|
||||||
|
targetUrl: string;
|
||||||
|
recordData: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CallWebhookJob implements MessageQueueJob<CallWebhookJobData> {
|
||||||
|
private readonly logger = new Logger(CallWebhookJob.name);
|
||||||
|
|
||||||
|
constructor(private readonly httpService: HttpService) {}
|
||||||
|
|
||||||
|
async handle(data: CallWebhookJobData): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.httpService.axiosRef.post(data.targetUrl, data.recordData);
|
||||||
|
this.logger.log(
|
||||||
|
`CallWebhookJob successfully called on targetUrl '${
|
||||||
|
data.targetUrl
|
||||||
|
}' with data: ${JSON.stringify(data.recordData)}`,
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(
|
||||||
|
`Error calling webhook on targetUrl '${data.targetUrl}': ${err}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
|
Inject,
|
||||||
Injectable,
|
Injectable,
|
||||||
InternalServerErrorException,
|
InternalServerErrorException,
|
||||||
Logger,
|
Logger,
|
||||||
@ -24,6 +25,13 @@ import {
|
|||||||
|
|
||||||
import { WorkspaceQueryBuilderFactory } from 'src/workspace/workspace-query-builder/workspace-query-builder.factory';
|
import { WorkspaceQueryBuilderFactory } from 'src/workspace/workspace-query-builder/workspace-query-builder.factory';
|
||||||
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
|
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
|
||||||
|
import { MessageQueueService } from 'src/integrations/message-queue/services/message-queue.service';
|
||||||
|
import { MessageQueue } from 'src/integrations/message-queue/message-queue.constants';
|
||||||
|
import {
|
||||||
|
CallWebhookJobsJob,
|
||||||
|
CallWebhookJobsJobData,
|
||||||
|
CallWebhookJobsJobOperation,
|
||||||
|
} from 'src/workspace/workspace-query-runner/jobs/call-webhook-jobs.job';
|
||||||
import { parseResult } from 'src/workspace/workspace-query-runner/utils/parse-result.util';
|
import { parseResult } from 'src/workspace/workspace-query-runner/utils/parse-result.util';
|
||||||
import { ExceptionHandlerService } from 'src/integrations/exception-handler/exception-handler.service';
|
import { ExceptionHandlerService } from 'src/integrations/exception-handler/exception-handler.service';
|
||||||
import { globalExceptionHandler } from 'src/filters/utils/global-exception-handler.util';
|
import { globalExceptionHandler } from 'src/filters/utils/global-exception-handler.util';
|
||||||
@ -41,6 +49,8 @@ export class WorkspaceQueryRunnerService {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly workspaceQueryBuilderFactory: WorkspaceQueryBuilderFactory,
|
private readonly workspaceQueryBuilderFactory: WorkspaceQueryBuilderFactory,
|
||||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||||
|
@Inject(MessageQueue.webhookQueue)
|
||||||
|
private readonly messageQueueService: MessageQueueService,
|
||||||
private readonly exceptionHandlerService: ExceptionHandlerService,
|
private readonly exceptionHandlerService: ExceptionHandlerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -117,11 +127,19 @@ export class WorkspaceQueryRunnerService {
|
|||||||
);
|
);
|
||||||
const result = await this.execute(query, workspaceId);
|
const result = await this.execute(query, workspaceId);
|
||||||
|
|
||||||
return this.parseResult<PGGraphQLMutation<Record>>(
|
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
|
||||||
result,
|
result,
|
||||||
targetTableName,
|
targetTableName,
|
||||||
'insertInto',
|
'insertInto',
|
||||||
)?.records;
|
)?.records;
|
||||||
|
|
||||||
|
await this.triggerWebhooks<Record>(
|
||||||
|
parsedResults,
|
||||||
|
CallWebhookJobsJobOperation.create,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
return parsedResults;
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
const error = globalExceptionHandler(
|
const error = globalExceptionHandler(
|
||||||
exception,
|
exception,
|
||||||
@ -136,9 +154,15 @@ export class WorkspaceQueryRunnerService {
|
|||||||
args: CreateOneResolverArgs<Record>,
|
args: CreateOneResolverArgs<Record>,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
): Promise<Record | undefined> {
|
): Promise<Record | undefined> {
|
||||||
const records = await this.createMany({ data: [args.data] }, options);
|
const results = await this.createMany({ data: [args.data] }, options);
|
||||||
|
|
||||||
return records?.[0];
|
await this.triggerWebhooks<Record>(
|
||||||
|
results,
|
||||||
|
CallWebhookJobsJobOperation.create,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
return results?.[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateOne<Record extends IRecord = IRecord>(
|
async updateOne<Record extends IRecord = IRecord>(
|
||||||
@ -153,11 +177,19 @@ export class WorkspaceQueryRunnerService {
|
|||||||
);
|
);
|
||||||
const result = await this.execute(query, workspaceId);
|
const result = await this.execute(query, workspaceId);
|
||||||
|
|
||||||
return this.parseResult<PGGraphQLMutation<Record>>(
|
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
|
||||||
result,
|
result,
|
||||||
targetTableName,
|
targetTableName,
|
||||||
'update',
|
'update',
|
||||||
)?.records?.[0];
|
)?.records;
|
||||||
|
|
||||||
|
await this.triggerWebhooks<Record>(
|
||||||
|
parsedResults,
|
||||||
|
CallWebhookJobsJobOperation.update,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
return parsedResults?.[0];
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
const error = globalExceptionHandler(
|
const error = globalExceptionHandler(
|
||||||
exception,
|
exception,
|
||||||
@ -180,11 +212,19 @@ export class WorkspaceQueryRunnerService {
|
|||||||
);
|
);
|
||||||
const result = await this.execute(query, workspaceId);
|
const result = await this.execute(query, workspaceId);
|
||||||
|
|
||||||
return this.parseResult<PGGraphQLMutation<Record>>(
|
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
|
||||||
result,
|
result,
|
||||||
targetTableName,
|
targetTableName,
|
||||||
'deleteFrom',
|
'deleteFrom',
|
||||||
)?.records?.[0];
|
)?.records;
|
||||||
|
|
||||||
|
await this.triggerWebhooks<Record>(
|
||||||
|
parsedResults,
|
||||||
|
CallWebhookJobsJobOperation.delete,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
return parsedResults?.[0];
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
const error = globalExceptionHandler(
|
const error = globalExceptionHandler(
|
||||||
exception,
|
exception,
|
||||||
@ -207,11 +247,19 @@ export class WorkspaceQueryRunnerService {
|
|||||||
);
|
);
|
||||||
const result = await this.execute(query, workspaceId);
|
const result = await this.execute(query, workspaceId);
|
||||||
|
|
||||||
return this.parseResult<PGGraphQLMutation<Record>>(
|
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
|
||||||
result,
|
result,
|
||||||
targetTableName,
|
targetTableName,
|
||||||
'update',
|
'update',
|
||||||
)?.records;
|
)?.records;
|
||||||
|
|
||||||
|
await this.triggerWebhooks<Record>(
|
||||||
|
parsedResults,
|
||||||
|
CallWebhookJobsJobOperation.update,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
return parsedResults;
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
const error = globalExceptionHandler(
|
const error = globalExceptionHandler(
|
||||||
exception,
|
exception,
|
||||||
@ -237,11 +285,19 @@ export class WorkspaceQueryRunnerService {
|
|||||||
);
|
);
|
||||||
const result = await this.execute(query, workspaceId);
|
const result = await this.execute(query, workspaceId);
|
||||||
|
|
||||||
return this.parseResult<PGGraphQLMutation<Record>>(
|
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
|
||||||
result,
|
result,
|
||||||
targetTableName,
|
targetTableName,
|
||||||
'deleteFrom',
|
'deleteFrom',
|
||||||
)?.records;
|
)?.records;
|
||||||
|
|
||||||
|
await this.triggerWebhooks<Record>(
|
||||||
|
parsedResults,
|
||||||
|
CallWebhookJobsJobOperation.delete,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
return parsedResults;
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
const error = globalExceptionHandler(
|
const error = globalExceptionHandler(
|
||||||
exception,
|
exception,
|
||||||
@ -306,4 +362,26 @@ export class WorkspaceQueryRunnerService {
|
|||||||
|
|
||||||
return this.parseResult(result, targetTableName, command);
|
return this.parseResult(result, targetTableName, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async triggerWebhooks<Record>(
|
||||||
|
jobsData: Record[] | undefined,
|
||||||
|
operation: CallWebhookJobsJobOperation,
|
||||||
|
options: WorkspaceQueryRunnerOptions,
|
||||||
|
) {
|
||||||
|
if (!Array.isArray(jobsData)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
jobsData.forEach((jobData) => {
|
||||||
|
this.messageQueueService.add<CallWebhookJobsJobData>(
|
||||||
|
CallWebhookJobsJob.name,
|
||||||
|
{
|
||||||
|
recordData: jobData,
|
||||||
|
workspaceId: options.workspaceId,
|
||||||
|
operation,
|
||||||
|
objectNameSingular: options.targetTableName,
|
||||||
|
},
|
||||||
|
{ retryLimit: 3 },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,21 @@
|
|||||||
import { Bundle, ZObject } from "zapier-platform-core";
|
import { Bundle, ZObject } from 'zapier-platform-core';
|
||||||
import requestDb, { requestSchema } from "../utils/requestDb";
|
import requestDb, { requestSchema } from '../utils/requestDb';
|
||||||
import handleQueryParams from "../utils/handleQueryParams";
|
import handleQueryParams from '../utils/handleQueryParams';
|
||||||
import { capitalize } from "../utils/capitalize";
|
import { capitalize } from '../utils/capitalize';
|
||||||
import { computeInputFields } from "../utils/computeInputFields";
|
import { computeInputFields } from '../utils/computeInputFields';
|
||||||
|
import { findObjectNamesSingularKey } from '../triggers/find_object_names_singular';
|
||||||
|
|
||||||
const recordInputFields = async (z: ZObject, bundle: Bundle) => {
|
const recordInputFields = async (z: ZObject, bundle: Bundle) => {
|
||||||
const schema = await requestSchema(z, bundle)
|
const schema = await requestSchema(z, bundle);
|
||||||
const infos = schema.components.schemas[bundle.inputData.nameSingular]
|
const infos = schema.components.schemas[bundle.inputData.nameSingular];
|
||||||
|
|
||||||
return computeInputFields(infos);
|
return computeInputFields(infos);
|
||||||
}
|
};
|
||||||
|
|
||||||
const perform = async (z: ZObject, bundle: Bundle) => {
|
const perform = async (z: ZObject, bundle: Bundle) => {
|
||||||
const data = bundle.inputData
|
const data = bundle.inputData;
|
||||||
const nameSingular = data.nameSingular
|
const nameSingular = data.nameSingular;
|
||||||
delete data.nameSingular
|
delete data.nameSingular;
|
||||||
const query = `
|
const query = `
|
||||||
mutation create${capitalize(nameSingular)} {
|
mutation create${capitalize(nameSingular)} {
|
||||||
create${capitalize(nameSingular)}(
|
create${capitalize(nameSingular)}(
|
||||||
@ -25,28 +26,30 @@ const perform = async (z: ZObject, bundle: Bundle) => {
|
|||||||
return await requestDb(z, bundle, query);
|
return await requestDb(z, bundle, query);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createRecordKey = 'create_record';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
display: {
|
display: {
|
||||||
description: 'Creates a new Record in Twenty',
|
description: 'Creates a new Record in Twenty',
|
||||||
hidden: false,
|
hidden: false,
|
||||||
label: 'Create New Record',
|
label: 'Create New Record',
|
||||||
},
|
},
|
||||||
key: 'create_record',
|
key: createRecordKey,
|
||||||
noun: 'Record',
|
noun: 'Record',
|
||||||
operation: {
|
operation: {
|
||||||
inputFields: [
|
inputFields: [
|
||||||
{
|
{
|
||||||
key: 'nameSingular',
|
key: 'nameSingular',
|
||||||
required: true,
|
required: true,
|
||||||
label: 'Name of the Record to create',
|
label: 'Record Name',
|
||||||
dynamic: 'find_objects.nameSingular',
|
dynamic: `${findObjectNamesSingularKey}.nameSingular`,
|
||||||
altersDynamicFields: true,
|
altersDynamicFields: true,
|
||||||
},
|
},
|
||||||
recordInputFields
|
recordInputFields,
|
||||||
],
|
],
|
||||||
sample: {
|
sample: {
|
||||||
id: '179ed459-79cf-41d9-ab85-96397fa8e936',
|
id: '179ed459-79cf-41d9-ab85-96397fa8e936',
|
||||||
},
|
},
|
||||||
perform
|
perform,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
@ -1,7 +1,22 @@
|
|||||||
|
import findObjectNamesPlural, {
|
||||||
|
findObjectNamesPluralKey,
|
||||||
|
} from './triggers/find_object_names_plural';
|
||||||
|
|
||||||
const { version } = require('../package.json');
|
const { version } = require('../package.json');
|
||||||
import { version as platformVersion } from 'zapier-platform-core';
|
import { version as platformVersion } from 'zapier-platform-core';
|
||||||
import createRecord from './creates/create_record';
|
import createRecord, { createRecordKey } from './creates/create_record';
|
||||||
import findObjects from './triggers/find_objects'
|
import findObjectNamesSingular, {
|
||||||
|
findObjectNamesSingularKey,
|
||||||
|
} from './triggers/find_object_names_singular';
|
||||||
|
import triggerRecordCreated, {
|
||||||
|
triggerRecordCreatedKey,
|
||||||
|
} from './triggers/trigger_record_created';
|
||||||
|
import triggerRecordDeleted, {
|
||||||
|
triggerRecordDeletedKey,
|
||||||
|
} from './triggers/trigger_record_deleted';
|
||||||
|
import triggerRecordUpdated, {
|
||||||
|
triggerRecordUpdatedKey,
|
||||||
|
} from './triggers/trigger_record_updated';
|
||||||
import authentication from './authentication';
|
import authentication from './authentication';
|
||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
|
|
||||||
@ -10,9 +25,13 @@ export default {
|
|||||||
platformVersion,
|
platformVersion,
|
||||||
authentication: authentication,
|
authentication: authentication,
|
||||||
triggers: {
|
triggers: {
|
||||||
[findObjects.key]: findObjects,
|
[findObjectNamesSingularKey]: findObjectNamesSingular,
|
||||||
|
[findObjectNamesPluralKey]: findObjectNamesPlural,
|
||||||
|
[triggerRecordCreatedKey]: triggerRecordCreated,
|
||||||
|
[triggerRecordUpdatedKey]: triggerRecordUpdated,
|
||||||
|
[triggerRecordDeletedKey]: triggerRecordDeleted,
|
||||||
},
|
},
|
||||||
creates: {
|
creates: {
|
||||||
[createRecord.key]: createRecord,
|
[createRecordKey]: createRecord,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,11 +1,5 @@
|
|||||||
import App from '../index';
|
import App from '../index';
|
||||||
import {
|
import { Bundle, createAppTester, tools, ZObject } from 'zapier-platform-core';
|
||||||
Bundle,
|
|
||||||
HttpRequestOptions,
|
|
||||||
createAppTester,
|
|
||||||
tools,
|
|
||||||
ZObject,
|
|
||||||
} from 'zapier-platform-core';
|
|
||||||
import getBundle from '../utils/getBundle';
|
import getBundle from '../utils/getBundle';
|
||||||
import handleQueryParams from '../utils/handleQueryParams';
|
import handleQueryParams from '../utils/handleQueryParams';
|
||||||
import requestDb from '../utils/requestDb';
|
import requestDb from '../utils/requestDb';
|
||||||
@ -43,19 +37,20 @@ describe('custom auth', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('fails on bad auth token format', async () => {
|
it('fails on bad auth token format', async () => {
|
||||||
const bundle = { authData: { apiKey: 'bad' } };
|
const bundle = getBundle();
|
||||||
|
bundle.authData.apiKey = 'bad';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await appTester(App.authentication.test, bundle);
|
await appTester(App.authentication.test, bundle);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
expect(error.message).toContain('UNAUTHENTICATED');
|
expect(error.message).toContain('Unauthorized');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw new Error('appTester should have thrown');
|
throw new Error('appTester should have thrown');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fails on invalid auth token', async () => {
|
it('fails on invalid auth token', async () => {
|
||||||
const expiresAt = '2020-01-01 10:10:10.000'
|
const expiresAt = '2020-01-01 10:10:10.000';
|
||||||
const apiKeyBundle = getBundle({
|
const apiKeyBundle = getBundle({
|
||||||
name: 'Test',
|
name: 'Test',
|
||||||
expiresAt,
|
expiresAt,
|
||||||
@ -65,15 +60,17 @@ describe('custom auth', () => {
|
|||||||
apiKeyId: apiKeyId,
|
apiKeyId: apiKeyId,
|
||||||
expiresAt,
|
expiresAt,
|
||||||
});
|
});
|
||||||
const expiredToken = await appTester(generateApiKeyToken, generateTokenBundle);
|
const expiredToken = await appTester(
|
||||||
const bundleWithExpiredApiKey = {
|
generateApiKeyToken,
|
||||||
authData: { apiKey: expiredToken },
|
generateTokenBundle,
|
||||||
};
|
);
|
||||||
|
const bundleWithExpiredApiKey = getBundle({});
|
||||||
|
bundleWithExpiredApiKey.authData.apiKey = expiredToken;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await appTester(App.authentication.test, bundleWithExpiredApiKey);
|
await appTester(App.authentication.test, bundleWithExpiredApiKey);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
expect(error.message).toContain('UNAUTHENTICATED');
|
expect(error.message).toContain('Unauthorized');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw new Error('appTester should have thrown');
|
throw new Error('appTester should have thrown');
|
||||||
|
|||||||
@ -1,25 +1,29 @@
|
|||||||
import App from '../../index';
|
import App from '../../index';
|
||||||
import getBundle from "../../utils/getBundle";
|
import getBundle from '../../utils/getBundle';
|
||||||
import { Bundle, createAppTester, tools, ZObject } from "zapier-platform-core";
|
import { Bundle, createAppTester, tools, ZObject } from 'zapier-platform-core';
|
||||||
import requestDb from "../../utils/requestDb";
|
import requestDb from '../../utils/requestDb';
|
||||||
|
import { createRecordKey } from '../../creates/create_record';
|
||||||
const appTester = createAppTester(App);
|
const appTester = createAppTester(App);
|
||||||
tools.env.inject;
|
tools.env.inject();
|
||||||
|
|
||||||
describe('creates.create_record', () => {
|
describe('creates.[createRecordKey]', () => {
|
||||||
test('should run to create a Company Record', async () => {
|
test('should run to create a Company Record', async () => {
|
||||||
const bundle = getBundle({
|
const bundle = getBundle({
|
||||||
nameSingular: 'Company',
|
nameSingular: 'Company',
|
||||||
name: 'Company Name',
|
name: 'Company Name',
|
||||||
address: 'Company Address',
|
address: 'Company Address',
|
||||||
domainName: 'Company Domain Name',
|
domainName: 'Company Domain Name',
|
||||||
linkedinLink: {url: '/linkedin_url', label: "Test linkedinUrl"},
|
linkedinLink: { url: '/linkedin_url', label: 'Test linkedinUrl' },
|
||||||
xLink: {url: '/x_url', label: "Test xUrl"},
|
xLink: { url: '/x_url', label: 'Test xUrl' },
|
||||||
annualRecurringRevenue: {amountMicros:100000000000,currencyCode: 'USD'},
|
annualRecurringRevenue: {
|
||||||
|
amountMicros: 100000000000,
|
||||||
|
currencyCode: 'USD',
|
||||||
|
},
|
||||||
idealCustomerProfile: true,
|
idealCustomerProfile: true,
|
||||||
employees: 25,
|
employees: 25,
|
||||||
});
|
});
|
||||||
const result = await appTester(
|
const result = await appTester(
|
||||||
App.creates.create_record.operation.perform,
|
App.creates[createRecordKey].operation.perform,
|
||||||
bundle,
|
bundle,
|
||||||
);
|
);
|
||||||
expect(result).toBeDefined();
|
expect(result).toBeDefined();
|
||||||
@ -33,20 +37,20 @@ describe('creates.create_record', () => {
|
|||||||
),
|
),
|
||||||
bundle,
|
bundle,
|
||||||
);
|
);
|
||||||
expect(checkDbResult.data.company.annualRecurringRevenue.amountMicros).toEqual(
|
expect(
|
||||||
100000000000,
|
checkDbResult.data.company.annualRecurringRevenue.amountMicros,
|
||||||
);
|
).toEqual(100000000000);
|
||||||
})
|
});
|
||||||
test('should run to create a Person Record', async () => {
|
test('should run to create a Person Record', async () => {
|
||||||
const bundle = getBundle({
|
const bundle = getBundle({
|
||||||
nameSingular: 'Person',
|
nameSingular: 'Person',
|
||||||
name: {firstName: 'John', lastName: 'Doe'},
|
name: { firstName: 'John', lastName: 'Doe' },
|
||||||
email: 'johndoe@gmail.com',
|
email: 'johndoe@gmail.com',
|
||||||
phone: '+33610203040',
|
phone: '+33610203040',
|
||||||
city: 'Paris',
|
city: 'Paris',
|
||||||
});
|
});
|
||||||
const result = await appTester(
|
const result = await appTester(
|
||||||
App.creates.create_record.operation.perform,
|
App.creates[createRecordKey].operation.perform,
|
||||||
bundle,
|
bundle,
|
||||||
);
|
);
|
||||||
expect(result).toBeDefined();
|
expect(result).toBeDefined();
|
||||||
@ -61,5 +65,5 @@ describe('creates.create_record', () => {
|
|||||||
bundle,
|
bundle,
|
||||||
);
|
);
|
||||||
expect(checkDbResult.data.person.phone).toEqual('+33610203040');
|
expect(checkDbResult.data.person.phone).toEqual('+33610203040');
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
import { createAppTester, tools } from 'zapier-platform-core';
|
||||||
|
import getBundle from '../../utils/getBundle';
|
||||||
|
import App from '../../index';
|
||||||
|
import { findObjectNamesPluralKey } from '../../triggers/find_object_names_plural';
|
||||||
|
tools.env.inject();
|
||||||
|
|
||||||
|
const appTester = createAppTester(App);
|
||||||
|
describe('triggers.find_object_names_plural', () => {
|
||||||
|
test('should run', async () => {
|
||||||
|
const bundle = getBundle({});
|
||||||
|
const result = await appTester(
|
||||||
|
App.triggers[findObjectNamesPluralKey].operation.perform,
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result.length).toBeGreaterThan(1);
|
||||||
|
expect(result[0].namePlural).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import { createAppTester, tools } from 'zapier-platform-core';
|
||||||
|
import getBundle from '../../utils/getBundle';
|
||||||
|
import App from '../../index';
|
||||||
|
import { findObjectNamesSingularKey } from '../../triggers/find_object_names_singular';
|
||||||
|
tools.env.inject();
|
||||||
|
|
||||||
|
const appTester = createAppTester(App);
|
||||||
|
describe('triggers.find_object_names_singular', () => {
|
||||||
|
test('should run', async () => {
|
||||||
|
const bundle = getBundle({});
|
||||||
|
const result = await appTester(
|
||||||
|
App.triggers[findObjectNamesSingularKey].operation.perform,
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result.length).toBeGreaterThan(1);
|
||||||
|
expect(result[0].nameSingular).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,17 +0,0 @@
|
|||||||
import { createAppTester } from "zapier-platform-core";
|
|
||||||
import getBundle from '../../utils/getBundle';
|
|
||||||
import App from '../../index';
|
|
||||||
|
|
||||||
const appTester = createAppTester(App);
|
|
||||||
describe('triggers.find_objects', () => {
|
|
||||||
test('should run', async () => {
|
|
||||||
const bundle = getBundle({});
|
|
||||||
const result = await appTester(
|
|
||||||
App.triggers.find_objects.operation.perform,
|
|
||||||
bundle,
|
|
||||||
);
|
|
||||||
expect(result).toBeDefined();
|
|
||||||
expect(result.length).toBeGreaterThan(1)
|
|
||||||
expect(result[0].nameSingular).toBeDefined()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -0,0 +1,93 @@
|
|||||||
|
import { Bundle, createAppTester, ZObject } from 'zapier-platform-core';
|
||||||
|
import App from '../../index';
|
||||||
|
import getBundle from '../../utils/getBundle';
|
||||||
|
import requestDb from '../../utils/requestDb';
|
||||||
|
import { triggerRecordCreatedKey } from '../../triggers/trigger_record_created';
|
||||||
|
const appTester = createAppTester(App);
|
||||||
|
|
||||||
|
describe('triggers.trigger_record_created', () => {
|
||||||
|
test('should succeed to subscribe', async () => {
|
||||||
|
const bundle = getBundle({});
|
||||||
|
bundle.inputData.namePlural = 'companies';
|
||||||
|
bundle.targetUrl = 'https://test.com';
|
||||||
|
const result = await appTester(
|
||||||
|
App.triggers[triggerRecordCreatedKey].operation.performSubscribe,
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result.id).toBeDefined();
|
||||||
|
const checkDbResult = await appTester(
|
||||||
|
(z: ZObject, bundle: Bundle) =>
|
||||||
|
requestDb(
|
||||||
|
z,
|
||||||
|
bundle,
|
||||||
|
`query webhook {webhooks(filter: {id: {eq: "${result.id}"}}){edges {node {id operation}}}}`,
|
||||||
|
),
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(checkDbResult.data.webhooks.edges[0].node.operation).toEqual(
|
||||||
|
'create.companies',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
test('should succeed to unsubscribe', async () => {
|
||||||
|
const bundle = getBundle({});
|
||||||
|
bundle.inputData.namePlural = 'companies';
|
||||||
|
bundle.targetUrl = 'https://test.com';
|
||||||
|
const result = await appTester(
|
||||||
|
App.triggers[triggerRecordCreatedKey].operation.performSubscribe,
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
const unsubscribeBundle = getBundle({});
|
||||||
|
unsubscribeBundle.subscribeData = { id: result.id };
|
||||||
|
const unsubscribeResult = await appTester(
|
||||||
|
App.triggers[triggerRecordCreatedKey].operation.performUnsubscribe,
|
||||||
|
unsubscribeBundle,
|
||||||
|
);
|
||||||
|
expect(unsubscribeResult).toBeDefined();
|
||||||
|
expect(unsubscribeResult.id).toEqual(result.id);
|
||||||
|
const checkDbResult = await appTester(
|
||||||
|
(z: ZObject, bundle: Bundle) =>
|
||||||
|
requestDb(
|
||||||
|
z,
|
||||||
|
bundle,
|
||||||
|
`query webhook {webhooks(filter: {id: {eq: "${result.id}"}}){edges {node {id operation}}}}`,
|
||||||
|
),
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(checkDbResult.data.webhooks.edges.length).toEqual(0);
|
||||||
|
});
|
||||||
|
test('should load company from webhook', async () => {
|
||||||
|
const bundle = {
|
||||||
|
cleanedRequest: {
|
||||||
|
id: 'd6ccb1d1-a90b-4822-a992-a0dd946592c9',
|
||||||
|
name: '',
|
||||||
|
domainName: '',
|
||||||
|
createdAt: '2023-10-19 10:10:12.490',
|
||||||
|
address: '',
|
||||||
|
employees: null,
|
||||||
|
linkedinUrl: null,
|
||||||
|
xUrl: null,
|
||||||
|
annualRecurringRevenue: null,
|
||||||
|
idealCustomerProfile: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const results = await appTester(
|
||||||
|
App.triggers[triggerRecordCreatedKey].operation.perform,
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(results.length).toEqual(1);
|
||||||
|
const company = results[0];
|
||||||
|
expect(company.id).toEqual('d6ccb1d1-a90b-4822-a992-a0dd946592c9');
|
||||||
|
});
|
||||||
|
it('should load companies from list', async () => {
|
||||||
|
const bundle = getBundle({});
|
||||||
|
bundle.inputData.namePlural = 'companies';
|
||||||
|
const results = await appTester(
|
||||||
|
App.triggers[triggerRecordCreatedKey].operation.performList,
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(results.length).toBeGreaterThan(1);
|
||||||
|
const firstCompany = results[0];
|
||||||
|
expect(firstCompany).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,95 @@
|
|||||||
|
import { Bundle, createAppTester, ZObject } from 'zapier-platform-core';
|
||||||
|
import App from '../../index';
|
||||||
|
import getBundle from '../../utils/getBundle';
|
||||||
|
import requestDb from '../../utils/requestDb';
|
||||||
|
import { triggerRecordDeletedKey } from '../../triggers/trigger_record_deleted';
|
||||||
|
const appTester = createAppTester(App);
|
||||||
|
|
||||||
|
describe('triggers.trigger_record_deleted', () => {
|
||||||
|
test('should succeed to subscribe', async () => {
|
||||||
|
const bundle = getBundle({});
|
||||||
|
bundle.inputData.namePlural = 'companies';
|
||||||
|
bundle.targetUrl = 'https://test.com';
|
||||||
|
const result = await appTester(
|
||||||
|
App.triggers[triggerRecordDeletedKey].operation.performSubscribe,
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result.id).toBeDefined();
|
||||||
|
const checkDbResult = await appTester(
|
||||||
|
(z: ZObject, bundle: Bundle) =>
|
||||||
|
requestDb(
|
||||||
|
z,
|
||||||
|
bundle,
|
||||||
|
`query webhook {webhooks(filter: {id: {eq: "${result.id}"}}){edges {node {id operation}}}}`,
|
||||||
|
),
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(checkDbResult.data.webhooks.edges[0].node.operation).toEqual(
|
||||||
|
'delete.companies',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
test('should succeed to unsubscribe', async () => {
|
||||||
|
const bundle = getBundle({});
|
||||||
|
bundle.inputData.namePlural = 'companies';
|
||||||
|
bundle.targetUrl = 'https://test.com';
|
||||||
|
const result = await appTester(
|
||||||
|
App.triggers[triggerRecordDeletedKey].operation.performSubscribe,
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
const unsubscribeBundle = getBundle({});
|
||||||
|
unsubscribeBundle.subscribeData = { id: result.id };
|
||||||
|
const unsubscribeResult = await appTester(
|
||||||
|
App.triggers[triggerRecordDeletedKey].operation.performUnsubscribe,
|
||||||
|
unsubscribeBundle,
|
||||||
|
);
|
||||||
|
expect(unsubscribeResult).toBeDefined();
|
||||||
|
expect(unsubscribeResult.id).toEqual(result.id);
|
||||||
|
const checkDbResult = await appTester(
|
||||||
|
(z: ZObject, bundle: Bundle) =>
|
||||||
|
requestDb(
|
||||||
|
z,
|
||||||
|
bundle,
|
||||||
|
`query webhook {webhooks(filter: {id: {eq: "${result.id}"}}){edges {node {id operation}}}}`,
|
||||||
|
),
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(checkDbResult.data.webhooks.edges.length).toEqual(0);
|
||||||
|
});
|
||||||
|
test('should load company from webhook', async () => {
|
||||||
|
const bundle = {
|
||||||
|
cleanedRequest: {
|
||||||
|
id: 'd6ccb1d1-a90b-4822-a992-a0dd946592c9',
|
||||||
|
name: '',
|
||||||
|
domainName: '',
|
||||||
|
createdAt: '2023-10-19 10:10:12.490',
|
||||||
|
address: '',
|
||||||
|
employees: null,
|
||||||
|
linkedinUrl: null,
|
||||||
|
xUrl: null,
|
||||||
|
annualRecurringRevenue: null,
|
||||||
|
idealCustomerProfile: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const results = await appTester(
|
||||||
|
App.triggers[triggerRecordDeletedKey].operation.perform,
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(results.length).toEqual(1);
|
||||||
|
const company = results[0];
|
||||||
|
expect(company.id).toEqual('d6ccb1d1-a90b-4822-a992-a0dd946592c9');
|
||||||
|
});
|
||||||
|
it('should load companies from list', async () => {
|
||||||
|
const bundle = getBundle({});
|
||||||
|
bundle.inputData.namePlural = 'companies';
|
||||||
|
const results = await appTester(
|
||||||
|
App.triggers[triggerRecordDeletedKey].operation.performList,
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(results.length).toBeGreaterThan(1);
|
||||||
|
const firstCompany = results[0];
|
||||||
|
expect(firstCompany).toBeDefined();
|
||||||
|
expect(firstCompany.id).toBeDefined();
|
||||||
|
expect(Object.keys(firstCompany).length).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,93 @@
|
|||||||
|
import { Bundle, createAppTester, ZObject } from 'zapier-platform-core';
|
||||||
|
import App from '../../index';
|
||||||
|
import getBundle from '../../utils/getBundle';
|
||||||
|
import requestDb from '../../utils/requestDb';
|
||||||
|
import { triggerRecordUpdatedKey } from '../../triggers/trigger_record_updated';
|
||||||
|
const appTester = createAppTester(App);
|
||||||
|
|
||||||
|
describe('triggers.trigger_record_updated', () => {
|
||||||
|
test('should succeed to subscribe', async () => {
|
||||||
|
const bundle = getBundle({});
|
||||||
|
bundle.inputData.namePlural = 'companies';
|
||||||
|
bundle.targetUrl = 'https://test.com';
|
||||||
|
const result = await appTester(
|
||||||
|
App.triggers[triggerRecordUpdatedKey].operation.performSubscribe,
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result.id).toBeDefined();
|
||||||
|
const checkDbResult = await appTester(
|
||||||
|
(z: ZObject, bundle: Bundle) =>
|
||||||
|
requestDb(
|
||||||
|
z,
|
||||||
|
bundle,
|
||||||
|
`query webhook {webhooks(filter: {id: {eq: "${result.id}"}}){edges {node {id operation}}}}`,
|
||||||
|
),
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(checkDbResult.data.webhooks.edges[0].node.operation).toEqual(
|
||||||
|
'update.companies',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
test('should succeed to unsubscribe', async () => {
|
||||||
|
const bundle = getBundle({});
|
||||||
|
bundle.inputData.namePlural = 'companies';
|
||||||
|
bundle.targetUrl = 'https://test.com';
|
||||||
|
const result = await appTester(
|
||||||
|
App.triggers[triggerRecordUpdatedKey].operation.performSubscribe,
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
const unsubscribeBundle = getBundle({});
|
||||||
|
unsubscribeBundle.subscribeData = { id: result.id };
|
||||||
|
const unsubscribeResult = await appTester(
|
||||||
|
App.triggers[triggerRecordUpdatedKey].operation.performUnsubscribe,
|
||||||
|
unsubscribeBundle,
|
||||||
|
);
|
||||||
|
expect(unsubscribeResult).toBeDefined();
|
||||||
|
expect(unsubscribeResult.id).toEqual(result.id);
|
||||||
|
const checkDbResult = await appTester(
|
||||||
|
(z: ZObject, bundle: Bundle) =>
|
||||||
|
requestDb(
|
||||||
|
z,
|
||||||
|
bundle,
|
||||||
|
`query webhook {webhooks(filter: {id: {eq: "${result.id}"}}){edges {node {id operation}}}}`,
|
||||||
|
),
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(checkDbResult.data.webhooks.edges.length).toEqual(0);
|
||||||
|
});
|
||||||
|
test('should load company from webhook', async () => {
|
||||||
|
const bundle = {
|
||||||
|
cleanedRequest: {
|
||||||
|
id: 'd6ccb1d1-a90b-4822-a992-a0dd946592c9',
|
||||||
|
name: '',
|
||||||
|
domainName: '',
|
||||||
|
createdAt: '2023-10-19 10:10:12.490',
|
||||||
|
address: '',
|
||||||
|
employees: null,
|
||||||
|
linkedinUrl: null,
|
||||||
|
xUrl: null,
|
||||||
|
annualRecurringRevenue: null,
|
||||||
|
idealCustomerProfile: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const results = await appTester(
|
||||||
|
App.triggers[triggerRecordUpdatedKey].operation.perform,
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(results.length).toEqual(1);
|
||||||
|
const company = results[0];
|
||||||
|
expect(company.id).toEqual('d6ccb1d1-a90b-4822-a992-a0dd946592c9');
|
||||||
|
});
|
||||||
|
it('should load companies from list', async () => {
|
||||||
|
const bundle = getBundle({});
|
||||||
|
bundle.inputData.namePlural = 'companies';
|
||||||
|
const results = await appTester(
|
||||||
|
App.triggers[triggerRecordUpdatedKey].operation.performList,
|
||||||
|
bundle,
|
||||||
|
);
|
||||||
|
expect(results.length).toBeGreaterThan(1);
|
||||||
|
const firstCompany = results[0];
|
||||||
|
expect(firstCompany).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { capitalize } from "../../utils/capitalize";
|
|
||||||
|
|
||||||
describe('capitalize', ()=> {
|
|
||||||
test('should capitalize properly', ()=> {
|
|
||||||
expect(capitalize('word')).toEqual('Word')
|
|
||||||
expect(capitalize('word word')).toEqual('Word word')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
8
packages/twenty-zapier/src/test/utils/capitalize.test.ts
Normal file
8
packages/twenty-zapier/src/test/utils/capitalize.test.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { capitalize } from '../../utils/capitalize';
|
||||||
|
|
||||||
|
describe('capitalize', () => {
|
||||||
|
test('should capitalize properly', () => {
|
||||||
|
expect(capitalize('word')).toEqual('Word');
|
||||||
|
expect(capitalize('word word')).toEqual('Word word');
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,42 +0,0 @@
|
|||||||
import { computeInputFields } from "../../utils/computeInputFields";
|
|
||||||
|
|
||||||
describe('computeInputFields', ()=> {
|
|
||||||
test('should create Person input fields properly', ()=> {
|
|
||||||
const personInfos = {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
email: {
|
|
||||||
type: "string"
|
|
||||||
},
|
|
||||||
xLink: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
url: {
|
|
||||||
type: "string"
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
type: "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
avatarUrl: {
|
|
||||||
type: "string"
|
|
||||||
},
|
|
||||||
favorites: {
|
|
||||||
type: "array",
|
|
||||||
items: {
|
|
||||||
$ref: "#/components/schemas/Favorite"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
example: {},
|
|
||||||
required: ['avatarUrl']
|
|
||||||
}
|
|
||||||
expect(computeInputFields(personInfos)).toEqual([
|
|
||||||
{ key: "email", label: "Email", required: false, type: "string" },
|
|
||||||
{ key: "xLink__url", label: "X Link: Url", required: false, type: "string" },
|
|
||||||
{ key: "xLink__label", label: "X Link: Label", required: false, type: "string" },
|
|
||||||
{ key: "avatarUrl", label: "Avatar Url", required: true, type: "string" },
|
|
||||||
])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
import { computeInputFields } from '../../utils/computeInputFields';
|
||||||
|
|
||||||
|
describe('computeInputFields', () => {
|
||||||
|
test('should create Person input fields properly', () => {
|
||||||
|
const personInfos = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
email: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
xLink: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
url: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
avatarUrl: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
favorites: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
$ref: '#/components/schemas/Favorite',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
example: {},
|
||||||
|
required: ['avatarUrl'],
|
||||||
|
};
|
||||||
|
expect(computeInputFields(personInfos)).toEqual([
|
||||||
|
{ key: 'email', label: 'Email', required: false, type: 'string' },
|
||||||
|
{
|
||||||
|
key: 'xLink__url',
|
||||||
|
label: 'X Link: Url',
|
||||||
|
required: false,
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'xLink__label',
|
||||||
|
label: 'X Link: Label',
|
||||||
|
required: false,
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{ key: 'avatarUrl', label: 'Avatar Url', required: true, type: 'string' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -7,15 +7,15 @@ describe('utils.handleQueryParams', () => {
|
|||||||
const expectedResult = '';
|
const expectedResult = '';
|
||||||
expect(result).toEqual(expectedResult);
|
expect(result).toEqual(expectedResult);
|
||||||
});
|
});
|
||||||
test('should format', async () => {
|
test('should format', () => {
|
||||||
const inputData = {
|
const inputData = {
|
||||||
name: 'Company Name',
|
name: 'Company Name',
|
||||||
address: 'Company Address',
|
address: 'Company Address',
|
||||||
domainName: 'Company Domain Name',
|
domainName: 'Company Domain Name',
|
||||||
linkedinUrl__url: '/linkedin_url',
|
linkedinUrl__url: '/linkedin_url',
|
||||||
linkedinUrl__label: "Test linkedinUrl",
|
linkedinUrl__label: 'Test linkedinUrl',
|
||||||
xUrl__url: '/x_url',
|
xUrl__url: '/x_url',
|
||||||
xUrl__label: "Test xUrl",
|
xUrl__label: 'Test xUrl',
|
||||||
annualRecurringRevenue: 100000,
|
annualRecurringRevenue: 100000,
|
||||||
idealCustomerProfile: true,
|
idealCustomerProfile: true,
|
||||||
employees: 25,
|
employees: 25,
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
import { labelling } from "../../utils/labelling";
|
|
||||||
|
|
||||||
describe('labelling', ()=> {
|
|
||||||
test('should label properly', ()=> {
|
|
||||||
expect(labelling('createdAt')).toEqual('Created At')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
7
packages/twenty-zapier/src/test/utils/labelize.test.ts
Normal file
7
packages/twenty-zapier/src/test/utils/labelize.test.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { labelling } from '../../utils/labelling';
|
||||||
|
|
||||||
|
describe('labelling', () => {
|
||||||
|
test('should label properly', () => {
|
||||||
|
expect(labelling('createdAt')).toEqual('Created At');
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
import { Bundle, ZObject } from 'zapier-platform-core';
|
||||||
|
import { requestSchema } from '../utils/requestDb';
|
||||||
|
|
||||||
|
const objectNamesPluralListRequest = async (z: ZObject, bundle: Bundle) => {
|
||||||
|
const schema = await requestSchema(z, bundle);
|
||||||
|
const tags: { name: string }[] = schema.tags;
|
||||||
|
return Object.values(tags)
|
||||||
|
.filter((tag) => tag.name !== 'General')
|
||||||
|
.map((tag) => {
|
||||||
|
return { id: tag.name, namePlural: tag.name };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const findObjectNamesPluralKey = 'find_object_names_plural';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
display: {
|
||||||
|
description: 'Find objects',
|
||||||
|
label: 'Find objects',
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
key: findObjectNamesPluralKey,
|
||||||
|
noun: 'Object',
|
||||||
|
operation: {
|
||||||
|
perform: objectNamesPluralListRequest,
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
import { Bundle, ZObject } from 'zapier-platform-core';
|
||||||
|
import { requestSchema } from '../utils/requestDb';
|
||||||
|
|
||||||
|
const objectListRequest = async (z: ZObject, bundle: Bundle) => {
|
||||||
|
const schema = await requestSchema(z, bundle);
|
||||||
|
return Object.keys(schema.components.schemas).map((schema) => {
|
||||||
|
return { id: schema, nameSingular: schema };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const findObjectNamesSingularKey = 'find_object_names_singular';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
display: {
|
||||||
|
description: 'Find objects',
|
||||||
|
label: 'Find objects',
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
key: findObjectNamesSingularKey,
|
||||||
|
noun: 'Object',
|
||||||
|
operation: {
|
||||||
|
perform: objectListRequest,
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -1,22 +0,0 @@
|
|||||||
import { Bundle, ZObject } from "zapier-platform-core";
|
|
||||||
import { requestSchema } from "../utils/requestDb";
|
|
||||||
|
|
||||||
const objectListRequest = async (z: ZObject, bundle: Bundle) => {
|
|
||||||
const schema = await requestSchema(z, bundle)
|
|
||||||
return Object.keys(schema.components.schemas).map((schema)=> {
|
|
||||||
return {id: schema, nameSingular: schema}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
display: {
|
|
||||||
description: 'Find objects',
|
|
||||||
label: 'Find objects',
|
|
||||||
hidden: true,
|
|
||||||
},
|
|
||||||
key: 'find_objects',
|
|
||||||
noun: 'Object',
|
|
||||||
operation: {
|
|
||||||
perform: objectListRequest,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
import { findObjectNamesPluralKey } from '../triggers/find_object_names_plural';
|
||||||
|
import {
|
||||||
|
listSample,
|
||||||
|
Operation,
|
||||||
|
perform,
|
||||||
|
performUnsubscribe,
|
||||||
|
subscribe,
|
||||||
|
} from '../utils/triggers.utils';
|
||||||
|
import { Bundle, ZObject } from 'zapier-platform-core';
|
||||||
|
|
||||||
|
export const triggerRecordCreatedKey = 'trigger_record_created';
|
||||||
|
|
||||||
|
const performSubscribe = (z: ZObject, bundle: Bundle) =>
|
||||||
|
subscribe(z, bundle, Operation.create);
|
||||||
|
const performList = (z: ZObject, bundle: Bundle) => listSample(z, bundle);
|
||||||
|
|
||||||
|
export default {
|
||||||
|
key: triggerRecordCreatedKey,
|
||||||
|
noun: 'Record',
|
||||||
|
display: {
|
||||||
|
label: 'Record Trigger Created',
|
||||||
|
description: 'Triggers when a Record is created.',
|
||||||
|
},
|
||||||
|
operation: {
|
||||||
|
inputFields: [
|
||||||
|
{
|
||||||
|
key: 'namePlural',
|
||||||
|
required: true,
|
||||||
|
label: 'Record Name',
|
||||||
|
dynamic: `${findObjectNamesPluralKey}.namePlural`,
|
||||||
|
altersDynamicFields: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: 'hook',
|
||||||
|
performSubscribe,
|
||||||
|
performUnsubscribe,
|
||||||
|
perform,
|
||||||
|
performList,
|
||||||
|
sample: {
|
||||||
|
id: 'f75f6b2e-9442-4c72-aa95-47d8e5ec8cb3',
|
||||||
|
createdAt: '2023-10-19T07:37:25.306Z',
|
||||||
|
workspaceId: 'c8b070fc-c969-4ca5-837a-e7c3735734d2',
|
||||||
|
},
|
||||||
|
outputFields: [
|
||||||
|
{ key: 'id', label: 'ID' },
|
||||||
|
{ key: 'createdAt', label: 'Created At' },
|
||||||
|
{ key: 'workspaceId', label: 'Workspace ID' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
import { findObjectNamesPluralKey } from '../triggers/find_object_names_plural';
|
||||||
|
import {
|
||||||
|
perform,
|
||||||
|
listSample,
|
||||||
|
subscribe,
|
||||||
|
performUnsubscribe,
|
||||||
|
Operation,
|
||||||
|
} from '../utils/triggers.utils';
|
||||||
|
import { Bundle, ZObject } from 'zapier-platform-core';
|
||||||
|
|
||||||
|
export const triggerRecordDeletedKey = 'trigger_record_deleted';
|
||||||
|
|
||||||
|
const performSubscribe = (z: ZObject, bundle: Bundle) =>
|
||||||
|
subscribe(z, bundle, Operation.delete);
|
||||||
|
const performList = (z: ZObject, bundle: Bundle) => listSample(z, bundle, true);
|
||||||
|
|
||||||
|
export default {
|
||||||
|
key: triggerRecordDeletedKey,
|
||||||
|
noun: 'Record',
|
||||||
|
display: {
|
||||||
|
label: 'Record Trigger Deleted',
|
||||||
|
description: 'Triggers when a Record is deleted.',
|
||||||
|
},
|
||||||
|
operation: {
|
||||||
|
inputFields: [
|
||||||
|
{
|
||||||
|
key: 'namePlural',
|
||||||
|
required: true,
|
||||||
|
label: 'Record Name',
|
||||||
|
dynamic: `${findObjectNamesPluralKey}.namePlural`,
|
||||||
|
altersDynamicFields: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: 'hook',
|
||||||
|
performSubscribe,
|
||||||
|
performUnsubscribe,
|
||||||
|
perform,
|
||||||
|
performList,
|
||||||
|
sample: {
|
||||||
|
id: 'f75f6b2e-9442-4c72-aa95-47d8e5ec8cb3',
|
||||||
|
},
|
||||||
|
outputFields: [{ key: 'id', label: 'ID' }],
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
import { findObjectNamesPluralKey } from '../triggers/find_object_names_plural';
|
||||||
|
import {
|
||||||
|
listSample,
|
||||||
|
Operation,
|
||||||
|
perform,
|
||||||
|
performUnsubscribe,
|
||||||
|
subscribe,
|
||||||
|
} from '../utils/triggers.utils';
|
||||||
|
import { Bundle, ZObject } from 'zapier-platform-core';
|
||||||
|
|
||||||
|
export const triggerRecordUpdatedKey = 'trigger_record_updated';
|
||||||
|
|
||||||
|
const performSubscribe = (z: ZObject, bundle: Bundle) =>
|
||||||
|
subscribe(z, bundle, Operation.update);
|
||||||
|
const performList = (z: ZObject, bundle: Bundle) => listSample(z, bundle);
|
||||||
|
|
||||||
|
export default {
|
||||||
|
key: triggerRecordUpdatedKey,
|
||||||
|
noun: 'Record',
|
||||||
|
display: {
|
||||||
|
label: 'Record Trigger Updated',
|
||||||
|
description: 'Triggers when a Record is updated.',
|
||||||
|
},
|
||||||
|
operation: {
|
||||||
|
inputFields: [
|
||||||
|
{
|
||||||
|
key: 'namePlural',
|
||||||
|
required: true,
|
||||||
|
label: 'Record Name',
|
||||||
|
dynamic: `${findObjectNamesPluralKey}.namePlural`,
|
||||||
|
altersDynamicFields: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: 'hook',
|
||||||
|
performSubscribe,
|
||||||
|
performUnsubscribe,
|
||||||
|
perform,
|
||||||
|
performList,
|
||||||
|
sample: {
|
||||||
|
id: 'f75f6b2e-9442-4c72-aa95-47d8e5ec8cb3',
|
||||||
|
createdAt: '2023-10-19T07:37:25.306Z',
|
||||||
|
workspaceId: 'c8b070fc-c969-4ca5-837a-e7c3735734d2',
|
||||||
|
},
|
||||||
|
outputFields: [
|
||||||
|
{ key: 'id', label: 'ID' },
|
||||||
|
{ key: 'createdAt', label: 'Created At' },
|
||||||
|
{ key: 'workspaceId', label: 'Workspace ID' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -1,3 +1,3 @@
|
|||||||
export const capitalize = (word: string): string => {
|
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 = {
|
type Infos = {
|
||||||
properties: {
|
properties: {
|
||||||
[field: string]: {
|
[field: string]: {
|
||||||
type: string;
|
type: string;
|
||||||
properties?: { [field: string]: { type: string } }
|
properties?: { [field: string]: { type: string } };
|
||||||
items?: { [$ref: string]: string }
|
items?: { [$ref: string]: string };
|
||||||
}
|
};
|
||||||
},
|
};
|
||||||
example: object,
|
example: object;
|
||||||
required: string[]
|
required: string[];
|
||||||
}
|
};
|
||||||
|
|
||||||
export const computeInputFields = (infos: Infos): object[] => {
|
export const computeInputFields = (infos: Infos): object[] => {
|
||||||
const result = []
|
const result = [];
|
||||||
|
|
||||||
for (const fieldName of Object.keys(infos.properties)) {
|
for (const fieldName of Object.keys(infos.properties)) {
|
||||||
switch (infos.properties[fieldName].type) {
|
switch (infos.properties[fieldName].type) {
|
||||||
@ -23,17 +23,19 @@ export const computeInputFields = (infos: Infos): object[] => {
|
|||||||
if (!infos.properties[fieldName].properties) {
|
if (!infos.properties[fieldName].properties) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
for (const subFieldName of Object.keys(infos.properties[fieldName].properties || {})) {
|
for (const subFieldName of Object.keys(
|
||||||
|
infos.properties[fieldName].properties || {},
|
||||||
|
)) {
|
||||||
const field = {
|
const field = {
|
||||||
key: `${fieldName}__${subFieldName}`,
|
key: `${fieldName}__${subFieldName}`,
|
||||||
label: `${labelling(fieldName)}: ${labelling(subFieldName)}`,
|
label: `${labelling(fieldName)}: ${labelling(subFieldName)}`,
|
||||||
type: infos.properties[fieldName].properties?.[subFieldName].type,
|
type: infos.properties[fieldName].properties?.[subFieldName].type,
|
||||||
required: false,
|
required: false,
|
||||||
}
|
};
|
||||||
if (infos.required?.includes(fieldName)) {
|
if (infos.required?.includes(fieldName)) {
|
||||||
field.required = true
|
field.required = true;
|
||||||
}
|
}
|
||||||
result.push(field)
|
result.push(field);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -42,13 +44,13 @@ export const computeInputFields = (infos: Infos): object[] => {
|
|||||||
label: labelling(fieldName),
|
label: labelling(fieldName),
|
||||||
type: infos.properties[fieldName].type,
|
type: infos.properties[fieldName].type,
|
||||||
required: false,
|
required: false,
|
||||||
}
|
};
|
||||||
if (infos.required?.includes(fieldName)) {
|
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 handleQueryParams = (inputData: { [x: string]: any }): string => {
|
||||||
const formattedInputData: {[x:string]: any} = {};
|
const formattedInputData: { [x: string]: any } = {};
|
||||||
Object.keys(inputData).forEach((key) => {
|
Object.keys(inputData).forEach((key) => {
|
||||||
if(key.includes('__')) {
|
if (key.includes('__')) {
|
||||||
const [objectKey, nestedObjectKey] = key.split('__')
|
const [objectKey, nestedObjectKey] = key.split('__');
|
||||||
if (formattedInputData[objectKey]) {
|
if (formattedInputData[objectKey]) {
|
||||||
formattedInputData[objectKey][nestedObjectKey] = inputData[key]
|
formattedInputData[objectKey][nestedObjectKey] = inputData[key];
|
||||||
} else {
|
} else {
|
||||||
formattedInputData[objectKey] = {[nestedObjectKey]: inputData[key]}
|
formattedInputData[objectKey] = { [nestedObjectKey]: inputData[key] };
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
formattedInputData[key]=inputData[key]
|
formattedInputData[key] = inputData[key];
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
let result = '';
|
let result = '';
|
||||||
Object.keys(formattedInputData).forEach((key) => {
|
Object.keys(formattedInputData).forEach((key) => {
|
||||||
let quote = '';
|
let quote = '';
|
||||||
if (typeof formattedInputData[key]==='object') {
|
if (typeof formattedInputData[key] === 'object') {
|
||||||
result=result.concat(`${key}: {${handleQueryParams(formattedInputData[key])}}, `)
|
result = result.concat(
|
||||||
|
`${key}: {${handleQueryParams(formattedInputData[key])}}, `,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
if (typeof formattedInputData[key] === 'string') quote = '"';
|
if (typeof formattedInputData[key] === 'string') quote = '"';
|
||||||
result = result.concat(`${key}: ${quote}${formattedInputData[key]}${quote}, `);
|
result = result.concat(
|
||||||
|
`${key}: ${quote}${formattedInputData[key]}${quote}, `,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (result.length) result = result.slice(0, -2); // Remove the last ', '
|
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 => {
|
export const labelling = (str: string): string => {
|
||||||
return str
|
return str
|
||||||
.replace(/[A-Z]/g, letter => ` ${letter.toLowerCase()}`)
|
.replace(/[A-Z]/g, (letter) => ` ${letter.toLowerCase()}`)
|
||||||
.split(' ')
|
.split(' ')
|
||||||
.map((word)=> capitalize(word))
|
.map((word) => capitalize(word))
|
||||||
.join(' ');
|
.join(' ');
|
||||||
}
|
};
|
||||||
|
|||||||
@ -2,18 +2,17 @@ import { Bundle, HttpRequestOptions, ZObject } from 'zapier-platform-core';
|
|||||||
|
|
||||||
export const requestSchema = async (z: ZObject, bundle: Bundle) => {
|
export const requestSchema = async (z: ZObject, bundle: Bundle) => {
|
||||||
const options = {
|
const options = {
|
||||||
url: `${process.env.SERVER_BASE_URL}/open-api`,
|
url: `${process.env.SERVER_BASE_URL}/open-api`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
Authorization: `Bearer ${bundle.authData.apiKey}`,
|
Authorization: `Bearer ${bundle.authData.apiKey}`,
|
||||||
},
|
},
|
||||||
} satisfies HttpRequestOptions;
|
} satisfies HttpRequestOptions;
|
||||||
|
|
||||||
return z.request(options)
|
return z.request(options).then((response) => response.json);
|
||||||
.then((response) => response.json)
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const requestDb = async (z: ZObject, bundle: Bundle, query: string) => {
|
const requestDb = async (z: ZObject, bundle: Bundle, query: string) => {
|
||||||
const options = {
|
const options = {
|
||||||
@ -37,7 +36,7 @@ const requestDb = async (z: ZObject, bundle: Bundle, query: string) => {
|
|||||||
throw new z.errors.Error(
|
throw new z.errors.Error(
|
||||||
`query: ${query}, error: ${JSON.stringify(results.errors)}`,
|
`query: ${query}, error: ${JSON.stringify(results.errors)}`,
|
||||||
'ApiError',
|
'ApiError',
|
||||||
response.status
|
response.status,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
response.throwForStatus();
|
response.throwForStatus();
|
||||||
@ -47,9 +46,32 @@ const requestDb = async (z: ZObject, bundle: Bundle, query: string) => {
|
|||||||
throw new z.errors.Error(
|
throw new z.errors.Error(
|
||||||
`query: ${query}, error: ${err.message}`,
|
`query: ${query}, error: ${err.message}`,
|
||||||
'Error',
|
'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;
|
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