salesforcer

Overview

The {salesforcer} package is the R implementation of the Salesforce platform APIs. I created the package to build upon the functionality provided in the {RForcecom} package by including calls from more APIs (REST, SOAP, Bulk, and Metadata) along with following more tidy data principles when wrangling data from the APIs.

GitHub - salesforcer View source code on GitHub at: https://github.com/StevenMMortimer/salesforcer

Features

This R package supports a variety of Salesforce API operations and more will be added. Package features include:

  • OAuth 2.0 (Single Sign On) and Basic (Username-Password) Authentication methods (sf_auth())
  • CRUD (Create, Retrieve, Update, Delete) methods for records using the SOAP, REST, and Bulk APIs
  • Query records via the SOAP, REST, Bulk 1.0, and Bulk 2.0 APIs using sf_query()
  • Manage and execute reports and dashboards with:
    • sf_list_reports(), sf_create_report(), sf_run_report(), and more
  • Retrieve and modify metadata (Custom Objects, Fields, etc.) using the Metadata API with:
    • sf_describe_objects(), sf_create_metadata(), sf_update_metadata(), and more
  • Utilize backwards compatible functions for the {RForcecom} package, such as:
    • rforcecom.login(), rforcecom.getObjectDescription(), rforcecom.query(), rforcecom.create()
  • Basic utility calls (sf_user_info(), sf_server_timestamp(), sf_list_objects())
  • Functions to assist with master data management (MDM) or data integrity of records by finding duplicates (sf_find_duplicates(), sf_find_duplicates_by_id()), merging records (sf_merge()), and converting leads (sf_convert_lead())
  • Recover (sf_undelete()) or delete from the Recycle Bin (sf_empty_recycle_bin()) and list ids of records deleted (sf_get_deleted()) or updated (sf_get_updated()) within a specific timeframe
  • Passing API call control parameters such as, “All or None”, “Duplicate Rule”, “Assignment Rule” execution and many more!

Quickstart Guide

Install salesforcer Library

# install the current version from CRAN
install.packages("salesforcer")

# or get the development version on GitHub
# install.packages("devtools")
devtools::install_github("StevenMMortimer/salesforcer")

Authentication

The {salesforcer} package provides two methods to authenticate:

  1. OAuth 2.0
  2. Basic Username-Password with Security Token

It is recommended to use OAuth 2.0 so that passwords do not have to be shared or embedded within scripts. User credentials will be stored in locally cached file entitled “.httr-oauth-salesforcer” in the current working directory. These credentials will be passed to the API for each call and refreshed if necessary.

library(tidyverse)
library(salesforcer)

# Using OAuth 2.0 authentication
sf_auth()

# Using Basic Username-Password authentication
sf_auth(username = "test@gmail.com", 
        password = "{PASSWORD_HERE}",
        security_token = "{SECURITY_TOKEN_HERE}")

Support for Multiple APIs

The {salesforcer} package is unique in that it provides a common interface to all of the REST, SOAP, and Bulk APIs. If you would like to create a set of records, then you would use the same R function, sf_create(), in the same way and just change the function argument called api_type.

# create a dataset of 2 new contacts to create
n <- 2
new_contacts <- tibble(FirstName = rep("Test", n),
                       LastName = paste0("Contact-Create-", 1:n))

# using the REST API
rest_records <- sf_create(new_contacts, object_name="Contact", api_type="REST")
rest_records
#> # A tibble: 2 x 2
#>   id                 success
#>   <chr>              <lgl>  
#> 1 0033s000013YPkFAAW TRUE   
#> 2 0033s000013YPkGAAW TRUE

# using the REST API
soap_records <- sf_create(new_contacts, object_name="Contact", api_type="SOAP")
soap_records
#> # A tibble: 2 x 2
#>   id                 success
#>   <chr>              <lgl>  
#> 1 0033s000013YPiAAAW TRUE   
#> 2 0033s000013YPiBAAW TRUE

# using the Bulk 1.0 API
bulk_records <- sf_create(new_contacts, object_name="Contact", api_type="Bulk 1.0")
bulk_records
#> # A tibble: 2 x 4
#>   Id                 Success Created Error
#>   <chr>              <lgl>   <lgl>   <lgl>
#> 1 0033s000013YPkKAAW TRUE    TRUE    NA   
#> 2 0033s000013YPkLAAW TRUE    TRUE    NA

This form is especially useful when switching between the REST and Bulk APIs where if you start dealing with larger and larger datasets, then you can switch code easily to the Bulk API. Note: The Bulk 2.0 API does NOT guarantee the order of the data submitted is preserved in the output that is returned. This means that you must join on other data columns to match up the IDs that are returned in the output with the data you submitted. For this reason, Bulk 2.0 may not be a good solution for creating, updating, or upserting records where you need to keep track of the created IDs. The Bulk 2.0 API would be fine for deleting records where you only need to know which IDs were successfully deleted.

Support for Metadata

The {salesforcer} package also supports the creating, editing, and deleting of Salesforce metadata. Metadata refers to the configuration of “objects” within Salesforce. An object could be a standard or new type of record. For example, the Metadata API can configuring which fields are captured on an Account or whether they should be displayed on the page. Even more complex configurations, such as, workflows and triggers can be configured using the Metadata API. The support for the Metadata API in {salesforcer} is experimental, so please use at your own risk and consult the appropriate Salesforce resources when trying to use. One common use case for the Metadata API is retrieving information about an object (fields, permissions, etc.). You can use the sf_read_metadata() function to return a list of objects and their metadata. In the example below we retrieve the metadata for the Account and Contact objects. Note that the metadata_type argument is “CustomObject”. Standard Objects are an implementation of CustomObjects, so they are returned using that metadata type.

