Add document:upload operation to Salesforce Node (#2030)

This commit is contained in:
Ricardo Espinoza 2021-07-29 08:11:04 -04:00 committed by GitHub
parent 1faaef1171
commit 9a7c25aacd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 192 additions and 3 deletions

View file

@ -1,9 +1,9 @@
export interface IAttachment { export interface IAttachment {
ParentId?: string; ParentId?: string;
Name?: string; Name?: string;
Body?: string;
OwnerId?: string; OwnerId?: string;
IsPrivate?: boolean; IsPrivate?: boolean;
ContentType?: string; ContentType?: string;
Description?: string; Description?: string;
Body?: string;
} }

View file

@ -0,0 +1,107 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const documentOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'document',
],
},
},
options: [
{
name: 'Upload',
value: 'upload',
description: 'Upload a document',
},
],
default: 'upload',
description: 'The operation to perform.',
},
] as INodeProperties[];
export const documentFields = [
/* -------------------------------------------------------------------------- */
/* document:upload */
/* -------------------------------------------------------------------------- */
{
displayName: 'Title',
name: 'title',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'document',
],
operation: [
'upload',
],
},
},
description: 'Name of the file',
},
{
displayName: 'Binary Property',
name: 'binaryPropertyName',
type: 'string',
default: 'data',
required: true,
displayOptions: {
show: {
resource: [
'document',
],
operation: [
'upload',
],
},
},
placeholder: '',
description: 'Name of the binary property which contains<br />the data for the file to be uploaded.',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: [
'document',
],
operation: [
'upload',
],
},
},
options: [
{
displayName: 'Link To Object ID',
name: 'linkToObjectId',
type: 'string',
default: '',
description: 'ID of the object you want to link this document to',
},
{
displayName: 'Owner ID',
name: 'ownerId',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getUsers',
},
default: '',
description: 'ID of the owner of this document',
},
],
},
] as INodeProperties[];

View file

