Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions components/salesforce_rest_api/actions/upsert-record/upsert-record.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import {
convertFieldsToProps, getAdditionalFields,
} from "../../common/props-utils.mjs";
import salesforce from "../../salesforce_rest_api.app.mjs";
import { additionalFields } from "../common/base-create-update.mjs";

export default {
key: "salesforce_rest_api-upsert-record",
name: "Upsert Record",
description: "Create or update a record of a given object. [See the documentation](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_upsert.htm)",
version: "0.0.1",
type: "action",
props: {
salesforce,
objectType: {
propDefinition: [
salesforce,
"objectType",
],
description: "The type of object to create a record of",
reloadProps: true,
},
},
methods: {
getAdditionalFields,
convertFieldsToProps,
async upsertRecord(sobjectName, {
externalIdFieldName, externalIdValue, ...args
}) {
const url = `${this.salesforce._sObjectTypeApiUrl(sobjectName)}/${externalIdFieldName}/${externalIdValue}`;
return this.salesforce._makeRequest({
url,
method: "PATCH",
...args,
});
},
Comment on lines +27 to +36
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add parameter validation and URL encoding

While the upsert implementation is correct, consider these safety improvements:

  1. Add parameter validation for externalIdFieldName and externalIdValue
  2. URL encode the externalIdValue to handle special characters safely
 async upsertRecord(sobjectName, {
   externalIdFieldName, externalIdValue, ...args
 }) {
+  if (!externalIdFieldName || !externalIdValue) {
+    throw new Error("External ID field name and value are required");
+  }
-  const url = `${this.salesforce._sObjectTypeApiUrl(sobjectName)}/${externalIdFieldName}/${externalIdValue}`;
+  const url = `${this.salesforce._sObjectTypeApiUrl(sobjectName)}/${externalIdFieldName}/${encodeURIComponent(externalIdValue)}`;
   return this.salesforce._makeRequest({
     url,
     method: "PATCH",
     ...args,
   });
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async upsertRecord(sobjectName, {
externalIdFieldName, externalIdValue, ...args
}) {
const url = `${this.salesforce._sObjectTypeApiUrl(sobjectName)}/${externalIdFieldName}/${externalIdValue}`;
return this.salesforce._makeRequest({
url,
method: "PATCH",
...args,
});
},
async upsertRecord(sobjectName, {
externalIdFieldName, externalIdValue, ...args
}) {
if (!externalIdFieldName || !externalIdValue) {
throw new Error("External ID field name and value are required");
}
const url = `${this.salesforce._sObjectTypeApiUrl(sobjectName)}/${externalIdFieldName}/${encodeURIComponent(externalIdValue)}`;
return this.salesforce._makeRequest({
url,
method: "PATCH",
...args,
});
},

},
async additionalProps() {
const { objectType } = this;
const fields = await this.salesforce.getFieldsForObjectType(objectType);

const requiredFields = fields.filter((field) => {
return field.createable && field.updateable && !field.nillable && !field.defaultedOnCreate;
});

const externalIdFieldOptions = fields.filter((field) => field.externalId).map(({
label, name,
}) => ({
label,
value: name,
}));

const requiredFieldProps = this.convertFieldsToProps(requiredFields);

return {
docsInfo: {
type: "alert",
alertType: "info",
content: `[See the documentation](https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_${objectType.toLowerCase()}.htm) for information on all available fields.`,
},
externalIdFieldName: {
type: "string",
label: "External ID Field",
description: "The field to use as the external ID to identify the record.",
options: externalIdFieldOptions,
},
docsInfoExtId: {
type: "alert",
alertType: "info",
content: "If you don't see any fields in the above list, you probably need to create one in Salesforce's Object Manager. Only a field marked as an external id field can be used to identify a record.",
},
externalIdValue: {
type: "string",
label: "External ID Value",
description: "The value of the external ID field selected above. If a record with this value exists, it will be updated, otherwise a new one will be created.",
},
updateOnly: {
type: "boolean",
label: "Update Only",
description: "If enabled, the action will only update an existing record, but not create one.",
optional: true,
},
...requiredFieldProps,
additionalFields,
};
},
async run({ $ }) {
/* eslint-disable no-unused-vars */
const {
salesforce,
objectType,
getAdditionalFields: getData,
convertFieldsToProps,
docsInfo,
docsInfoExtId,
additionalFields,
externalIdFieldName,
externalIdValue,
updateOnly,
...data
} = this;
/* eslint-enable no-unused-vars */
const response = await this.upsertRecord(objectType, {
$,
externalIdFieldName,
externalIdValue,
params: {
updateOnly,
},
data: {
...data,
...getData(),
},
});
$.export("$summary", `Successfully ${response.created
? "created"
: "updated"} ${objectType} record (ID: ${response.id})`);
return response;
},
Comment on lines +87 to +119
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling and response validation

The run method should include proper error handling and response validation:

 async run({ $ }) {
   /* eslint-disable no-unused-vars */
   const {
     salesforce,
     objectType,
     getAdditionalFields: getData,
     convertFieldsToProps,
     docsInfo,
     docsInfoExtId,
     additionalFields,
     externalIdFieldName,
     externalIdValue,
     updateOnly,
     ...data
   } = this;
   /* eslint-enable no-unused-vars */
+  try {
+    if (!objectType || !externalIdFieldName || !externalIdValue) {
+      throw new Error("Missing required fields: objectType, externalIdFieldName, or externalIdValue");
+    }
     const response = await this.upsertRecord(objectType, {
       $,
       externalIdFieldName,
       externalIdValue,
       params: {
         updateOnly,
       },
       data: {
         ...data,
         ...getData(),
       },
     });
+    if (!response?.id) {
+      throw new Error("Invalid response: missing record ID");
+    }
     $.export("$summary", `Successfully ${response.created
       ? "created"
       : "updated"} ${objectType} record (ID: ${response.id})`);
     return response;
+  } catch (error) {
+    throw new Error(`Failed to upsert ${objectType} record: ${error.message}`);
+  }
 },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async run({ $ }) {
/* eslint-disable no-unused-vars */
const {
salesforce,
objectType,
getAdditionalFields: getData,
convertFieldsToProps,
docsInfo,
docsInfoExtId,
additionalFields,
externalIdFieldName,
externalIdValue,
updateOnly,
...data
} = this;
/* eslint-enable no-unused-vars */
const response = await this.upsertRecord(objectType, {
$,
externalIdFieldName,
externalIdValue,
params: {
updateOnly,
},
data: {
...data,
...getData(),
},
});
$.export("$summary", `Successfully ${response.created
? "created"
: "updated"} ${objectType} record (ID: ${response.id})`);
return response;
},
async run({ $ }) {
/* eslint-disable no-unused-vars */
const {
salesforce,
objectType,
getAdditionalFields: getData,
convertFieldsToProps,
docsInfo,
docsInfoExtId,
additionalFields,
externalIdFieldName,
externalIdValue,
updateOnly,
...data
} = this;
/* eslint-enable no-unused-vars */
try {
if (!objectType || !externalIdFieldName || !externalIdValue) {
throw new Error("Missing required fields: objectType, externalIdFieldName, or externalIdValue");
}
const response = await this.upsertRecord(objectType, {
$,
externalIdFieldName,
externalIdValue,
params: {
updateOnly,
},
data: {
...data,
...getData(),
},
});
if (!response?.id) {
throw new Error("Invalid response: missing record ID");
}
$.export("$summary", `Successfully ${response.created
? "created"
: "updated"} ${objectType} record (ID: ${response.id})`);
return response;
} catch (error) {
throw new Error(`Failed to upsert ${objectType} record: ${error.message}`);
}
},

};
2 changes: 1 addition & 1 deletion components/salesforce_rest_api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pipedream/salesforce_rest_api",
"version": "1.3.1",
"version": "1.4.0",
"description": "Pipedream Salesforce (REST API) Components",
"main": "salesforce_rest_api.app.mjs",
"keywords": [
Expand Down
Loading