Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/stevenmmortimer/salesforcer

This R package connects the Salesforce APIs from R using tidy principles.
https://github.com/stevenmmortimer/salesforcer

api-wrappers r r-language r-package r-programming salesforce salesforce-apis

Last synced: 7 days ago
JSON representation

This R package connects the Salesforce APIs from R using tidy principles.

Awesome Lists containing this project

README

        

---
output:
github_document:
html_preview: false
---

```{r, echo = FALSE, message = FALSE}
knitr::opts_chunk$set(
fig.align = 'center',
collapse = TRUE,
comment = "#>",
fig.path = "man/figures/README-")
options(tibble.print_min = 5L, tibble.print_max = 5L)
```

# salesforcer

[![R Build Status](https://github.com/stevenmmortimer/salesforcer/workflows/R-CMD-check/badge.svg)](https://github.com/stevenmmortimer/salesforcer/actions?workflow=R-CMD-check)
[![CRAN Status](https://www.r-pkg.org/badges/version/salesforcer)](https://cran.r-project.org/package=salesforcer)
[![Lifecycle: Stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://lifecycle.r-lib.org/articles/stages.html#stable)
[![Monthly Downloads](https://cranlogs.r-pkg.org/badges/last-month/salesforcer)](https://cran.r-project.org/package=salesforcer)
[![Coverage Status](https://codecov.io/gh/stevenmmortimer/salesforcer/branch/main/graph/badge.svg)](https://app.codecov.io/gh/stevenmmortimer/salesforcer?branch=main)

{salesforcer} is an R package that connects to Salesforce Platform APIs using
tidy principles. The package implements actions from the REST, SOAP, Bulk 1.0,
Bulk 2.0, Reports and Dashboards, and Metadata APIs.

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!

## Table of Contents
* [Installation](#installation)
* [Vignettes](#vignettes)
* [Usage](#usage)
* [Authenticate](#authenticate)
* [Create](#create)
* [Query](#query)
* [Update](#update)
* [Bulk Operations](#bulk-operations)
* [Using the Metadata API](#using-the-metadata-api)
* [Future](#future)
* [Credits](#credits)
* [More Information](#more-information)

## Installation

```{r, eval = FALSE}
# install the current CRAN version (1.0.1)
install.packages("salesforcer")

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

If you encounter an issue while using this package, please file a minimal reproducible
example on [GitHub](https://github.com/stevenmmortimer/salesforcer/issues).

## Vignettes

The README below outlines the basic package functionality. For more information
please feel free to browse the {salesforcer} website at https://stevenmmortimer.github.io/salesforcer/ which contains the following vignettes:

* [Getting Started](https://stevenmmortimer.github.io/salesforcer/articles/getting-started.html)
* [Supported Queries](https://stevenmmortimer.github.io/salesforcer/articles/supported-queries.html)
* [Working with Bulk APIs](https://stevenmmortimer.github.io/salesforcer/articles/working-with-bulk-apis.html)
* [Working with Reports](https://stevenmmortimer.github.io/salesforcer/articles/working-with-reports.html)
* [Working with Attachments](https://stevenmmortimer.github.io/salesforcer/articles/working-with-attachments.html)
* [Working with Metadata](https://stevenmmortimer.github.io/salesforcer/articles/working-with-metadata.html)
* [Passing Control Args](https://stevenmmortimer.github.io/salesforcer/articles/passing-control-args.html)
* [Transitioning from RForcecom](https://stevenmmortimer.github.io/salesforcer/articles/transitioning-from-RForcecom.html)

## Usage

### Authenticate

First, load the {salesforcer} package and log in. There are two ways to
authenticate:

1. OAuth 2.0
2. ~~Basic Username-Password~~

**NOTE**: Beginning February 1, 2022 authentication via a username and
password will not work in most Salesforce organizations. On that date Salesforce
will begin requiring customers to enable multi-factor authentication (MFA). The
function `sf_auth()` will return the error message:

```sh
INVALID_LOGIN: Invalid username, password, security token; or user locked out.
```

It has always been recommended to use OAuth 2.0 so that passwords do not have to
be shared or embedded within scripts. For more information on how OAuth 2.0 works
within the {salesforcer} package, please read the
[Getting Started](https://stevenmmortimer.github.io/salesforcer/articles/getting-started.html)
vignette.

```{r auth, include = FALSE}
suppressWarnings(suppressMessages(library(dplyr)))
library(salesforcer)
token_path <- Sys.getenv("SALESFORCER_TOKEN_PATH")
sf_auth(token = paste0(token_path, "salesforcer_token.rds"))
```

```{r, eval=FALSE}
library(dplyr, warn.conflicts = FALSE)
library(salesforcer)

# Using OAuth 2.0 authentication
sf_auth()
```

After logging in with `sf_auth()`, you can check your connectivity by looking at
the information returned about the current user. It should be information about you!

```{r}
# pull down information of person logged in
# it's a simple easy call to get started
# and confirm a connection to the APIs
user_info <- sf_user_info()
sprintf("Organization Id: %s", user_info$organizationId)
sprintf("User Id: %s", user_info$userId)
```

### Create

Salesforce has objects and those objects contain records. One default object is the
"Contact" object. This example shows how to create two records in the Contact object.

```{r create-records}
n <- 2
new_contacts <- tibble(FirstName = rep("Test", n),
LastName = paste0("Contact-Create-", 1:n))
created_records <- sf_create(new_contacts, object_name = "Contact")
created_records
```

### Query

Salesforce has proprietary form of SQL called SOQL (Salesforce Object Query
Language). SOQL is a powerful tool that allows you to return the attributes of records
on almost any object in Salesforce including Accounts, Contacts, Tasks, Opportunities,
even Attachments! Below is an example where we grab the data we just created
including Account object information for which the Contact record is associated
with.

```{r query-records}
my_soql <- sprintf("SELECT Id,
Account.Name,
FirstName,
LastName
FROM Contact
WHERE Id in ('%s')",
paste0(created_records$id , collapse = "','"))
queried_records <- sf_query(my_soql)
queried_records
```

**NOTE**: In the example above, you'll notice that the `"Account.Name"` column
does not appear in the results. This is because the SOAP and REST APIs only
return an empty Account object for the record if there is no relationship to an
account (see #78). There
is no reliable way to extract and rebuild the empty columns based on the query
string. If there were Account information, an additional column titled
`"Account.Name"` would appear in the results. Note, that the Bulk 1.0 and Bulk
2.0 APIs will return `"Account.Name"` as a column of all `NA` values for this
query because they return results differently.

### Update

After creating records you can update them using `sf_update()`. Updating a record
requires you to pass the Salesforce `Id` of the record. Salesforce creates a unique
18-character identifier on each record and uses that to know which record to
attach the update information you provide. Simply include a field or column in your
update dataset called "Id" and the information will be matched. Here is an example
where we update each of the records we created earlier with a new first name
called "TestTest".

```{r update-records}
# Update some of those records
queried_records <- queried_records %>%
mutate(FirstName = "TestTest")

updated_records <- sf_update(queried_records, object_name = "Contact")
updated_records
```

```{r cleanup-records, include=FALSE}
deleted_records <- sf_delete(updated_records$id)
deleted_records
```

### Bulk Operations

For really large operations (inserts, updates, upserts, deletes, and queries) Salesforce
provides the [Bulk 1.0](https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/asynch_api_intro.htm)
and [Bulk 2.0](https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/introduction_bulk_api_2.htm)
APIs. In order to use the Bulk APIs in {salesforcer} you can just add `api_type = "Bulk 1.0"`
or `api_type = "Bulk 2.0"` to your functions and the operation will be executed
using the Bulk APIs. It's that simple.

The benefits of using the Bulk API for larger datasets is that the operation will
reduce the number of individual API calls (organization usually have a limit on
total calls) and batching the requests in bulk is usually quicker than running thousands
of individuals calls when your data is large. **Note:** the Bulk 2.0 API does **NOT**
guarantee the order of the data submitted is preserved in the output. 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.

```{r bulk-example}
# create contacts using the Bulk API
n <- 2
new_contacts <- tibble(FirstName = rep("Test", n),
LastName = paste0("Contact-Create-", 1:n))
created_records <- sf_create(new_contacts, "Contact", api_type = "Bulk 1.0")
created_records

# query large recordsets using the Bulk API
my_soql <- sprintf("SELECT Id,
FirstName,
LastName
FROM Contact
WHERE Id in ('%s')",
paste0(created_records$Id , collapse = "','"))

queried_records <- sf_query(my_soql, "Contact", api_type = "Bulk 1.0")
queried_records

# delete these records using the Bulk 2.0 API
deleted_records <- sf_delete(queried_records$Id, "Contact", api_type = "Bulk 2.0")
deleted_records
```

### Using the Metadata API

Salesforce is a very flexible platform in that it provides the
[Metadata API](https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_intro.htm)
for users to create, read, update and delete their entire Salesforce environment from
objects to page layouts and more. This makes it very easy to programmatically setup
and teardown the Salesforce environment. 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.

```{r metadata-read}
read_obj_result <- sf_read_metadata(metadata_type='CustomObject',
object_names=c('Account', 'Contact'))
read_obj_result[[1]][c('fullName', 'label', 'sharingModel', 'enableHistory')]
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]
```

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:

```{r soap-describe-object-fields}
acct_fields <- sf_describe_object_fields('Account')
acct_fields %>% select(name, label, length, soapType, type)

# show the picklist selection options for the Account Type field
acct_fields %>%
filter(label == "Account Type") %>%
.$picklistValues
```

## Future

Future APIs to support (roughly in priority order):

- [Connect (Chatter) REST API](https://developer.salesforce.com/docs/atlas.en-us.chatterapi.meta/chatterapi/intro_what_is_chatter_connect.htm)
- [Analytics External Data API](https://developer.salesforce.com/docs/atlas.en-us.bi_dev_guide_ext_data.meta/bi_dev_guide_ext_data/bi_ext_data_overview.htm)
- [Analytics REST API](https://developer.salesforce.com/docs/atlas.en-us.bi_dev_guide_rest.meta/bi_dev_guide_rest/bi_rest_overview.htm)
- [Tooling API](https://developer.salesforce.com/docs/atlas.en-us.api_tooling.meta/api_tooling/intro_api_tooling.htm)
- [Actions API](https://developer.salesforce.com/docs/atlas.en-us.api_action.meta/api_action/actions_intro.htm)
- [Streaming API](https://developer.salesforce.com/docs/atlas.en-us.api_streaming.meta/api_streaming/intro_stream.htm)
- [Place Order API](https://developer.salesforce.com/docs/atlas.en-us.api_placeorder.meta/api_placeorder/sforce_placeorder_rest_api_intro.htm)
- [Industries API](https://developer.salesforce.com/docs/atlas.en-us.api_rest_industries.meta/api_rest_industries/intro.htm)
- [Data.com API](https://developer.salesforce.com/docs/atlas.en-us.datadotcom_api_dev_guide.meta/datadotcom_api_dev_guide/datadotcom_api_dev_guide_intro.htm)

## 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.

## More Information

Salesforce provides client libraries and examples in many programming languages (Java,
Python, Ruby, and PhP) but unfortunately R is not a supported language. However,
most all operations supported by the Salesforce APIs are available via this package.
This package makes requests best formatted to match what the APIs require as input.
This articulation is not perfect and continued progress will be made to add and improve
functionality. For details on formatting, attributes, and methods please refer to
[Salesforce's documentation](https://trailhead.salesforce.com/en/content/learn/modules/api_basics/api_basics_overview) as they are explained better there. More information
is also available on the {salesforcer} pkgdown website at https://stevenmmortimer.github.io/salesforcer/.

[Get supported salesforcer with the Tidelift Subscription](https://tidelift.com/subscription/pkg/cran-salesforcer?utm_source=cran-salesforcer&utm_medium=referral&utm_campaign=readme)

---
Please note that this project is released with a [Contributor Code of Conduct](https://github.com/stevenmmortimer/salesforcer/blob/main/.github/CODE_OF_CONDUCT.md).
By participating in this project you agree to abide by its terms.

[Top](#salesforcer)