@ -24,7 +24,6 @@ import {
export async function salesforceApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any export async function salesforceApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
const authenticationMethod = this.getNodeParameter('authentication', 0, 'oAuth2') as string; const authenticationMethod = this.getNodeParameter('authentication', 0, 'oAuth2') as string;
try { try {
if (authenticationMethod === 'jwt') { if (authenticationMethod === 'jwt') {
// https://help.salesforce.com/articleView?id=remoteaccess_oauth_jwt_flow.htm&type=5 // https://help.salesforce.com/articleView?id=remoteaccess_oauth_jwt_flow.htm&type=5
@ -35,6 +34,7 @@ export async function salesforceApiRequest(this: IExecuteFunctions | IExecuteSin
const options = getOptions.call(this, method, (uri || endpoint), body, qs, instance_url as string); const options = getOptions.call(this, method, (uri || endpoint), body, qs, instance_url as string);
Logger.debug(`Authentication for "Salesforce" node is using "jwt". Invoking URI ${options.uri}`); Logger.debug(`Authentication for "Salesforce" node is using "jwt". Invoking URI ${options.uri}`);
options.headers!.Authorization = `Bearer ${access_token}`; options.headers!.Authorization = `Bearer ${access_token}`;
Object.assign(options, option);
//@ts-ignore //@ts-ignore
return await this.helpers.request(options); return await this.helpers.request(options);
} else { } else {
@ -43,6 +43,7 @@ export async function salesforceApiRequest(this: IExecuteFunctions | IExecuteSin
const credentials = this.getCredentials(credentialsType) as { oauthTokenData: { instance_url: string } }; const credentials = this.getCredentials(credentialsType) as { oauthTokenData: { instance_url: string } };
const options = getOptions.call(this, method, (uri || endpoint), body, qs, credentials.oauthTokenData.instance_url); const options = getOptions.call(this, method, (uri || endpoint), body, qs, credentials.oauthTokenData.instance_url);
Logger.debug(`Authentication for "Salesforce" node is using "OAuth2". Invoking URI ${options.uri}`); Logger.debug(`Authentication for "Salesforce" node is using "OAuth2". Invoking URI ${options.uri}`);
Object.assign(options, option);
//@ts-ignore //@ts-ignore
return await this.helpers.requestOAuth2.call(this, credentialsType, options); return await this.helpers.requestOAuth2.call(this, credentialsType, options);
} }
@ -90,12 +91,16 @@ function getOptions(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOpt
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
method, method,
body: method === 'GET' ? undefined : body, body,
qs, qs,
uri: `${instanceUrl}/services/data/v39.0${endpoint}`, uri: `${instanceUrl}/services/data/v39.0${endpoint}`,
json: true, json: true,
}; };
if (!Object.keys(options.body).length) {
delete options.body;
}
//@ts-ignore //@ts-ignore
return options; return options;
} }

View file

@ -1,4 +1,5 @@
import { import {
BINARY_ENCODING,
IExecuteFunctions, IExecuteFunctions,
} from 'n8n-core'; } from 'n8n-core';
@ -112,6 +113,11 @@ import {
userOperations, userOperations,
} from './UserDescription'; } from './UserDescription';
import {
documentFields,
documentOperations,
} from './DocumentDescription';
import { import {
LoggerProxy as Logger, LoggerProxy as Logger,
} from 'n8n-workflow'; } from 'n8n-workflow';
@ -203,6 +209,11 @@ export class Salesforce implements INodeType {
value: 'customObject', value: 'customObject',
description: 'Represents a custom object.', description: 'Represents a custom object.',
}, },
{
name: 'Document',
value: 'document',
description: 'Represents a document.',
},
{ {
name: 'Flow', name: 'Flow',
value: 'flow', value: 'flow',
@ -243,6 +254,8 @@ export class Salesforce implements INodeType {
...contactFields, ...contactFields,
...customObjectOperations, ...customObjectOperations,
...customObjectFields, ...customObjectFields,
...documentOperations,
...documentFields,
...opportunityOperations, ...opportunityOperations,
...opportunityFields, ...opportunityFields,
...accountOperations, ...accountOperations,
@ -936,6 +949,27 @@ export class Salesforce implements INodeType {
sortOptions(returnData); sortOptions(returnData);
return returnData; return returnData;
}, },
// // Get all folders to display them to user so that he can
// // select them easily
// async getFolders(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
// const returnData: INodePropertyOptions[] = [];
// const fields = await salesforceApiRequestAllItems.call(this, 'records', 'GET', '/sobjects/folder/describe');
// console.log(JSON.stringify(fields, undefined, 2))
// const qs = {
// //ContentFolderItem ContentWorkspace ContentFolder
// q: `SELECT Id, Title FROM ContentVersion`,
// //q: `SELECT Id FROM Folder where Type = 'Document'`,
// };
// const folders = await salesforceApiRequestAllItems.call(this, 'records', 'GET', '/query', {}, qs);
// for (const folder of folders) {
// returnData.push({
// name: folder.Name,
// value: folder.Id,
// });
// }
// return returnData;
// },
}, },
}; };
@ -1588,6 +1622,49 @@ export class Salesforce implements INodeType {
} }
} }
} }
if (resource === 'document') {
//https://developer.salesforce.com/docs/atlas.en-us.206.0.api_rest.meta/api_rest/dome_sobject_insert_update_blob.htm
if (operation === 'upload') {
const title = this.getNodeParameter('title', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string;
let data;
const body: { entity_content: { [key: string]: string } } = {
entity_content: {
Title: title,
ContentLocation: 'S',
},
};
if (additionalFields.ownerId) {
body.entity_content['ownerId'] = additionalFields.ownerId as string;
}
if (additionalFields.linkToObjectId) {
body.entity_content['FirstPublishLocationId'] = additionalFields.linkToObjectId as string;
}
if (items[i].binary && items[i].binary![binaryPropertyName]) {
const binaryData = items[i].binary![binaryPropertyName];
body.entity_content['PathOnClient'] = `${title}.${binaryData.fileExtension}`;
data = {
entity_content: {
value: JSON.stringify(body.entity_content),
options: {
contentType: 'application/json',
},
},
VersionData: {
value: Buffer.from(binaryData.data, BINARY_ENCODING),
options: {
filename: binaryData.fileName,
contentType: binaryData.mimeType,
},
},
};
} else {
throw new NodeOperationError(this.getNode(), `The property ${binaryPropertyName} does not exist`);
}
responseData = await salesforceApiRequest.call(this, 'POST', '/sobjects/ContentVersion', {}, {}, undefined, { formData: data });
}
}
if (resource === 'opportunity') { if (resource === 'opportunity') {
//https://developer.salesforce.com/docs/api-explorer/sobject/Opportunity/post-opportunity //https://developer.salesforce.com/docs/api-explorer/sobject/Opportunity/post-opportunity
if (operation === 'create' || operation === 'upsert') { if (operation === 'create' || operation === 'upsert') {