read_obj_result <- sf_read_metadata(metadata_type='CustomObject',
                                    object_names=c('Account', 'Contact'))
read_obj_result[[1]][c('fullName', 'label', 'sharingModel', 'enableHistory')]
#> $fullName
#> [1] "Account"
#> 
#> $label
#> [1] "Account"
#> 
#> $sharingModel
#> [1] "ReadWrite"
#> 
#> $enableHistory
#> [1] "false"
first_two_fields_idx <- head(which(names(read_obj_result[[1]]) == 'fields'), 2)
# show the first two returned fields of the Account object
read_obj_result[[1]][first_two_fields_idx]
#> $fields
#> $fields$fullName
#> [1] "AccountNumber"
#> 
#> $fields$trackFeedHistory
#> [1] "false"
#> 
#> 
#> $fields
#> $fields$fullName
#> [1] "AccountSource"
#> 
#> $fields$trackFeedHistory
#> [1] "false"
#> 
#> $fields$type
#> [1] "Picklist"

The data is returned as a list because object definitions are highly nested representations. You may notice that we are missing some really specific details, such as, the picklist values of a field with type “Picklist”. You can get that information using sf_describe_object_fields(). Here is an example using sf_describe_object_fields() where we get a tbl_df with one row for each field on the Account object:

acct_fields <- sf_describe_object_fields('Account')
acct_fields %>% select(name, label, length, soapType, type)
#> # A tibble: 69 x 5
#>   name           label            length soapType    type     
#>   <chr>          <chr>            <chr>  <chr>       <chr>    
#> 1 Id             Account ID       18     tns:ID      id       
#> 2 IsDeleted      Deleted          0      xsd:boolean boolean  
#> 3 MasterRecordId Master Record ID 18     tns:ID      reference
#> 4 Name           Account Name     255    xsd:string  string   
#> 5 Type           Account Type     40     xsd:string  picklist 
#> # … with 64 more rows
# show the picklist selection options for the Account Type field
acct_fields %>% 
  filter(label == "Account Type") %>% 
  .$picklistValues
#> [[1]]
#> # A tibble: 7 x 4
#>   active defaultValue label                      value                     
#>   <chr>  <chr>        <chr>                      <chr>                     
#> 1 true   false        Prospect                   Prospect                  
#> 2 true   false        Customer - Direct          Customer - Direct         
#> 3 true   false        Customer - Channel         Customer - Channel        
#> 4 true   false        Channel Partner / Reseller Channel Partner / Reseller
#> 5 true   false        Installation Partner       Installation Partner      
#> # … with 2 more rows

Extension of the RForcecom Package

The {salesforcer} package can be seen as an extension of the popular {RForcecom} package that originally introduced R users to the Salesforce APIs. However, there are a few key differences. First, {salesforcer} supports more APIs. It allows for the same methods to utilize either the REST or SOAP APIs. It also supports the newer version of the Bulk API called “Bulk 2.0” and a limited set of functionality from the Metadata API. There are future plans to incorporate the Reporting and Analytics APIs. Second, {salesforcer} makes it easier to submit multiple records at once by supplying a data.frame instead of a named vector.

n <- 2
new_contacts <- tibble(FirstName = rep("Test", n),
                       LastName = paste0("Contact-Create-", 1:n))

# the RForcecom way
rforcecom_results <- NULL
for(i in 1:nrow(new_contacts)){
  temp <- RForcecom::rforcecom.create(session, 
                                      objectName = "Contact", 
                                      fields = unlist(slice(new_contacts,i)))
  rforcecom_results <- bind_rows(rforcecom_results, temp)
}
rforcecom_results
#>                   id success
#> 1 0033s000013YPkPAAW    true
#> 2 0033s000013YPkUAAW    true

# the salesforcer way
salesforcer_results <- salesforcer::sf_create(new_contacts, object_name="Contact")
salesforcer_results
#> # A tibble: 2 x 2
#>   id                 success
#>   <chr>              <lgl>  
#> 1 0033s000013YPkZAAW TRUE   
#> 2 0033s000013YPkaAAG TRUE

Finally, the package is meant to adhere to principles outlined by the tidyverse package. Function names are snake case and data is returned as tbl_df objects. Do not worry if you are fully utilizing the {RForcecom} package. Many of those functions are included in the {salesforcer} package so you can drop the {RForcecom} dependency but not have to change all of your code at once.

Check out the Tests

The {salesforcer} package has quite a bit of unit test coverage to track any code changes and ensure package reliabilty. These tests are an excellent source of examples because they cover most all cases of utilizing the package functions.

For example, if you are not sure on how to upsert records using the Bulk API just check out the tests at https://github.com/StevenMMortimer/salesforcer/blob/master/tests/testthat/test-bulk.R.

The test-bulk.R script walks through a process of creating, searching, querying, updating, upserting, and deleting the same set of records, so it is a concise set of code for manipulating records via the Bulk API. Similar test scripts exist for the REST and SOAP APIs as well.

Credits

This application uses other open source software components. The authentication components are mostly verbatim copies of the routines established in the {googlesheets} package (https://github.com/jennybc/googlesheets). Methods are inspired by the {RForcecom} package (https://github.com/hiratake55/RForcecom). We acknowledge and are grateful to these developers for their contributions to open source.

License

The {salesforcer} package is licensed under the MIT License (http://choosealicense.com/licenses/mit/)

Questions

If you have further questions please submit them via email or issue on GitHub at https://github.com/StevenMMortimer/salesforcer/issues. Thank you!

Support

This project was made with love and coffee. Studies show that I write R code 3x faster after drinking one cup coffee and that productivity scales linearly. Imagine what I could accomplish if you bought me 10 cups of coffee…

Thank you for your support!