Skip to content
This repository has been archived by the owner on Oct 2, 2023. It is now read-only.

WIP: Add SalesforceAPI Bulk API Recipe #163

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8" ?>
<CustomIndex xmlns="http://soap.sforce.com/2006/04/metadata" />
Expand Up @@ -35,6 +35,25 @@
</header>
<div class="slds-modal__content" id="modal-content-id-1">
<div class="slds-var-p-around_medium">
<template if:false={loading}>
<template if:true={selectedFunction.instructions}>
<span class="slds-assistive-text">info</span>
<div class="slds-box slds-m-around_large">
<div class="slds-inline_icon_text slds-grid">
<lightning-icon
class="slds-p-right_medium slds-align-middle"
icon-name="utility:info"
size="medium"
></lightning-icon>
<div class="slds-col slds-align-middle">
<lightning-formatted-rich-text
value={selectedFunction.instructions}
></lightning-formatted-rich-text>
</div>
</div>
</div>
</template>
</template>
<template if:true={loading}>
<div class="spinner-container">
<lightning-spinner
Expand All @@ -51,23 +70,6 @@
type="inlineMessage"
></c-error-panel>
</template>
<template if:true={selectedFunction.instructions}>
<span class="slds-assistive-text">info</span>
<div class="slds-box slds-m-around_large">
<div class="slds-inline_icon_text slds-grid">
<lightning-icon
class="slds-p-right_medium slds-align-middle"
icon-name="utility:info"
size="medium"
></lightning-icon>
<div class="slds-col slds-align-middle">
<lightning-formatted-rich-text
value={selectedFunction.instructions}
></lightning-formatted-rich-text>
</div>
</div>
</div>
</template>
<template if:true={selectedFunction.inputs}>
<div
class="
Expand Down
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>ExternalID__c</fullName>
<caseSensitive>false</caseSensitive>
<externalId>true</externalId>
<label>ExternalID</label>
<length>36</length>
<required>false</required>
<trackFeedHistory>false</trackFeedHistory>
<type>Text</type>
<unique>true</unique>
</CustomField>
@@ -1,6 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
<description>Functions Recipes - Object Permissions</description>
<fieldPermissions>
<editable>true</editable>
<field>Account.ExternalID__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>true</editable>
<field>Account.Description</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>true</editable>
<field>Account.AccountNumber</field>
Expand Down Expand Up @@ -50,10 +60,10 @@
<label>Functions</label>
<objectPermissions>
<allowCreate>true</allowCreate>
<allowDelete>false</allowDelete>
<allowDelete>true</allowDelete>
<allowEdit>true</allowEdit>
<allowRead>true</allowRead>
<modifyAllRecords>false</modifyAllRecords>
<modifyAllRecords>true</modifyAllRecords>
<object>Account</object>
<viewAllRecords>true</viewAllRecords>
</objectPermissions>
Expand Down
141 changes: 141 additions & 0 deletions force-app/main/default/staticresources/functionDetails.js
Expand Up @@ -1367,6 +1367,147 @@ export default async function (event, context, logger) {
const results = await pbkdf2(password, salt, 10e3, keyLength, "sha512");
return results.toString("hex");
}
`
}
]
}
]
},
{
name: "06_SalesforceAPI_Bulk_v2_Ingest_JS",
label: "Bulk API v2.0 Ingest",
subtitle: "Functions Recipes",
description:
"Fetches an external CSV file and uses the Salesforce Bulk API v2.0 to ingest it.",
instructions:
'Check results by running: <code>sfdx force:org:open -p "/lightning/setup/AsyncApiJobStatus/home"</code>',
functions: [
{
name: "06_SalesforceAPI_Bulk_v2_Ingest_JS",
label: "Salesforce APIs - Bulk v2.0 Ingest - JavaScript",
deployment: "functions_recipes.bulkingestjs",
language: "JavaScript",
files: [
{
name: "index.js",
label: "Index",
body: `import { request } from "undici";

