mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat(Gong Node): New node (#10777)
This commit is contained in:
parent
aa3c0dd226
commit
785b47feb3
58
packages/nodes-base/credentials/GongApi.credentials.ts
Normal file
58
packages/nodes-base/credentials/GongApi.credentials.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import type {
|
||||||
|
IAuthenticateGeneric,
|
||||||
|
ICredentialTestRequest,
|
||||||
|
ICredentialType,
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export class GongApi implements ICredentialType {
|
||||||
|
name = 'gongApi';
|
||||||
|
|
||||||
|
displayName = 'Gong API';
|
||||||
|
|
||||||
|
documentationUrl = 'gong';
|
||||||
|
|
||||||
|
properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Base URL',
|
||||||
|
name: 'baseUrl',
|
||||||
|
type: 'string',
|
||||||
|
default: 'https://api.gong.io',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Access Key',
|
||||||
|
name: 'accessKey',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
password: true,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Access Key Secret',
|
||||||
|
name: 'accessKeySecret',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
typeOptions: {
|
||||||
|
password: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
authenticate: IAuthenticateGeneric = {
|
||||||
|
type: 'generic',
|
||||||
|
properties: {
|
||||||
|
auth: {
|
||||||
|
username: '={{ $credentials.accessKey }}',
|
||||||
|
password: '={{ $credentials.accessKeySecret }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
test: ICredentialTestRequest = {
|
||||||
|
request: {
|
||||||
|
baseURL: '={{ $credentials.baseUrl.replace(new RegExp("/$"), "") }}',
|
||||||
|
url: '/v2/users',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
59
packages/nodes-base/credentials/GongOAuth2Api.credentials.ts
Normal file
59
packages/nodes-base/credentials/GongOAuth2Api.credentials.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export class GongOAuth2Api implements ICredentialType {
|
||||||
|
name = 'gongOAuth2Api';
|
||||||
|
|
||||||
|
extends = ['oAuth2Api'];
|
||||||
|
|
||||||
|
displayName = 'Gong OAuth2 API';
|
||||||
|
|
||||||
|
documentationUrl = 'gong';
|
||||||
|
|
||||||
|
properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Base URL',
|
||||||
|
name: 'baseUrl',
|
||||||
|
type: 'string',
|
||||||
|
default: 'https://api.gong.io',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Grant Type',
|
||||||
|
name: 'grantType',
|
||||||
|
type: 'hidden',
|
||||||
|
default: 'authorizationCode',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Authorization URL',
|
||||||
|
name: 'authUrl',
|
||||||
|
type: 'hidden',
|
||||||
|
default: 'https://app.gong.io/oauth2/authorize',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Access Token URL',
|
||||||
|
name: 'accessTokenUrl',
|
||||||
|
type: 'hidden',
|
||||||
|
default: 'https://app.gong.io/oauth2/generate-customer-token',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Scope',
|
||||||
|
name: 'scope',
|
||||||
|
type: 'hidden',
|
||||||
|
default:
|
||||||
|
'api:calls:read:transcript api:provisioning:read api:workspaces:read api:meetings:user:delete api:crm:get-objects api:data-privacy:delete api:crm:schema api:flows:write api:crm:upload api:meetings:integration:status api:calls:read:extensive api:meetings:user:update api:integration-settings:write api:settings:scorecards:read api:stats:scorecards api:stats:interaction api:stats:user-actions api:crm:integration:delete api:calls:read:basic api:calls:read:media-url api:digital-interactions:write api:crm:integrations:read api:library:read api:data-privacy:read api:users:read api:logs:read api:calls:create api:meetings:user:create api:stats:user-actions:detailed api:settings:trackers:read api:crm:integration:register api:provisioning:read-write api:engagement-data:write api:permission-profile:read api:permission-profile:write api:flows:read api:crm-calls:manual-association:read',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Auth URI Query Parameters',
|
||||||
|
name: 'authQueryParameters',
|
||||||
|
type: 'hidden',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Authentication',
|
||||||
|
name: 'authentication',
|
||||||
|
type: 'hidden',
|
||||||
|
default: 'header',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
227
packages/nodes-base/nodes/Gong/GenericFunctions.ts
Normal file
227
packages/nodes-base/nodes/Gong/GenericFunctions.ts
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import type {
|
||||||
|
DeclarativeRestApiSettings,
|
||||||
|
IDataObject,
|
||||||
|
IExecuteFunctions,
|
||||||
|
IExecutePaginationFunctions,
|
||||||
|
IExecuteSingleFunctions,
|
||||||
|
IHttpRequestMethods,
|
||||||
|
IHttpRequestOptions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
IN8nHttpFullResponse,
|
||||||
|
INodeExecutionData,
|
||||||
|
JsonObject,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { NodeApiError } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export async function gongApiRequest(
|
||||||
|
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||||
|
method: IHttpRequestMethods,
|
||||||
|
endpoint: string,
|
||||||
|
body: IDataObject = {},
|
||||||
|
query: IDataObject = {},
|
||||||
|
) {
|
||||||
|
const authentication = this.getNodeParameter('authentication', 0) as 'accessToken' | 'oAuth2';
|
||||||
|
const credentialsType = authentication === 'oAuth2' ? 'gongOAuth2Api' : 'gongApi';
|
||||||
|
const { baseUrl } = await this.getCredentials<{
|
||||||
|
baseUrl: string;
|
||||||
|
}>(credentialsType);
|
||||||
|
|
||||||
|
const options: IHttpRequestOptions = {
|
||||||
|
method,
|
||||||
|
url: baseUrl.replace(new RegExp('/$'), '') + endpoint,
|
||||||
|
json: true,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body,
|
||||||
|
qs: query,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Object.keys(body).length === 0) {
|
||||||
|
delete options.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.helpers.requestWithAuthentication.call(this, credentialsType, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function gongApiPaginateRequest(
|
||||||
|
this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
|
||||||
|
method: IHttpRequestMethods,
|
||||||
|
endpoint: string,
|
||||||
|
body: IDataObject = {},
|
||||||
|
query: IDataObject = {},
|
||||||
|
itemIndex: number = 0,
|
||||||
|
rootProperty: string | undefined = undefined,
|
||||||
|
): Promise<any> {
|
||||||
|
const authentication = this.getNodeParameter('authentication', 0) as 'accessToken' | 'oAuth2';
|
||||||
|
const credentialsType = authentication === 'oAuth2' ? 'gongOAuth2Api' : 'gongApi';
|
||||||
|
const { baseUrl } = await this.getCredentials<{
|
||||||
|
baseUrl: string;
|
||||||
|
}>(credentialsType);
|
||||||
|
|
||||||
|
const options: IHttpRequestOptions = {
|
||||||
|
method,
|
||||||
|
url: baseUrl.replace(new RegExp('/$'), '') + endpoint,
|
||||||
|
json: true,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body,
|
||||||
|
qs: query,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Object.keys(body).length === 0) {
|
||||||
|
delete options.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pages = await this.helpers.requestWithAuthenticationPaginated.call(
|
||||||
|
this,
|
||||||
|
options,
|
||||||
|
itemIndex,
|
||||||
|
{
|
||||||
|
requestInterval: 340, // Rate limit 3 calls per second
|
||||||
|
continue: '={{ $response.body.records.cursor }}',
|
||||||
|
request: {
|
||||||
|
[method === 'POST' ? 'body' : 'qs']:
|
||||||
|
'={{ $if($response.body?.records.cursor, { cursor: $response.body.records.cursor }, {}) }}',
|
||||||
|
url: options.url,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
credentialsType,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (rootProperty) {
|
||||||
|
let results: IDataObject[] = [];
|
||||||
|
for (const page of pages) {
|
||||||
|
const items = page.body[rootProperty];
|
||||||
|
if (items) {
|
||||||
|
results = results.concat(items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
} else {
|
||||||
|
return pages.flat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCursorPaginator = (
|
||||||
|
extractItems: (items: INodeExecutionData[]) => INodeExecutionData[],
|
||||||
|
) => {
|
||||||
|
return async function cursorPagination(
|
||||||
|
this: IExecutePaginationFunctions,
|
||||||
|
requestOptions: DeclarativeRestApiSettings.ResultOptions,
|
||||||
|
): Promise<INodeExecutionData[]> {
|
||||||
|
let executions: INodeExecutionData[] = [];
|
||||||
|
let responseData: INodeExecutionData[];
|
||||||
|
let nextCursor: string | undefined = undefined;
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', true) as boolean;
|
||||||
|
|
||||||
|
do {
|
||||||
|
(requestOptions.options.body as IDataObject).cursor = nextCursor;
|
||||||
|
responseData = await this.makeRoutingRequest(requestOptions);
|
||||||
|
const lastItem = responseData[responseData.length - 1].json;
|
||||||
|
nextCursor = (lastItem.records as IDataObject)?.cursor as string | undefined;
|
||||||
|
executions = executions.concat(extractItems(responseData));
|
||||||
|
} while (returnAll && nextCursor);
|
||||||
|
|
||||||
|
return executions;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const extractCalls = (items: INodeExecutionData[]): INodeExecutionData[] => {
|
||||||
|
const calls: IDataObject[] = items.flatMap((item) => get(item.json, 'calls') as IDataObject[]);
|
||||||
|
return calls.map((call) => {
|
||||||
|
const { metaData, ...rest } = call ?? {};
|
||||||
|
return { json: { ...(metaData as IDataObject), ...rest } };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const extractUsers = (items: INodeExecutionData[]): INodeExecutionData[] => {
|
||||||
|
const users: IDataObject[] = items.flatMap((item) => get(item.json, 'users') as IDataObject[]);
|
||||||
|
return users.map((user) => ({ json: user }));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCursorPaginatorCalls = () => {
|
||||||
|
return getCursorPaginator(extractCalls);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCursorPaginatorUsers = () => {
|
||||||
|
return getCursorPaginator(extractUsers);
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function handleErrorPostReceive(
|
||||||
|
this: IExecuteSingleFunctions,
|
||||||
|
data: INodeExecutionData[],
|
||||||
|
response: IN8nHttpFullResponse,
|
||||||
|
): Promise<INodeExecutionData[]> {
|
||||||
|
if (String(response.statusCode).startsWith('4') || String(response.statusCode).startsWith('5')) {
|
||||||
|
const { resource, operation } = this.getNode().parameters;
|
||||||
|
|
||||||
|
if (resource === 'call') {
|
||||||
|
if (operation === 'get') {
|
||||||
|
if (response.statusCode === 404) {
|
||||||
|
throw new NodeApiError(this.getNode(), response as unknown as JsonObject, {
|
||||||
|
message: "The required call doesn't match any existing one",
|
||||||
|
description: "Double-check the value in the parameter 'Call to Get' and try again",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (operation === 'getAll') {
|
||||||
|
if (response.statusCode === 404) {
|
||||||
|
const primaryUserId = this.getNodeParameter('filters.primaryUserIds', {}) as IDataObject;
|
||||||
|
if (Object.keys(primaryUserId).length !== 0) {
|
||||||
|
return [{ json: {} }];
|
||||||
|
}
|
||||||
|
} else if (response.statusCode === 400 || response.statusCode === 500) {
|
||||||
|
throw new NodeApiError(this.getNode(), response as unknown as JsonObject, {
|
||||||
|
description: 'Double-check the value(s) in the parameter(s)',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (resource === 'user') {
|
||||||
|
if (operation === 'get') {
|
||||||
|
if (response.statusCode === 404) {
|
||||||
|
throw new NodeApiError(this.getNode(), response as unknown as JsonObject, {
|
||||||
|
message: "The required user doesn't match any existing one",
|
||||||
|
description: "Double-check the value in the parameter 'User to Get' and try again",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (operation === 'getAll') {
|
||||||
|
if (response.statusCode === 404) {
|
||||||
|
const userIds = this.getNodeParameter('filters.userIds', '') as string;
|
||||||
|
if (userIds) {
|
||||||
|
throw new NodeApiError(this.getNode(), response as unknown as JsonObject, {
|
||||||
|
message: "The Users IDs don't match any existing user",
|
||||||
|
description: "Double-check the values in the parameter 'Users IDs' and try again",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new NodeApiError(this.getNode(), response as unknown as JsonObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isValidNumberIds(value: number | number[] | string | string[]): boolean {
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value) && value.every((item) => typeof item === 'number')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const parts = value.split(',');
|
||||||
|
return parts.every((part) => !isNaN(Number(part.trim())));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value) && value.every((item) => typeof item === 'string')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
18
packages/nodes-base/nodes/Gong/Gong.node.json
Normal file
18
packages/nodes-base/nodes/Gong/Gong.node.json
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"node": "n8n-nodes-base.gong",
|
||||||
|
"nodeVersion": "1.0",
|
||||||
|
"codexVersion": "1.0",
|
||||||
|
"categories": ["Development", "Developer Tools"],
|
||||||
|
"resources": {
|
||||||
|
"credentialDocumentation": [
|
||||||
|
{
|
||||||
|
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.gong/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryDocumentation": [
|
||||||
|
{
|
||||||
|
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.gong/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
171
packages/nodes-base/nodes/Gong/Gong.node.ts
Normal file
171
packages/nodes-base/nodes/Gong/Gong.node.ts
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
import {
|
||||||
|
NodeConnectionType,
|
||||||
|
type IDataObject,
|
||||||
|
type ILoadOptionsFunctions,
|
||||||
|
type INodeListSearchItems,
|
||||||
|
type INodeListSearchResult,
|
||||||
|
type INodeType,
|
||||||
|
type INodeTypeDescription,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { callFields, callOperations, userFields, userOperations } from './descriptions';
|
||||||
|
import { gongApiRequest } from './GenericFunctions';
|
||||||
|
|
||||||
|
export class Gong implements INodeType {
|
||||||
|
description: INodeTypeDescription = {
|
||||||
|
displayName: 'Gong',
|
||||||
|
name: 'gong',
|
||||||
|
icon: 'file:gong.svg',
|
||||||
|
group: ['transform'],
|
||||||
|
version: 1,
|
||||||
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||||
|
description: 'Interact with Gong API',
|
||||||
|
defaults: {
|
||||||
|
name: 'Gong',
|
||||||
|
},
|
||||||
|
inputs: [NodeConnectionType.Main],
|
||||||
|
outputs: [NodeConnectionType.Main],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'gongApi',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
authentication: ['accessToken'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'gongOAuth2Api',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
authentication: ['oAuth2'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
requestDefaults: {
|
||||||
|
baseURL: '={{ $credentials.baseUrl.replace(new RegExp("/$"), "") }}',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Authentication',
|
||||||
|
name: 'authentication',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Access Token',
|
||||||
|
value: 'accessToken',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'OAuth2',
|
||||||
|
value: 'oAuth2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'accessToken',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Resource',
|
||||||
|
name: 'resource',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Call',
|
||||||
|
value: 'call',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'User',
|
||||||
|
value: 'user',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'call',
|
||||||
|
},
|
||||||
|
...callOperations,
|
||||||
|
...callFields,
|
||||||
|
...userOperations,
|
||||||
|
...userFields,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
methods = {
|
||||||
|
listSearch: {
|
||||||
|
async getCalls(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
filter?: string,
|
||||||
|
paginationToken?: string,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
const query: IDataObject = {};
|
||||||
|
if (paginationToken) {
|
||||||
|
query.cursor = paginationToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseData = await gongApiRequest.call(this, 'GET', '/v2/calls', {}, query);
|
||||||
|
|
||||||
|
const calls: Array<{
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
}> = responseData.calls;
|
||||||
|
|
||||||
|
const results: INodeListSearchItems[] = calls
|
||||||
|
.map((c) => ({
|
||||||
|
name: c.title,
|
||||||
|
value: c.id,
|
||||||
|
}))
|
||||||
|
.filter(
|
||||||
|
(c) =>
|
||||||
|
!filter ||
|
||||||
|
c.name.toLowerCase().includes(filter.toLowerCase()) ||
|
||||||
|
c.value?.toString() === filter,
|
||||||
|
)
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1;
|
||||||
|
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { results, paginationToken: responseData.records.cursor };
|
||||||
|
},
|
||||||
|
|
||||||
|
async getUsers(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
filter?: string,
|
||||||
|
paginationToken?: string,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
const query: IDataObject = {};
|
||||||
|
if (paginationToken) {
|
||||||
|
query.cursor = paginationToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseData = await gongApiRequest.call(this, 'GET', '/v2/users', {}, query);
|
||||||
|
|
||||||
|
const users: Array<{
|
||||||
|
id: string;
|
||||||
|
emailAddress: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
}> = responseData.users;
|
||||||
|
|
||||||
|
const results: INodeListSearchItems[] = users
|
||||||
|
.map((u) => ({
|
||||||
|
name: `${u.firstName} ${u.lastName} (${u.emailAddress})`,
|
||||||
|
value: u.id,
|
||||||
|
}))
|
||||||
|
.filter(
|
||||||
|
(u) =>
|
||||||
|
!filter ||
|
||||||
|
u.name.toLowerCase().includes(filter.toLowerCase()) ||
|
||||||
|
u.value?.toString() === filter,
|
||||||
|
)
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1;
|
||||||
|
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { results, paginationToken: responseData.records.cursor };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
603
packages/nodes-base/nodes/Gong/descriptions/CallDescription.ts
Normal file
603
packages/nodes-base/nodes/Gong/descriptions/CallDescription.ts
Normal file
|
@ -0,0 +1,603 @@
|
||||||
|
import type {
|
||||||
|
IDataObject,
|
||||||
|
IExecuteSingleFunctions,
|
||||||
|
IHttpRequestOptions,
|
||||||
|
IN8nHttpFullResponse,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { NodeApiError } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getCursorPaginatorCalls,
|
||||||
|
gongApiPaginateRequest,
|
||||||
|
isValidNumberIds,
|
||||||
|
handleErrorPostReceive,
|
||||||
|
extractCalls,
|
||||||
|
} from '../GenericFunctions';
|
||||||
|
|
||||||
|
export const callOperations: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['call'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Retrieve data for a specific call',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/v2/calls/extensive',
|
||||||
|
ignoreHttpStatusErrors: true,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
postReceive: [handleErrorPostReceive],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action: 'Get call',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Many',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Retrieve a list of calls',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/v2/calls/extensive',
|
||||||
|
body: {
|
||||||
|
filter: {},
|
||||||
|
},
|
||||||
|
ignoreHttpStatusErrors: true,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
postReceive: [handleErrorPostReceive],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action: 'Get many calls',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'getAll',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const getFields: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Call to Get',
|
||||||
|
name: 'call',
|
||||||
|
default: {
|
||||||
|
mode: 'list',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['call'],
|
||||||
|
operation: ['get'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From List',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'getCalls',
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'By ID',
|
||||||
|
name: 'id',
|
||||||
|
placeholder: 'e.g. 7782342274025937895',
|
||||||
|
type: 'string',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: '[0-9]{1,20}',
|
||||||
|
errorMessage: 'Not a valid Gong Call ID',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'By URL',
|
||||||
|
name: 'url',
|
||||||
|
extractValue: {
|
||||||
|
type: 'regex',
|
||||||
|
regex: 'https:\\/\\/[a-zA-Z0-9-]+\\.app\\.gong\\.io\\/call\\?id=([0-9]{1,20})',
|
||||||
|
},
|
||||||
|
placeholder: 'e.g. https://subdomain.app.gong.io/call?id=7782342274025937895',
|
||||||
|
type: 'string',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: 'https:\\/\\/[a-zA-Z0-9-]+\\.app\\.gong\\.io\\/call\\?id=([0-9]{1,20})',
|
||||||
|
errorMessage: 'Not a valid Gong URL',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
required: true,
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
type: 'body',
|
||||||
|
property: 'filter.callIds',
|
||||||
|
propertyInDotNotation: true,
|
||||||
|
value: '={{ [$value] }}',
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
postReceive: [
|
||||||
|
{
|
||||||
|
type: 'rootProperty',
|
||||||
|
properties: {
|
||||||
|
property: 'calls',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'resourceLocator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['call'],
|
||||||
|
operation: ['get'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Call Data to Include',
|
||||||
|
name: 'properties',
|
||||||
|
type: 'multiOptions',
|
||||||
|
default: [],
|
||||||
|
description:
|
||||||
|
'The Call properties to include in the returned results. Choose from a list, or specify IDs using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Action Items',
|
||||||
|
value: 'pointsOfInterest',
|
||||||
|
description: 'Call points of interest',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Audio and Video URLs',
|
||||||
|
value: 'media',
|
||||||
|
description: 'Audio and video URL of the call. The URLs will be available for 8 hours.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Brief',
|
||||||
|
value: 'brief',
|
||||||
|
description: 'Spotlight call brief',
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
type: 'body',
|
||||||
|
property: 'contentSelector.exposedFields.content.brief',
|
||||||
|
propertyInDotNotation: true,
|
||||||
|
value: '={{ $value }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Comments',
|
||||||
|
value: 'publicComments',
|
||||||
|
description: 'Public comments made for this call',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Highlights',
|
||||||
|
value: 'highlights',
|
||||||
|
description: 'Call highlights',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Keypoints',
|
||||||
|
value: 'keyPoints',
|
||||||
|
description: 'Key points of the call',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Outcome',
|
||||||
|
value: 'callOutcome',
|
||||||
|
description: 'Outcome of the call',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Outline',
|
||||||
|
value: 'outline',
|
||||||
|
description: 'Call outline',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Participants',
|
||||||
|
value: 'parties',
|
||||||
|
description: 'Information about the participants of the call',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Structure',
|
||||||
|
value: 'structure',
|
||||||
|
description: 'Call agenda',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Topics',
|
||||||
|
value: 'topics',
|
||||||
|
description: 'Duration of call topics',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Trackers',
|
||||||
|
value: 'trackers',
|
||||||
|
description: 'Smart tracker and keyword tracker information for the call',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Transcript',
|
||||||
|
value: 'transcript',
|
||||||
|
description: 'Information about the participants',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
preSend: [
|
||||||
|
async function (
|
||||||
|
this: IExecuteSingleFunctions,
|
||||||
|
requestOptions: IHttpRequestOptions,
|
||||||
|
): Promise<IHttpRequestOptions> {
|
||||||
|
const contentProperties = [
|
||||||
|
'pointsOfInterest',
|
||||||
|
'brief',
|
||||||
|
'highlights',
|
||||||
|
'keyPoints',
|
||||||
|
'outline',
|
||||||
|
'callOutcome',
|
||||||
|
'structure',
|
||||||
|
'trackers',
|
||||||
|
'topics',
|
||||||
|
];
|
||||||
|
const exposedFieldsProperties = ['media', 'parties'];
|
||||||
|
const collaborationProperties = ['publicComments'];
|
||||||
|
|
||||||
|
const properties = this.getNodeParameter('options.properties') as string[];
|
||||||
|
const contentSelector = { exposedFields: {} } as any;
|
||||||
|
for (const property of properties) {
|
||||||
|
if (exposedFieldsProperties.includes(property)) {
|
||||||
|
contentSelector.exposedFields[property] = true;
|
||||||
|
} else if (contentProperties.includes(property)) {
|
||||||
|
contentSelector.exposedFields.content ??= {};
|
||||||
|
contentSelector.exposedFields.content[property] = true;
|
||||||
|
} else if (collaborationProperties.includes(property)) {
|
||||||
|
contentSelector.exposedFields.collaboration ??= {};
|
||||||
|
contentSelector.exposedFields.collaboration[property] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestOptions.body ||= {};
|
||||||
|
Object.assign(requestOptions.body, { contentSelector });
|
||||||
|
return requestOptions;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
postReceive: [
|
||||||
|
async function (
|
||||||
|
this: IExecuteSingleFunctions,
|
||||||
|
items: INodeExecutionData[],
|
||||||
|
_responseData: IN8nHttpFullResponse,
|
||||||
|
): Promise<INodeExecutionData[]> {
|
||||||
|
const properties = this.getNodeParameter('options.properties') as string[];
|
||||||
|
if (properties.includes('transcript')) {
|
||||||
|
for (const item of items) {
|
||||||
|
const callTranscripts = await gongApiPaginateRequest.call(
|
||||||
|
this,
|
||||||
|
'POST',
|
||||||
|
'/v2/calls/transcript',
|
||||||
|
{ filter: { callIds: [(item.json.metaData as IDataObject).id] } },
|
||||||
|
{},
|
||||||
|
item.index ?? 0,
|
||||||
|
'callTranscripts',
|
||||||
|
);
|
||||||
|
item.json.transcript = callTranscripts?.length
|
||||||
|
? callTranscripts[0].transcript
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
type: 'collection',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const getAllFields: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
default: false,
|
||||||
|
description: 'Whether to return all results or only up to a given limit',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['call'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
paginate: '={{ $value }}',
|
||||||
|
},
|
||||||
|
operations: {
|
||||||
|
pagination: getCursorPaginatorCalls(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Limit',
|
||||||
|
name: 'limit',
|
||||||
|
default: 50,
|
||||||
|
description: 'Max number of results to return',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['call'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
returnAll: [false],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
routing: {
|
||||||
|
output: {
|
||||||
|
postReceive: [
|
||||||
|
async function (
|
||||||
|
this: IExecuteSingleFunctions,
|
||||||
|
items: INodeExecutionData[],
|
||||||
|
_response: IN8nHttpFullResponse,
|
||||||
|
): Promise<INodeExecutionData[]> {
|
||||||
|
return extractCalls(items);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'limit',
|
||||||
|
properties: {
|
||||||
|
maxResults: '={{ $value }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'number',
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 1,
|
||||||
|
},
|
||||||
|
validateType: 'number',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Filters',
|
||||||
|
name: 'filters',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['call'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'After',
|
||||||
|
name: 'fromDateTime',
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'Returns calls that started on or after the specified date and time. If not provided, list starts with earliest call. For web-conference calls recorded by Gong, the date denotes its scheduled time, otherwise, it denotes its actual start time.',
|
||||||
|
placeholder: 'e.g. 2018-02-18T02:30:00-07:00 or 2018-02-18T08:00:00Z',
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
type: 'body',
|
||||||
|
property: 'filter.fromDateTime',
|
||||||
|
propertyInDotNotation: true,
|
||||||
|
value: '={{ new Date($value).toISOString() }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'dateTime',
|
||||||
|
validateType: 'dateTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Before',
|
||||||
|
name: 'toDateTime',
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'Returns calls that started up to but excluding specified date and time. If not provided, list ends with most recent call. For web-conference calls recorded by Gong, the date denotes its scheduled time, otherwise, it denotes its actual start time.',
|
||||||
|
placeholder: 'e.g. 2018-02-18T02:30:00-07:00 or 2018-02-18T08:00:00Z',
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
type: 'body',
|
||||||
|
property: 'filter.toDateTime',
|
||||||
|
propertyInDotNotation: true,
|
||||||
|
value: '={{ new Date($value).toISOString() }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'dateTime',
|
||||||
|
validateType: 'dateTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Workspace ID',
|
||||||
|
name: 'workspaceId',
|
||||||
|
default: '',
|
||||||
|
description: 'Return only the calls belonging to this workspace',
|
||||||
|
placeholder: 'e.g. 623457276584334',
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
type: 'body',
|
||||||
|
property: 'filter.workspaceId',
|
||||||
|
propertyInDotNotation: true,
|
||||||
|
value: '={{ $value }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'string',
|
||||||
|
validateType: 'number',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Call IDs',
|
||||||
|
name: 'callIds',
|
||||||
|
default: '',
|
||||||
|
description: 'List of calls IDs to be filtered',
|
||||||
|
hint: 'Comma separated list of IDs, array of strings can be set in expression',
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
preSend: [
|
||||||
|
async function (
|
||||||
|
this: IExecuteSingleFunctions,
|
||||||
|
requestOptions: IHttpRequestOptions,
|
||||||
|
): Promise<IHttpRequestOptions> {
|
||||||
|
const callIdsParam = this.getNodeParameter('filters.callIds') as
|
||||||
|
| number
|
||||||
|
| number[]
|
||||||
|
| string
|
||||||
|
| string[];
|
||||||
|
if (callIdsParam && !isValidNumberIds(callIdsParam)) {
|
||||||
|
throw new NodeApiError(this.getNode(), {
|
||||||
|
message: 'Call IDs must be numeric',
|
||||||
|
description: "Double-check the value in the parameter 'Call IDs' and try again",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const callIds = Array.isArray(callIdsParam)
|
||||||
|
? callIdsParam.map((x) => x.toString())
|
||||||
|
: callIdsParam
|
||||||
|
.toString()
|
||||||
|
.split(',')
|
||||||
|
.map((x) => x.trim());
|
||||||
|
|
||||||
|
requestOptions.body ||= {};
|
||||||
|
(requestOptions.body as IDataObject).filter ||= {};
|
||||||
|
Object.assign((requestOptions.body as IDataObject).filter as IDataObject, {
|
||||||
|
callIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
return requestOptions;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
placeholder: 'e.g. 7782342274025937895',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Organizer',
|
||||||
|
name: 'primaryUserIds',
|
||||||
|
default: {
|
||||||
|
mode: 'list',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
description: 'Return only the calls hosted by the specified user',
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From List',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'getUsers',
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'By ID',
|
||||||
|
name: 'id',
|
||||||
|
placeholder: 'e.g. 7782342274025937895',
|
||||||
|
type: 'string',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: '[0-9]{1,20}',
|
||||||
|
errorMessage: 'Not a valid Gong User ID',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
type: 'body',
|
||||||
|
property: 'filter.primaryUserIds',
|
||||||
|
propertyInDotNotation: true,
|
||||||
|
value: '={{ [$value] }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'resourceLocator',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
placeholder: 'Add Filter',
|
||||||
|
type: 'collection',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['call'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Call Data to Include',
|
||||||
|
name: 'properties',
|
||||||
|
type: 'multiOptions',
|
||||||
|
default: [],
|
||||||
|
description:
|
||||||
|
'The Call properties to include in the returned results. Choose from a list, or specify IDs using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Participants',
|
||||||
|
value: 'parties',
|
||||||
|
description: 'Information about the participants of the call',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Topics',
|
||||||
|
value: 'topics',
|
||||||
|
description: 'Information about the topics of the call',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
preSend: [
|
||||||
|
async function (
|
||||||
|
this: IExecuteSingleFunctions,
|
||||||
|
requestOptions: IHttpRequestOptions,
|
||||||
|
): Promise<IHttpRequestOptions> {
|
||||||
|
const contentProperties = ['topics'];
|
||||||
|
const exposedFieldsProperties = ['parties'];
|
||||||
|
|
||||||
|
const properties = this.getNodeParameter('options.properties') as string[];
|
||||||
|
const contentSelector = { exposedFields: {} } as any;
|
||||||
|
for (const property of properties) {
|
||||||
|
if (exposedFieldsProperties.includes(property)) {
|
||||||
|
contentSelector.exposedFields[property] = true;
|
||||||
|
} else if (contentProperties.includes(property)) {
|
||||||
|
contentSelector.exposedFields.content ??= {};
|
||||||
|
contentSelector.exposedFields.content[property] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestOptions.body ||= {};
|
||||||
|
Object.assign(requestOptions.body, { contentSelector });
|
||||||
|
return requestOptions;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
type: 'collection',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const callFields: INodeProperties[] = [...getFields, ...getAllFields];
|
288
packages/nodes-base/nodes/Gong/descriptions/UserDescription.ts
Normal file
288
packages/nodes-base/nodes/Gong/descriptions/UserDescription.ts
Normal file
|
@ -0,0 +1,288 @@
|
||||||
|
import type {
|
||||||
|
IDataObject,
|
||||||
|
IExecuteSingleFunctions,
|
||||||
|
IHttpRequestOptions,
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { NodeApiError } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getCursorPaginatorUsers,
|
||||||
|
isValidNumberIds,
|
||||||
|
handleErrorPostReceive,
|
||||||
|
} from '../GenericFunctions';
|
||||||
|
|
||||||
|
export const userOperations: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['user'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Retrieve data for a specific user',
|
||||||
|
action: 'Get user',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/v2/users/extensive',
|
||||||
|
ignoreHttpStatusErrors: true,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
postReceive: [handleErrorPostReceive],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Many',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Retrieve a list of users',
|
||||||
|
action: 'Get many users',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/v2/users/extensive',
|
||||||
|
body: {
|
||||||
|
filter: {},
|
||||||
|
},
|
||||||
|
ignoreHttpStatusErrors: true,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
postReceive: [handleErrorPostReceive],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'get',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const getOperation: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'User to Get',
|
||||||
|
name: 'user',
|
||||||
|
default: {
|
||||||
|
mode: 'list',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['user'],
|
||||||
|
operation: ['get'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From List',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'getUsers',
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'By ID',
|
||||||
|
name: 'id',
|
||||||
|
placeholder: 'e.g. 7782342274025937895',
|
||||||
|
type: 'string',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: '[0-9]{1,20}',
|
||||||
|
errorMessage: 'Not a valid Gong User ID',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
type: 'body',
|
||||||
|
property: 'filter.userIds',
|
||||||
|
propertyInDotNotation: true,
|
||||||
|
value: '={{ [$value] }}',
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
postReceive: [
|
||||||
|
{
|
||||||
|
type: 'rootProperty',
|
||||||
|
properties: {
|
||||||
|
property: 'users',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'resourceLocator',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const getAllOperation: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
default: false,
|
||||||
|
description: 'Whether to return all results or only up to a given limit',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['user'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
paginate: '={{ $value }}',
|
||||||
|
},
|
||||||
|
operations: {
|
||||||
|
pagination: getCursorPaginatorUsers(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'boolean',
|
||||||
|
validateType: 'boolean',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Limit',
|
||||||
|
name: 'limit',
|
||||||
|
default: 50,
|
||||||
|
description: 'Max number of results to return',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['user'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
returnAll: [false],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
routing: {
|
||||||
|
output: {
|
||||||
|
postReceive: [
|
||||||
|
{
|
||||||
|
type: 'rootProperty',
|
||||||
|
properties: {
|
||||||
|
property: 'users',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'limit',
|
||||||
|
properties: {
|
||||||
|
maxResults: '={{ $value }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'number',
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 1,
|
||||||
|
},
|
||||||
|
validateType: 'number',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Filters',
|
||||||
|
name: 'filters',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['user'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Created After',
|
||||||
|
name: 'createdFromDateTime',
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'An optional user creation time lower limit, if supplied the API will return only the users created at or after this time',
|
||||||
|
placeholder: 'e.g. 2018-02-18T02:30:00-07:00 or 2018-02-18T08:00:00Z',
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
type: 'body',
|
||||||
|
property: 'filter.createdFromDateTime',
|
||||||
|
propertyInDotNotation: true,
|
||||||
|
value: '={{ new Date($value).toISOString() }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'dateTime',
|
||||||
|
validateType: 'dateTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Created Before',
|
||||||
|
name: 'createdToDateTime',
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'An optional user creation time upper limit, if supplied the API will return only the users created before this time',
|
||||||
|
placeholder: 'e.g. 2018-02-18T02:30:00-07:00 or 2018-02-18T08:00:00Z',
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
type: 'body',
|
||||||
|
property: 'filter.createdToDateTime',
|
||||||
|
propertyInDotNotation: true,
|
||||||
|
value: '={{ new Date($value).toISOString() }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'dateTime',
|
||||||
|
validateType: 'dateTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'User IDs',
|
||||||
|
name: 'userIds',
|
||||||
|
default: '',
|
||||||
|
description: "Set of Gong's unique numeric identifiers for the users (up to 20 digits)",
|
||||||
|
hint: 'Comma separated list of IDs, array of strings can be set in expression',
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
preSend: [
|
||||||
|
async function (
|
||||||
|
this: IExecuteSingleFunctions,
|
||||||
|
requestOptions: IHttpRequestOptions,
|
||||||
|
): Promise<IHttpRequestOptions> {
|
||||||
|
const userIdsParam = this.getNodeParameter('filters.userIds') as
|
||||||
|
| number
|
||||||
|
| number[]
|
||||||
|
| string
|
||||||
|
| string[];
|
||||||
|
if (userIdsParam && !isValidNumberIds(userIdsParam)) {
|
||||||
|
throw new NodeApiError(this.getNode(), {
|
||||||
|
message: 'User IDs must be numeric',
|
||||||
|
description: "Double-check the value in the parameter 'User IDs' and try again",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const userIds = Array.isArray(userIdsParam)
|
||||||
|
? userIdsParam.map((x) => x.toString())
|
||||||
|
: userIdsParam
|
||||||
|
.toString()
|
||||||
|
.split(',')
|
||||||
|
.map((x) => x.trim());
|
||||||
|
|
||||||
|
requestOptions.body ||= {};
|
||||||
|
(requestOptions.body as IDataObject).filter ||= {};
|
||||||
|
Object.assign((requestOptions.body as IDataObject).filter as IDataObject, {
|
||||||
|
userIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
return requestOptions;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
placeholder: 'e.g. 7782342274025937895',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
placeholder: 'Add Filter',
|
||||||
|
type: 'collection',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const userFields: INodeProperties[] = [...getOperation, ...getAllOperation];
|
2
packages/nodes-base/nodes/Gong/descriptions/index.ts
Normal file
2
packages/nodes-base/nodes/Gong/descriptions/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './CallDescription';
|
||||||
|
export * from './UserDescription';
|
4
packages/nodes-base/nodes/Gong/gong.svg
Normal file
4
packages/nodes-base/nodes/Gong/gong.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg viewBox="30.0354 21.6399 25.8853 28.023" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M 55.285 33.621 L 47.685 33.621 C 47.251 33.621 46.92 34.065 47.075 34.479 L 48.905 39.215 C 48.985 39.426 48.821 39.65 48.595 39.639 L 46.258 39.494 C 46.151 39.486 46.048 39.537 45.99 39.628 L 44.17 42.203 C 44.074 42.354 43.873 42.396 43.725 42.296 L 41.037 40.466 C 40.931 40.393 40.791 40.393 40.685 40.466 L 36.963 42.989 C 36.725 43.154 36.404 42.927 36.487 42.647 L 37.521 38.935 C 37.566 38.781 37.485 38.619 37.335 38.563 L 35.36 37.757 C 35.164 37.679 35.102 37.433 35.236 37.271 L 36.983 35.12 C 37.069 35.012 37.073 34.86 36.993 34.748 L 35.525 32.628 C 35.389 32.432 35.517 32.162 35.755 32.142 C 35.757 32.142 35.76 32.142 35.763 32.142 L 38.038 31.966 C 38.204 31.956 38.338 31.811 38.328 31.636 L 38.152 28.451 C 38.143 28.221 38.372 28.058 38.586 28.141 L 41.409 29.309 C 41.533 29.361 41.678 29.329 41.76 29.226 L 43.704 27.055 C 43.858 26.882 44.138 26.926 44.232 27.137 L 45.4 30.147 C 45.555 30.519 46.02 30.653 46.352 30.405 L 50.922 27.003 C 51.439 26.62 51.108 25.793 50.467 25.886 L 47.457 26.29 C 47.315 26.31 47.177 26.228 47.127 26.093 L 45.545 22.03 C 45.375 21.605 44.819 21.504 44.511 21.843 L 41.068 25.576 C 40.977 25.67 40.837 25.699 40.716 25.649 L 36.187 23.736 C 35.782 23.568 35.335 23.856 35.319 24.294 L 35.153 28.968 C 35.147 29.127 35.022 29.256 34.863 29.268 L 30.728 29.536 C 30.249 29.566 29.983 30.103 30.248 30.502 C 30.25 30.504 30.251 30.506 30.252 30.508 L 33.002 34.562 C 33.083 34.679 33.075 34.836 32.982 34.944 L 30.19 38.15 C 29.91 38.468 30.025 38.968 30.417 39.132 L 33.623 40.518 C 33.765 40.576 33.84 40.731 33.799 40.879 L 31.751 48.883 C 31.632 49.349 32.062 49.769 32.525 49.639 C 32.596 49.619 32.663 49.587 32.723 49.544 L 40.416 44.023 C 40.524 43.945 40.67 43.945 40.778 44.023 L 44.284 46.483 C 44.573 46.69 44.966 46.608 45.162 46.318 L 47.355 42.958 C 47.418 42.856 47.537 42.802 47.655 42.823 L 52.897 43.454 C 53.321 43.516 53.745 43.164 53.59 42.772 L 51.408 37.126 C 51.346 36.971 51.408 36.806 51.594 36.712 L 55.574 34.83 C 56.164 34.53 55.957 33.62 55.285 33.62 L 55.285 33.621 Z" fill="#9069E7"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
1079
packages/nodes-base/nodes/Gong/test/Gong.node.test.ts
Normal file
1079
packages/nodes-base/nodes/Gong/test/Gong.node.test.ts
Normal file
File diff suppressed because it is too large
Load diff
781
packages/nodes-base/nodes/Gong/test/mocks.ts
Normal file
781
packages/nodes-base/nodes/Gong/test/mocks.ts
Normal file
|
@ -0,0 +1,781 @@
|
||||||
|
/* eslint-disable n8n-nodes-base/node-param-display-name-miscased */
|
||||||
|
export const gongApiResponse = {
|
||||||
|
// https://gong.app.gong.io/settings/api/documentation#post-/v2/calls
|
||||||
|
postCalls: {
|
||||||
|
requestId: '4al018gzaztcr8nbukw',
|
||||||
|
callId: '7782342274025937895',
|
||||||
|
},
|
||||||
|
// https://gong.app.gong.io/settings/api/documentation#put-/v2/calls/-id-/media
|
||||||
|
postCallsMedia: {
|
||||||
|
requestId: '4al018gzaztcr8nbukw',
|
||||||
|
callId: '7782342274025937895',
|
||||||
|
url: 'https://app.gong.io/call?id=7782342274025937895',
|
||||||
|
},
|
||||||
|
// https://gong.app.gong.io/settings/api/documentation#post-/v2/calls/extensive
|
||||||
|
postCallsExtensive: {
|
||||||
|
requestId: '4al018gzaztcr8nbukw',
|
||||||
|
records: {
|
||||||
|
totalRecords: 263,
|
||||||
|
currentPageSize: 100,
|
||||||
|
currentPageNumber: 0,
|
||||||
|
cursor: 'eyJhbGciOiJIUzI1NiJ9.eyJjYWxsSWQiM1M30.6qKwpOcvnuweTZmFRzYdtjs_YwJphJU4QIwWFM',
|
||||||
|
},
|
||||||
|
calls: [
|
||||||
|
{
|
||||||
|
metaData: {
|
||||||
|
id: '7782342274025937895',
|
||||||
|
url: 'https://app.gong.io/call?id=7782342274025937895',
|
||||||
|
title: 'Example call',
|
||||||
|
scheduled: 1518863400,
|
||||||
|
started: 1518863400,
|
||||||
|
duration: 460,
|
||||||
|
primaryUserId: '234599484848423',
|
||||||
|
direction: 'Inbound',
|
||||||
|
system: 'Outreach',
|
||||||
|
scope: 'Internal',
|
||||||
|
media: 'Video',
|
||||||
|
language: 'eng',
|
||||||
|
workspaceId: '623457276584334',
|
||||||
|
sdrDisposition: 'Got the gatekeeper',
|
||||||
|
clientUniqueId: '7JEHFRGXDDZFEW2FC4U',
|
||||||
|
customData: 'Conference Call',
|
||||||
|
purpose: 'Demo Call',
|
||||||
|
meetingUrl: 'https://zoom.us/j/123',
|
||||||
|
isPrivate: false,
|
||||||
|
calendarEventId: 'abcde@google.com',
|
||||||
|
},
|
||||||
|
context: [
|
||||||
|
{
|
||||||
|
system: 'Salesforce',
|
||||||
|
objects: [
|
||||||
|
{
|
||||||
|
objectType: 'Opportunity',
|
||||||
|
objectId: '0013601230sV7grAAC',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
value: 'Gong Inc.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timing: 'Now',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
parties: [
|
||||||
|
{
|
||||||
|
id: '56825452554556',
|
||||||
|
emailAddress: 'test@test.com',
|
||||||
|
name: 'Test User',
|
||||||
|
title: 'Enterprise Account Executive',
|
||||||
|
userId: '234599484848423',
|
||||||
|
speakerId: '6432345678555530067',
|
||||||
|
context: [
|
||||||
|
{
|
||||||
|
system: 'Salesforce',
|
||||||
|
objects: [
|
||||||
|
{
|
||||||
|
objectType: 'Contact',
|
||||||
|
objectId: '0013601230sV7grAAC',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
value: 'Gong Inc.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timing: 'Now',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
affiliation: 'Internal',
|
||||||
|
phoneNumber: '+1 123-567-8989',
|
||||||
|
methods: ['Invitee'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
content: {
|
||||||
|
structure: [
|
||||||
|
{
|
||||||
|
name: 'Meeting Setup',
|
||||||
|
duration: 67,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
trackers: [
|
||||||
|
{
|
||||||
|
id: '56825452554556',
|
||||||
|
name: 'Competitors',
|
||||||
|
count: 7,
|
||||||
|
type: 'KEYWORD',
|
||||||
|
occurrences: [
|
||||||
|
{
|
||||||
|
startTime: 32.56,
|
||||||
|
speakerId: '234599484848423',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
phrases: [
|
||||||
|
{
|
||||||
|
count: 5,
|
||||||
|
occurrences: [
|
||||||
|
{
|
||||||
|
startTime: 32.56,
|
||||||
|
speakerId: '234599484848423',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
phrase: 'Walmart',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
topics: [
|
||||||
|
{
|
||||||
|
name: 'Objections',
|
||||||
|
duration: 86,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pointsOfInterest: {
|
||||||
|
actionItems: [
|
||||||
|
{
|
||||||
|
snippetStartTime: 26,
|
||||||
|
snippetEndTime: 26,
|
||||||
|
speakerID: '56825452554556',
|
||||||
|
snippet:
|
||||||
|
"And I'll send you an invite with a link that you can use at that time as well.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
brief: 'string',
|
||||||
|
outline: [
|
||||||
|
{
|
||||||
|
section: 'string',
|
||||||
|
startTime: 0.5,
|
||||||
|
duration: 0.5,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'string',
|
||||||
|
startTime: 0.5,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
highlights: [
|
||||||
|
{
|
||||||
|
title: 'string',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'string',
|
||||||
|
startTimes: [0.5],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
callOutcome: {
|
||||||
|
id: 'MEETING_BOOKED',
|
||||||
|
category: 'Answered',
|
||||||
|
name: 'Meeting booked',
|
||||||
|
},
|
||||||
|
keyPoints: [
|
||||||
|
{
|
||||||
|
text: 'string',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
interaction: {
|
||||||
|
speakers: [
|
||||||
|
{
|
||||||
|
id: '56825452554556',
|
||||||
|
userId: '234599484848423',
|
||||||
|
talkTime: 145,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
interactionStats: [
|
||||||
|
{
|
||||||
|
name: 'Interactivity',
|
||||||
|
value: 56,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
video: [
|
||||||
|
{
|
||||||
|
name: 'Browser',
|
||||||
|
duration: 218,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
questions: {
|
||||||
|
companyCount: 0,
|
||||||
|
nonCompanyCount: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
collaboration: {
|
||||||
|
publicComments: [
|
||||||
|
{
|
||||||
|
id: '6843152929075440037',
|
||||||
|
audioStartTime: 26,
|
||||||
|
audioEndTime: 26,
|
||||||
|
commenterUserId: '234599484848423',
|
||||||
|
comment: 'new comment',
|
||||||
|
posted: 1518863400,
|
||||||
|
inReplyTo: '792390015966656336',
|
||||||
|
duringCall: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
audioUrl: 'http://example.com',
|
||||||
|
videoUrl: 'http://example.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// https://gong.app.gong.io/settings/api/documentation#post-/v2/calls/transcript
|
||||||
|
postCallsTranscript: {
|
||||||
|
requestId: '4al018gzaztcr8nbukw',
|
||||||
|
records: {
|
||||||
|
totalRecords: 1,
|
||||||
|
currentPageSize: 1,
|
||||||
|
currentPageNumber: 0,
|
||||||
|
},
|
||||||
|
callTranscripts: [
|
||||||
|
{
|
||||||
|
callId: '7782342274025937895',
|
||||||
|
transcript: [
|
||||||
|
{
|
||||||
|
speakerId: '6432345678555530067',
|
||||||
|
topic: 'Objections',
|
||||||
|
sentences: [
|
||||||
|
{
|
||||||
|
start: 460230,
|
||||||
|
end: 462343,
|
||||||
|
text: 'No wait, I think we should check that out first.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// https://gong.app.gong.io/settings/api/documentation#post-/v2/users/extensive
|
||||||
|
postUsersExtensive: {
|
||||||
|
requestId: '4al018gzaztcr8nbukw',
|
||||||
|
records: {
|
||||||
|
totalRecords: 263,
|
||||||
|
currentPageSize: 100,
|
||||||
|
currentPageNumber: 0,
|
||||||
|
cursor: 'eyJhbGciOiJIUzI1NiJ9.eyJjYWxsSWQiM1M30.6qKwpOcvnuweTZmFRzYdtjs_YwJphJU4QIwWFM',
|
||||||
|
},
|
||||||
|
users: [
|
||||||
|
{
|
||||||
|
id: '234599484848423',
|
||||||
|
emailAddress: 'test@test.com',
|
||||||
|
created: '2018-02-17T02:30:00-08:00',
|
||||||
|
active: true,
|
||||||
|
emailAliases: ['testAlias@test.com'],
|
||||||
|
trustedEmailAddress: 'test@test.com',
|
||||||
|
firstName: 'Jon',
|
||||||
|
lastName: 'Snow',
|
||||||
|
title: 'Enterprise Account Executive',
|
||||||
|
phoneNumber: '+1 123-567-8989',
|
||||||
|
extension: '123',
|
||||||
|
personalMeetingUrls: ['https://zoom.us/j/123'],
|
||||||
|
settings: {
|
||||||
|
webConferencesRecorded: true,
|
||||||
|
preventWebConferenceRecording: false,
|
||||||
|
telephonyCallsImported: false,
|
||||||
|
emailsImported: true,
|
||||||
|
preventEmailImport: false,
|
||||||
|
nonRecordedMeetingsImported: true,
|
||||||
|
gongConnectEnabled: true,
|
||||||
|
},
|
||||||
|
managerId: '563515258458745',
|
||||||
|
meetingConsentPageUrl:
|
||||||
|
'https://join.gong.io/my-company/jon.snow?tkn=MoNpS9tMNt8BK7EZxQpSJl',
|
||||||
|
spokenLanguages: [
|
||||||
|
{
|
||||||
|
language: 'es-ES',
|
||||||
|
primary: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const gongNodeResponse = {
|
||||||
|
getCall: [
|
||||||
|
{
|
||||||
|
json: {
|
||||||
|
metaData: {
|
||||||
|
id: '7782342274025937895',
|
||||||
|
url: 'https://app.gong.io/call?id=7782342274025937895',
|
||||||
|
title: 'Example call',
|
||||||
|
scheduled: 1518863400,
|
||||||
|
started: 1518863400,
|
||||||
|
duration: 460,
|
||||||
|
primaryUserId: '234599484848423',
|
||||||
|
direction: 'Inbound',
|
||||||
|
system: 'Outreach',
|
||||||
|
scope: 'Internal',
|
||||||
|
media: 'Video',
|
||||||
|
language: 'eng',
|
||||||
|
workspaceId: '623457276584334',
|
||||||
|
sdrDisposition: 'Got the gatekeeper',
|
||||||
|
clientUniqueId: '7JEHFRGXDDZFEW2FC4U',
|
||||||
|
customData: 'Conference Call',
|
||||||
|
purpose: 'Demo Call',
|
||||||
|
meetingUrl: 'https://zoom.us/j/123',
|
||||||
|
isPrivate: false,
|
||||||
|
calendarEventId: 'abcde@google.com',
|
||||||
|
},
|
||||||
|
context: [
|
||||||
|
{
|
||||||
|
system: 'Salesforce',
|
||||||
|
objects: [
|
||||||
|
{
|
||||||
|
objectType: 'Opportunity',
|
||||||
|
objectId: '0013601230sV7grAAC',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
value: 'Gong Inc.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timing: 'Now',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
parties: [
|
||||||
|
{
|
||||||
|
id: '56825452554556',
|
||||||
|
emailAddress: 'test@test.com',
|
||||||
|
name: 'Test User',
|
||||||
|
title: 'Enterprise Account Executive',
|
||||||
|
userId: '234599484848423',
|
||||||
|
speakerId: '6432345678555530067',
|
||||||
|
context: [
|
||||||
|
{
|
||||||
|
system: 'Salesforce',
|
||||||
|
objects: [
|
||||||
|
{
|
||||||
|
objectType: 'Contact',
|
||||||
|
objectId: '0013601230sV7grAAC',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
value: 'Gong Inc.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timing: 'Now',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
affiliation: 'Internal',
|
||||||
|
phoneNumber: '+1 123-567-8989',
|
||||||
|
methods: ['Invitee'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
content: {
|
||||||
|
structure: [
|
||||||
|
{
|
||||||
|
name: 'Meeting Setup',
|
||||||
|
duration: 67,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
trackers: [
|
||||||
|
{
|
||||||
|
id: '56825452554556',
|
||||||
|
name: 'Competitors',
|
||||||
|
count: 7,
|
||||||
|
type: 'KEYWORD',
|
||||||
|
occurrences: [
|
||||||
|
{
|
||||||
|
startTime: 32.56,
|
||||||
|
speakerId: '234599484848423',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
phrases: [
|
||||||
|
{
|
||||||
|
count: 5,
|
||||||
|
occurrences: [
|
||||||
|
{
|
||||||
|
startTime: 32.56,
|
||||||
|
speakerId: '234599484848423',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
phrase: 'Walmart',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
topics: [
|
||||||
|
{
|
||||||
|
name: 'Objections',
|
||||||
|
duration: 86,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pointsOfInterest: {
|
||||||
|
actionItems: [
|
||||||
|
{
|
||||||
|
snippetStartTime: 26,
|
||||||
|
snippetEndTime: 26,
|
||||||
|
speakerID: '56825452554556',
|
||||||
|
snippet:
|
||||||
|
"And I'll send you an invite with a link that you can use at that time as well.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
brief: 'string',
|
||||||
|
outline: [
|
||||||
|
{
|
||||||
|
section: 'string',
|
||||||
|
startTime: 0.5,
|
||||||
|
duration: 0.5,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'string',
|
||||||
|
startTime: 0.5,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
highlights: [
|
||||||
|
{
|
||||||
|
title: 'string',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'string',
|
||||||
|
startTimes: [0.5],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
callOutcome: {
|
||||||
|
id: 'MEETING_BOOKED',
|
||||||
|
category: 'Answered',
|
||||||
|
name: 'Meeting booked',
|
||||||
|
},
|
||||||
|
keyPoints: [
|
||||||
|
{
|
||||||
|
text: 'string',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
interaction: {
|
||||||
|
speakers: [
|
||||||
|
{
|
||||||
|
id: '56825452554556',
|
||||||
|
userId: '234599484848423',
|
||||||
|
talkTime: 145,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
interactionStats: [
|
||||||
|
{
|
||||||
|
name: 'Interactivity',
|
||||||
|
value: 56,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
video: [
|
||||||
|
{
|
||||||
|
name: 'Browser',
|
||||||
|
duration: 218,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
questions: {
|
||||||
|
companyCount: 0,
|
||||||
|
nonCompanyCount: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
collaboration: {
|
||||||
|
publicComments: [
|
||||||
|
{
|
||||||
|
id: '6843152929075440037',
|
||||||
|
audioStartTime: 26,
|
||||||
|
audioEndTime: 26,
|
||||||
|
commenterUserId: '234599484848423',
|
||||||
|
comment: 'new comment',
|
||||||
|
posted: 1518863400,
|
||||||
|
inReplyTo: '792390015966656336',
|
||||||
|
duringCall: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
audioUrl: 'http://example.com',
|
||||||
|
videoUrl: 'http://example.com',
|
||||||
|
},
|
||||||
|
transcript: [
|
||||||
|
{
|
||||||
|
speakerId: '6432345678555530067',
|
||||||
|
topic: 'Objections',
|
||||||
|
sentences: [
|
||||||
|
{
|
||||||
|
start: 460230,
|
||||||
|
end: 462343,
|
||||||
|
text: 'No wait, I think we should check that out first.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
getAllCall: [
|
||||||
|
{
|
||||||
|
json: {
|
||||||
|
id: '7782342274025937895',
|
||||||
|
url: 'https://app.gong.io/call?id=7782342274025937895',
|
||||||
|
title: 'Example call',
|
||||||
|
scheduled: 1518863400,
|
||||||
|
started: 1518863400,
|
||||||
|
duration: 460,
|
||||||
|
primaryUserId: '234599484848423',
|
||||||
|
direction: 'Inbound',
|
||||||
|
system: 'Outreach',
|
||||||
|
scope: 'Internal',
|
||||||
|
media: 'Video',
|
||||||
|
language: 'eng',
|
||||||
|
workspaceId: '623457276584334',
|
||||||
|
sdrDisposition: 'Got the gatekeeper',
|
||||||
|
clientUniqueId: '7JEHFRGXDDZFEW2FC4U',
|
||||||
|
customData: 'Conference Call',
|
||||||
|
purpose: 'Demo Call',
|
||||||
|
meetingUrl: 'https://zoom.us/j/123',
|
||||||
|
isPrivate: false,
|
||||||
|
calendarEventId: 'abcde@google.com',
|
||||||
|
content: {
|
||||||
|
topics: [
|
||||||
|
{
|
||||||
|
name: 'Objections',
|
||||||
|
duration: 86,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
parties: [
|
||||||
|
{
|
||||||
|
id: '56825452554556',
|
||||||
|
emailAddress: 'test@test.com',
|
||||||
|
name: 'Test User',
|
||||||
|
title: 'Enterprise Account Executive',
|
||||||
|
userId: '234599484848423',
|
||||||
|
speakerId: '6432345678555530067',
|
||||||
|
context: [
|
||||||
|
{
|
||||||
|
system: 'Salesforce',
|
||||||
|
objects: [
|
||||||
|
{
|
||||||
|
objectType: 'Contact',
|
||||||
|
objectId: '0013601230sV7grAAC',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
value: 'Gong Inc.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timing: 'Now',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
affiliation: 'Internal',
|
||||||
|
phoneNumber: '+1 123-567-8989',
|
||||||
|
methods: ['Invitee'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
json: {
|
||||||
|
id: '7782342274025937896',
|
||||||
|
url: 'https://app.gong.io/call?id=7782342274025937896',
|
||||||
|
title: 'Example call',
|
||||||
|
scheduled: 1518863400,
|
||||||
|
started: 1518863400,
|
||||||
|
duration: 460,
|
||||||
|
primaryUserId: '234599484848423',
|
||||||
|
direction: 'Inbound',
|
||||||
|
system: 'Outreach',
|
||||||
|
scope: 'Internal',
|
||||||
|
media: 'Video',
|
||||||
|
language: 'eng',
|
||||||
|
workspaceId: '623457276584334',
|
||||||
|
sdrDisposition: 'Got the gatekeeper',
|
||||||
|
clientUniqueId: '7JEHFRGXDDZFEW2FC4U',
|
||||||
|
customData: 'Conference Call',
|
||||||
|
purpose: 'Demo Call',
|
||||||
|
meetingUrl: 'https://zoom.us/j/123',
|
||||||
|
isPrivate: false,
|
||||||
|
calendarEventId: 'abcde@google.com',
|
||||||
|
content: {
|
||||||
|
topics: [
|
||||||
|
{
|
||||||
|
name: 'Objections',
|
||||||
|
duration: 86,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
parties: [
|
||||||
|
{
|
||||||
|
id: '56825452554556',
|
||||||
|
emailAddress: 'test@test.com',
|
||||||
|
name: 'Test User',
|
||||||
|
title: 'Enterprise Account Executive',
|
||||||
|
userId: '234599484848423',
|
||||||
|
speakerId: '6432345678555530067',
|
||||||
|
context: [
|
||||||
|
{
|
||||||
|
system: 'Salesforce',
|
||||||
|
objects: [
|
||||||
|
{
|
||||||
|
objectType: 'Contact',
|
||||||
|
objectId: '0013601230sV7grAAC',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
value: 'Gong Inc.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timing: 'Now',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
affiliation: 'Internal',
|
||||||
|
phoneNumber: '+1 123-567-8989',
|
||||||
|
methods: ['Invitee'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
getAllCallNoOptions: [
|
||||||
|
{
|
||||||
|
json: {
|
||||||
|
id: '7782342274025937895',
|
||||||
|
url: 'https://app.gong.io/call?id=7782342274025937895',
|
||||||
|
title: 'Example call',
|
||||||
|
scheduled: 1518863400,
|
||||||
|
started: 1518863400,
|
||||||
|
duration: 460,
|
||||||
|
primaryUserId: '234599484848423',
|
||||||
|
direction: 'Inbound',
|
||||||
|
system: 'Outreach',
|
||||||
|
scope: 'Internal',
|
||||||
|
media: 'Video',
|
||||||
|
language: 'eng',
|
||||||
|
workspaceId: '623457276584334',
|
||||||
|
sdrDisposition: 'Got the gatekeeper',
|
||||||
|
clientUniqueId: '7JEHFRGXDDZFEW2FC4U',
|
||||||
|
customData: 'Conference Call',
|
||||||
|
purpose: 'Demo Call',
|
||||||
|
meetingUrl: 'https://zoom.us/j/123',
|
||||||
|
isPrivate: false,
|
||||||
|
calendarEventId: 'abcde@google.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
getUser: [
|
||||||
|
{
|
||||||
|
json: {
|
||||||
|
id: '234599484848423',
|
||||||
|
emailAddress: 'test@test.com',
|
||||||
|
created: '2018-02-17T02:30:00-08:00',
|
||||||
|
active: true,
|
||||||
|
emailAliases: ['testAlias@test.com'],
|
||||||
|
trustedEmailAddress: 'test@test.com',
|
||||||
|
firstName: 'Jon',
|
||||||
|
lastName: 'Snow',
|
||||||
|
title: 'Enterprise Account Executive',
|
||||||
|
phoneNumber: '+1 123-567-8989',
|
||||||
|
extension: '123',
|
||||||
|
personalMeetingUrls: ['https://zoom.us/j/123'],
|
||||||
|
settings: {
|
||||||
|
webConferencesRecorded: true,
|
||||||
|
preventWebConferenceRecording: false,
|
||||||
|
telephonyCallsImported: false,
|
||||||
|
emailsImported: true,
|
||||||
|
preventEmailImport: false,
|
||||||
|
nonRecordedMeetingsImported: true,
|
||||||
|
gongConnectEnabled: true,
|
||||||
|
},
|
||||||
|
managerId: '563515258458745',
|
||||||
|
meetingConsentPageUrl:
|
||||||
|
'https://join.gong.io/my-company/jon.snow?tkn=MoNpS9tMNt8BK7EZxQpSJl',
|
||||||
|
spokenLanguages: [
|
||||||
|
{
|
||||||
|
language: 'es-ES',
|
||||||
|
primary: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
getAllUser: [
|
||||||
|
{
|
||||||
|
json: {
|
||||||
|
id: '234599484848423',
|
||||||
|
emailAddress: 'test@test.com',
|
||||||
|
created: '2018-02-17T02:30:00-08:00',
|
||||||
|
active: true,
|
||||||
|
emailAliases: ['testAlias@test.com'],
|
||||||
|
trustedEmailAddress: 'test@test.com',
|
||||||
|
firstName: 'Jon',
|
||||||
|
lastName: 'Snow',
|
||||||
|
title: 'Enterprise Account Executive',
|
||||||
|
phoneNumber: '+1 123-567-8989',
|
||||||
|
extension: '123',
|
||||||
|
personalMeetingUrls: ['https://zoom.us/j/123'],
|
||||||
|
settings: {
|
||||||
|
webConferencesRecorded: true,
|
||||||
|
preventWebConferenceRecording: false,
|
||||||
|
telephonyCallsImported: false,
|
||||||
|
emailsImported: true,
|
||||||
|
preventEmailImport: false,
|
||||||
|
nonRecordedMeetingsImported: true,
|
||||||
|
gongConnectEnabled: true,
|
||||||
|
},
|
||||||
|
managerId: '563515258458745',
|
||||||
|
meetingConsentPageUrl:
|
||||||
|
'https://join.gong.io/my-company/jon.snow?tkn=MoNpS9tMNt8BK7EZxQpSJl',
|
||||||
|
spokenLanguages: [
|
||||||
|
{
|
||||||
|
language: 'es-ES',
|
||||||
|
primary: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
json: {
|
||||||
|
id: '234599484848424',
|
||||||
|
emailAddress: 'test@test.com',
|
||||||
|
created: '2018-02-17T02:30:00-08:00',
|
||||||
|
active: true,
|
||||||
|
emailAliases: ['testAlias@test.com'],
|
||||||
|
trustedEmailAddress: 'test@test.com',
|
||||||
|
firstName: 'Jon',
|
||||||
|
lastName: 'Snow',
|
||||||
|
title: 'Enterprise Account Executive',
|
||||||
|
phoneNumber: '+1 123-567-8989',
|
||||||
|
extension: '123',
|
||||||
|
personalMeetingUrls: ['https://zoom.us/j/123'],
|
||||||
|
settings: {
|
||||||
|
webConferencesRecorded: true,
|
||||||
|
preventWebConferenceRecording: false,
|
||||||
|
telephonyCallsImported: false,
|
||||||
|
emailsImported: true,
|
||||||
|
preventEmailImport: false,
|
||||||
|
nonRecordedMeetingsImported: true,
|
||||||
|
gongConnectEnabled: true,
|
||||||
|
},
|
||||||
|
managerId: '563515258458745',
|
||||||
|
meetingConsentPageUrl:
|
||||||
|
'https://join.gong.io/my-company/jon.snow?tkn=MoNpS9tMNt8BK7EZxQpSJl',
|
||||||
|
spokenLanguages: [
|
||||||
|
{
|
||||||
|
language: 'es-ES',
|
||||||
|
primary: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
|
@ -125,6 +125,8 @@
|
||||||
"dist/credentials/GitlabOAuth2Api.credentials.js",
|
"dist/credentials/GitlabOAuth2Api.credentials.js",
|
||||||
"dist/credentials/GitPassword.credentials.js",
|
"dist/credentials/GitPassword.credentials.js",
|
||||||
"dist/credentials/GmailOAuth2Api.credentials.js",
|
"dist/credentials/GmailOAuth2Api.credentials.js",
|
||||||
|
"dist/credentials/GongApi.credentials.js",
|
||||||
|
"dist/credentials/GongOAuth2Api.credentials.js",
|
||||||
"dist/credentials/GoogleAdsOAuth2Api.credentials.js",
|
"dist/credentials/GoogleAdsOAuth2Api.credentials.js",
|
||||||
"dist/credentials/GoogleAnalyticsOAuth2Api.credentials.js",
|
"dist/credentials/GoogleAnalyticsOAuth2Api.credentials.js",
|
||||||
"dist/credentials/GoogleApi.credentials.js",
|
"dist/credentials/GoogleApi.credentials.js",
|
||||||
|
@ -517,6 +519,7 @@
|
||||||
"dist/nodes/Github/GithubTrigger.node.js",
|
"dist/nodes/Github/GithubTrigger.node.js",
|
||||||
"dist/nodes/Gitlab/Gitlab.node.js",
|
"dist/nodes/Gitlab/Gitlab.node.js",
|
||||||
"dist/nodes/Gitlab/GitlabTrigger.node.js",
|
"dist/nodes/Gitlab/GitlabTrigger.node.js",
|
||||||
|
"dist/nodes/Gong/Gong.node.js",
|
||||||
"dist/nodes/Google/Ads/GoogleAds.node.js",
|
"dist/nodes/Google/Ads/GoogleAds.node.js",
|
||||||
"dist/nodes/Google/Analytics/GoogleAnalytics.node.js",
|
"dist/nodes/Google/Analytics/GoogleAnalytics.node.js",
|
||||||
"dist/nodes/Google/BigQuery/GoogleBigQuery.node.js",
|
"dist/nodes/Google/BigQuery/GoogleBigQuery.node.js",
|
||||||
|
|
|
@ -54,6 +54,32 @@ BQIDAQAB
|
||||||
airtableApi: {
|
airtableApi: {
|
||||||
apiKey: 'key123',
|
apiKey: 'key123',
|
||||||
},
|
},
|
||||||
|
gongApi: {
|
||||||
|
baseUrl: 'https://api.gong.io',
|
||||||
|
accessKey: 'accessKey123',
|
||||||
|
accessKeySecret: 'accessKeySecret123',
|
||||||
|
},
|
||||||
|
gongOAuth2Api: {
|
||||||
|
grantType: 'authorizationCode',
|
||||||
|
authUrl: 'https://app.gong.io/oauth2/authorize',
|
||||||
|
accessTokenUrl: 'https://app.gong.io/oauth2/generate-customer-token',
|
||||||
|
clientId: 'CLIENTID',
|
||||||
|
clientSecret: 'CLIENTSECRET',
|
||||||
|
scope:
|
||||||
|
'api:calls:read:transcript api:provisioning:read api:workspaces:read api:meetings:user:delete api:crm:get-objects api:data-privacy:delete api:crm:schema api:flows:write api:crm:upload api:meetings:integration:status api:calls:read:extensive api:meetings:user:update api:integration-settings:write api:settings:scorecards:read api:stats:scorecards api:stats:interaction api:stats:user-actions api:crm:integration:delete api:calls:read:basic api:calls:read:media-url api:digital-interactions:write api:crm:integrations:read api:library:read api:data-privacy:read api:users:read api:logs:read api:calls:create api:meetings:user:create api:stats:user-actions:detailed api:settings:trackers:read api:crm:integration:register api:provisioning:read-write api:engagement-data:write api:permission-profile:read api:permission-profile:write api:flows:read api:crm-calls:manual-association:read',
|
||||||
|
authQueryParameters: '',
|
||||||
|
authentication: 'header',
|
||||||
|
oauthTokenData: {
|
||||||
|
access_token: 'ACCESSTOKEN',
|
||||||
|
refresh_token: 'REFRESHTOKEN',
|
||||||
|
scope:
|
||||||
|
'api:calls:read:transcript api:provisioning:read api:workspaces:read api:meetings:user:delete api:crm:get-objects api:data-privacy:delete api:crm:schema api:flows:write api:crm:upload api:meetings:integration:status api:calls:read:extensive api:meetings:user:update api:integration-settings:write api:settings:scorecards:read api:stats:scorecards api:stats:interaction api:stats:user-actions api:crm:integration:delete api:calls:read:basic api:calls:read:media-url api:digital-interactions:write api:crm:integrations:read api:library:read api:data-privacy:read api:users:read api:logs:read api:calls:create api:meetings:user:create api:stats:user-actions:detailed api:settings:trackers:read api:crm:integration:register api:provisioning:read-write api:engagement-data:write api:permission-profile:read api:permission-profile:write api:flows:read api:crm-calls:manual-association:read',
|
||||||
|
token_type: 'bearer',
|
||||||
|
expires_in: 86400,
|
||||||
|
api_base_url_for_customer: 'https://api.gong.io',
|
||||||
|
},
|
||||||
|
baseUrl: 'https://api.gong.io',
|
||||||
|
},
|
||||||
n8nApi: {
|
n8nApi: {
|
||||||
apiKey: 'key123',
|
apiKey: 'key123',
|
||||||
baseUrl: 'https://test.app.n8n.cloud/api/v1',
|
baseUrl: 'https://test.app.n8n.cloud/api/v1',
|
||||||
|
|
|
@ -94,7 +94,7 @@ class CredentialType implements ICredentialTypes {
|
||||||
|
|
||||||
const credentialTypes = new CredentialType();
|
const credentialTypes = new CredentialType();
|
||||||
|
|
||||||
class CredentialsHelper extends ICredentialsHelper {
|
export class CredentialsHelper extends ICredentialsHelper {
|
||||||
getCredentialsProperties() {
|
getCredentialsProperties() {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -167,6 +167,8 @@ export function WorkflowExecuteAdditionalData(
|
||||||
return mock<IWorkflowExecuteAdditionalData>({
|
return mock<IWorkflowExecuteAdditionalData>({
|
||||||
credentialsHelper: new CredentialsHelper(),
|
credentialsHelper: new CredentialsHelper(),
|
||||||
hooks: new WorkflowHooks(hookFunctions, 'trigger', '1', mock()),
|
hooks: new WorkflowHooks(hookFunctions, 'trigger', '1', mock()),
|
||||||
|
// Get from node.parameters
|
||||||
|
currentNodeParameters: undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2377,7 +2377,7 @@ export interface WorkflowTestData {
|
||||||
nock?: {
|
nock?: {
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
mocks: Array<{
|
mocks: Array<{
|
||||||
method: 'get' | 'post';
|
method: 'delete' | 'get' | 'post' | 'put';
|
||||||
path: string;
|
path: string;
|
||||||
requestBody?: RequestBodyMatcher;
|
requestBody?: RequestBodyMatcher;
|
||||||
statusCode: number;
|
statusCode: number;
|
||||||
|
|
|
@ -41,6 +41,7 @@ import type {
|
||||||
PostReceiveAction,
|
PostReceiveAction,
|
||||||
JsonObject,
|
JsonObject,
|
||||||
CloseFunction,
|
CloseFunction,
|
||||||
|
INodeCredentialDescription,
|
||||||
} from './Interfaces';
|
} from './Interfaces';
|
||||||
import * as NodeHelpers from './NodeHelpers';
|
import * as NodeHelpers from './NodeHelpers';
|
||||||
import { sleep } from './utils';
|
import { sleep } from './utils';
|
||||||
|
@ -88,11 +89,6 @@ export class RoutingNode {
|
||||||
const items = inputData.main[0] as INodeExecutionData[];
|
const items = inputData.main[0] as INodeExecutionData[];
|
||||||
const returnData: INodeExecutionData[] = [];
|
const returnData: INodeExecutionData[] = [];
|
||||||
|
|
||||||
let credentialType: string | undefined;
|
|
||||||
|
|
||||||
if (nodeType.description.credentials?.length) {
|
|
||||||
credentialType = nodeType.description.credentials[0].name;
|
|
||||||
}
|
|
||||||
const closeFunctions: CloseFunction[] = [];
|
const closeFunctions: CloseFunction[] = [];
|
||||||
const executeFunctions = nodeExecuteFunctions.getExecuteFunctions(
|
const executeFunctions = nodeExecuteFunctions.getExecuteFunctions(
|
||||||
this.workflow,
|
this.workflow,
|
||||||
|
@ -108,24 +104,45 @@ export class RoutingNode {
|
||||||
abortSignal,
|
abortSignal,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let credentialDescription: INodeCredentialDescription | undefined;
|
||||||
|
|
||||||
|
if (nodeType.description.credentials?.length) {
|
||||||
|
if (nodeType.description.credentials.length === 1) {
|
||||||
|
credentialDescription = nodeType.description.credentials[0];
|
||||||
|
} else {
|
||||||
|
const authenticationMethod = executeFunctions.getNodeParameter(
|
||||||
|
'authentication',
|
||||||
|
0,
|
||||||
|
) as string;
|
||||||
|
credentialDescription = nodeType.description.credentials.find((x) =>
|
||||||
|
x.displayOptions?.show?.authentication?.includes(authenticationMethod),
|
||||||
|
);
|
||||||
|
if (!credentialDescription) {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.node,
|
||||||
|
`Node type "${this.node.type}" does not have any credentials of type "${authenticationMethod}" defined`,
|
||||||
|
{ level: 'warning' },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let credentials: ICredentialDataDecryptedObject | undefined;
|
let credentials: ICredentialDataDecryptedObject | undefined;
|
||||||
if (credentialsDecrypted) {
|
if (credentialsDecrypted) {
|
||||||
credentials = credentialsDecrypted.data;
|
credentials = credentialsDecrypted.data;
|
||||||
} else if (credentialType) {
|
} else if (credentialDescription) {
|
||||||
try {
|
try {
|
||||||
credentials =
|
credentials =
|
||||||
(await executeFunctions.getCredentials<ICredentialDataDecryptedObject>(credentialType)) ||
|
(await executeFunctions.getCredentials<ICredentialDataDecryptedObject>(
|
||||||
{};
|
credentialDescription.name,
|
||||||
|
)) || {};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (
|
if (credentialDescription.required) {
|
||||||
nodeType.description.credentials?.length &&
|
|
||||||
nodeType.description.credentials[0].required
|
|
||||||
) {
|
|
||||||
// Only throw error if credential is mandatory
|
// Only throw error if credential is mandatory
|
||||||
throw error;
|
throw error;
|
||||||
} else {
|
} else {
|
||||||
// Do not request cred type since it doesn't exist
|
// Do not request cred type since it doesn't exist
|
||||||
credentialType = undefined;
|
credentialDescription = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -282,7 +299,7 @@ export class RoutingNode {
|
||||||
itemContext[itemIndex].thisArgs,
|
itemContext[itemIndex].thisArgs,
|
||||||
itemIndex,
|
itemIndex,
|
||||||
runIndex,
|
runIndex,
|
||||||
credentialType,
|
credentialDescription?.name,
|
||||||
itemContext[itemIndex].requestData.requestOperations,
|
itemContext[itemIndex].requestData.requestOperations,
|
||||||
credentialsDecrypted,
|
credentialsDecrypted,
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in a new issue