Skip to content

Commit ab3106d

Browse files
authored
Upsert processing speed Increments (#45)
* Added new `Type Of Search` - `External IDs` to `Upsert Object` action * Implemented caching for metadata in `Upsert Object` action (metadata needs to find fields that contain attachment) * Small fixes
1 parent b1dfc33 commit ab3106d

File tree

8 files changed

+544
-270
lines changed

8 files changed

+544
-270
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 2.3.0 (June 17, 2022)
2+
* Added new `Type Of Search` - `External IDs` to `Upsert Object` action
3+
* Implemented caching for metadata in `Upsert Object` action (metadata needs to find fields that contain attachment)
4+
* Small fixes
5+
16
## 2.2.4 (June 03, 2022)
27
* Added timeout for `Upsert Object` action
38

README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,8 +318,19 @@ Action creates a single object.
318318

319319
#### List of Expected Config fields
320320
* **Object** - Input field where you should choose the object type, which you want to find. E.g. `Account`
321-
* **Type Of Search** - Dropdown list with two values: `Unique Fields` and `All Fields`.
322-
* **Lookup by field** - Dropdown list with all fields on the selected object if the *Type Of Search* is `All Fields`. If the *Type Of Search* is `Unique Fields`, the dropdown lists instead all fields on the selected object where `type` is `id` or `unique` is `true`.
321+
* **Type Of Search** - Dropdown list with values: `Unique Fields`, `All Fields` and `External IDs`
322+
* `All Fields` - all available fields in the object
323+
* `Unique Fields` - fields where `type` is `id` or `unique` is `true`
324+
* `External IDs` - fields where `externalId` is `true`, this option uses built-in salesforce method [upsert](https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_calls_upsert.htm).
325+
326+
It works as following:
327+
* If there is no value in the lookup field - a new object will be created
328+
* If a lookup value is specified and `External IDs` selected as a Type Of Search - it is the most efficient (fast) way to go. In this case an object will be upserted directly on the Salesforce side. When this field has an attribute `Unique` it would guarantee that no errors are emitted.
329+
* If a lookup value is specified and one of `Unique Fields` or `All Fields` selected - then an action will first lookup for an existing object in Salesforce:
330+
* If no objects found - a new one will be created
331+
* If 1 object found - it will be updated
332+
* If more than 1 object found - ar error `Found more than 1 Object` will be thrown
333+
* **Lookup by field** - Dropdown list with fields on the selected object, depending on the *Type Of Search*
323334

324335
#### Expected input metadata
325336
* lookup by - *name of filed selected in 'Lookup by field'*
@@ -331,5 +342,8 @@ The result of creating or updating an object
331342
* **success** - Boolean result of creation/update object
332343
* **errors** - Arrey of errors if they exist
333344

345+
#### Known limitations
346+
If you add a new field to an object in Salesforce, you must restart the flow to re-generate metadata
347+
334348
## Known limitations
335349
Attachments mechanism does not work with [Local Agent Installation](https://docs.elastic.io/getting-started/local-agent.html)

component.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"docsUrl": "https://github.com/elasticio/salesforce-component-v2",
55
"url": "http://www.salesforce.com/",
66
"buildType": "docker",
7-
"version": "2.2.4",
7+
"version": "2.3.0",
88
"authClientTypes": [
99
"oauth2"
1010
],
@@ -519,6 +519,7 @@
519519
],
520520
"model": {
521521
"uniqueFields": "Unique Fields",
522+
"externalIds": "External IDs",
522523
"allFields": "All Fields"
523524
},
524525
"prompt": "Please select a type of search",

lib/actions/upsert_v2.js

Lines changed: 36 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
/* eslint-disable no-param-reassign, no-unused-vars, class-methods-use-this */
1+
/* eslint-disable no-param-reassign, no-unused-vars, class-methods-use-this, no-restricted-syntax */
22
const { messages } = require('elasticio-node');
3-
const { Upsert } = require('@elastic.io/oih-standard-library/lib/actions/upsert');
43
const { AttachmentProcessor } = require('@elastic.io/component-commons-library');
54
const { callJSForceMethod } = require('../helpers/wrapper');
65
const { createProperty, getLookupFieldsModelWithTypeOfSearch } = require('../helpers/utils');
@@ -51,69 +50,55 @@ module.exports.getMetaModel = async function getMetaModel(configuration) {
5150

5251
result.in.properties[configuration.lookupField] = {
5352
type: 'string',
54-
title: `lookup by - ${configuration.lookupField}`,
55-
required: configuration.lookupField !== 'Id',
53+
title: configuration.lookupField,
54+
required: false,
5655
};
5756

5857
fields.forEach((field) => {
5958
result.in.properties[field.name] = createProperty(field);
6059
if (field.type === 'base64') result.in.properties[field.name].title += ' - use URL to file';
6160
});
61+
62+
result.in.properties[configuration.lookupField].title = `lookup by - ${result.in.properties[configuration.lookupField].title}`;
63+
6264
return result;
6365
};
6466

65-
class UpsertObject extends Upsert {
66-
constructor(context) {
67-
super(context);
68-
this.logger = context.logger;
69-
}
67+
let meta;
7068

71-
getCriteria(_msg, _cfg) {
72-
return true;
73-
}
69+
module.exports.process = async function upsertObject(msg, cfg) {
70+
this.logger.info(`Starting processing Upsert "${cfg.sobject}" object action`);
7471

75-
async lookupObject(_criteria, _type, cfg, msg) {
76-
let existingObj;
77-
if (cfg.lookupField === 'Id' && (!msg.body.Id)) {
78-
existingObj = undefined;
79-
} else {
80-
existingObj = await callJSForceMethod.call(this, { ...cfg, timeOut }, 'sobjectLookup', msg);
81-
if (existingObj.length > 1) {
82-
throw new Error('Found more than 1 Object');
83-
} else if (existingObj.length === 0) {
84-
existingObj = undefined;
85-
}
72+
if (!meta) meta = await callJSForceMethod.call(this, cfg, 'describe');
73+
for (const field in meta.fields) {
74+
if (field.type === 'base64' && msg.body[field.name]) {
75+
const { data } = await new AttachmentProcessor().getAttachment(msg.body[field.name], 'arraybuffer');
76+
msg.body[field.name] = data.toString('base64');
8677
}
87-
return existingObj;
8878
}
8979

90-
async getObjectFromMessage(msg, cfg) {
91-
const meta = await callJSForceMethod.call(this, cfg, 'describe');
92-
await meta.fields.forEach(async (field) => {
93-
if (field.type === 'base64') {
94-
if (msg.body[field.name]) {
95-
const attachmentProcessor = new AttachmentProcessor();
96-
const attachment = await attachmentProcessor.getAttachment(msg.body[field.name], 'arraybuffer');
97-
msg.body[field.name] = attachment.data.toString('base64');
98-
}
99-
}
100-
});
101-
return msg;
102-
}
103-
104-
async updateObject(_criteria, _type, object, cfg, _msg, existingObject) {
105-
object.body.Id = existingObject[0].Id;
106-
const result = await callJSForceMethod.call(this, { ...cfg, timeOut }, 'sobjectUpdate', object);
107-
return result;
108-
}
109-
110-
async createObject(object, cfg, _msg) {
111-
const result = callJSForceMethod.call(this, { ...cfg, timeOut }, 'sobjectCreate', object);
112-
return result;
80+
let result;
81+
if (!msg.body[cfg.lookupField]) {
82+
this.logger.info(`"${cfg.lookupField}" not specified, creating new object`);
83+
result = await callJSForceMethod.call(this, { ...cfg, timeOut }, 'sobjectCreate', msg);
84+
} else if (cfg.typeOfSearch === 'externalIds') {
85+
this.logger.info(`Going to upsert "${cfg.sobject}" object using "${cfg.lookupField}" field`);
86+
const extIdField = cfg.lookupField;
87+
result = await callJSForceMethod.call(this, { ...cfg, timeOut, extIdField }, 'sobjectUpsert', msg);
88+
} else {
89+
this.logger.info('Starting to get object for an upsert...');
90+
const existingObj = await callJSForceMethod.call(this, { ...cfg, timeOut }, 'sobjectLookup', msg);
91+
if (existingObj.length > 1) {
92+
throw new Error('Found more than 1 Object');
93+
} else if (existingObj.length === 0) {
94+
this.logger.info('An object does not exist, creating a new one');
95+
result = await callJSForceMethod.call(this, { ...cfg, timeOut }, 'sobjectCreate', msg);
96+
} else {
97+
this.logger.info('An object already exists, updating it');
98+
msg.body.Id = existingObj[0].Id;
99+
result = await callJSForceMethod.call(this, { ...cfg, timeOut }, 'sobjectUpdate', msg);
100+
}
113101
}
114-
}
115102

116-
module.exports.process = async function upsertObject(msg, cfg) {
117-
const upsert = new UpsertObject(this);
118-
return upsert.process(msg, cfg, {});
103+
return messages.newMessageWithBody(result);
119104
};

lib/helpers/utils.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,12 @@ exports.getLookupFieldsModelWithTypeOfSearch = async function getLookupFieldsMod
147147
.forEach((field) => {
148148
model[field.name] = `${field.label} (${field.name})`;
149149
});
150+
} else if (typeOfSearch === 'externalIds') {
151+
await meta.fields
152+
.filter((field) => field.externalId)
153+
.forEach((field) => {
154+
model[field.name] = `${field.label} (${field.name})`;
155+
});
150156
} else {
151157
await meta.fields
152158
.forEach((field) => {

0 commit comments

Comments
 (0)