/**
* Fetches an external CSV file and uses the Salesforce Bulk API v2.0 to ingest it.
*
* The exported method is the entry point for your code when the function is invoked.
*
* Following parameters are pre-configured and provided to your function on execution:
* @param event: represents the data associated with the occurrence of an event, and
* supporting metadata about the source of that occurrence.
* @param context: represents the connection to Functions and your Salesforce org.
* @param logger: logging handler used to capture application logs and trace specifically
* to a given execution of a function.
*/
export default async function (event, context, logger) {
logger.info(
\`Invoking bulkingestjs with payload \${JSON.stringify(event.data || {})}\`
);

// Extract dataApi information from context
const { accessToken, baseUrl, apiVersion } = context.org.dataApi;

// Setup Bulk API Authorization headers
const authHeaders = {
Authorization: \`Bearer \${accessToken}\`
};

// Construct API URL for Bulk API v2
const apiUrl = \`\${baseUrl}/services/data/v\${apiVersion}\`;

// Load CSV file from external site
const { statusCode: statusCodeCsv, body: csvStream } = await request(
"https://external-accounts-site.herokuapp.com/accounts.csv",
{
method: "GET"
}
);

if (statusCodeCsv !== 200) {
throw new Error(
\`Failed to load CSV file from external site. Status code: \${statusCodeCsv}\`
);
}

// Create a new Bulk API Job
const { statusCode: statusCodeJob, body: bodyJob } = await request(
\`\${apiUrl}/jobs/ingest\`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
...authHeaders
},
body: JSON.stringify({
operation: "upsert",
object: "Account",
contentType: "CSV",
externalIdFieldName: "ExternalID__c"
})
}
);

// Get Job Response
const createJobResponse = await bodyJob.json();

if (statusCodeJob !== 200) {
logger.error(JSON.stringify(createJobResponse));
throw new Error(\`Create job failed\`);
}

logger.info(\`Job created. Id: \${createJobResponse.id}\`);

// Upload CSV file to Bulk API Job
const { statusCode: statusCodeUpload } = await request(
\`\${apiUrl}/jobs/ingest/\${createJobResponse.id}/batches\`,
{
method: "PUT",
headers: {
"Content-Type": "text/csv",
...authHeaders
},
body: csvStream
}
);

if (statusCodeUpload !== 201) {
throw new Error(\`Upload failed\`);
}

logger.info(\`Upload complete. Status code: \${statusCodeUpload}\`);

// Close Job
const { statusCode, body } = await request(
\`\${apiUrl}/jobs/ingest/\${createJobResponse.id}\`,
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
...authHeaders
},
body: JSON.stringify({
state: "UploadComplete"
})
}
);

const result = await body.json();

if (statusCode !== 200) {
logger.error(JSON.stringify(result));
throw new Error(\`Close job failed\`);
}

logger.info(\`Job closed. Status code: \${statusCode}\`);

return result;
}
`
}
]
Expand Down
13 changes: 13 additions & 0 deletions functions/06_SalesforceAPI_Bulk_v2_Ingest_JS/.eslintrc
@@ -0,0 +1,13 @@
{
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 2020
},
"extends": "eslint:recommended",
"env": {
"node": true,
"es2020": true,
"mocha": true
},
"rules": {}
}
4 changes: 4 additions & 0 deletions functions/06_SalesforceAPI_Bulk_v2_Ingest_JS/.mocharc.json
@@ -0,0 +1,4 @@
{
"test": "./**/*.test.js",
"recursive": true
}
35 changes: 35 additions & 0 deletions functions/06_SalesforceAPI_Bulk_v2_Ingest_JS/README.md
@@ -0,0 +1,35 @@
# Bulkapijs Function

Fetches an external CSV file and uses the Salesforce Bulk API v2.0 to ingest it.

## Local Development

1. Install dependencies with

```
npm install
```

2. Run tests with

```
npm test
```

3. Start your function locally

```
sf run function start --verbose
```

4. Invoke your function locally

```
sf run function --function-url=http://localhost:8080 --payload={}
```

5. Check results by running

```
sfdx force:org:open -p "/lightning/setup/AsyncApiJobStatus/home"
```