Coder Social home page Coder Social logo

stevenmmortimer / salesforcer Goto Github PK

View Code? Open in Web Editor NEW
81.0 17.0 19.0 11.49 MB

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

Home Page: https://stevenmmortimer.github.io/salesforcer/

License: Other

R 99.97% Shell 0.03%
r salesforce r-package r-programming r-language api-wrappers salesforce-apis

salesforcer's Introduction

salesforcer

R Build Status CRAN Status Lifecycle: Stable Monthly Downloads Coverage Status

{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

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

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:

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:

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

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!

# 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)
#> [1] "Organization Id: 00D6A0000003dN3UAI"
sprintf("User Id: %s", user_info$userId)
#> [1] "User Id: 0056A000000MPRjQAO"

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.

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
#> # A tibble: 2 × 2
#>   id                 success
#>   <chr>              <lgl>  
#> 1 0033s00001BXHqaAAH TRUE   
#> 2 0033s00001BXHqbAAH TRUE

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.

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
#> # A tibble: 2 × 3
#>   Id                 FirstName LastName        
#>   <chr>              <chr>     <chr>           
#> 1 0033s00001BXHqaAAH Test      Contact-Create-1
#> 2 0033s00001BXHqbAAH Test      Contact-Create-2

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

# Update some of those records
queried_records <- queried_records %>%
  mutate(FirstName = "TestTest")

updated_records <- sf_update(queried_records, object_name = "Contact")
updated_records
#> # A tibble: 2 × 2
#>   id                 success
#>   <chr>              <lgl>  
#> 1 0033s00001BXHqaAAH TRUE   
#> 2 0033s00001BXHqbAAH TRUE

Bulk Operations

For really large operations (inserts, updates, upserts, deletes, and queries) Salesforce provides the Bulk 1.0 and Bulk 2.0 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.

# 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
#> # A tibble: 2 × 4
#>   Id                 Success Created Error
#>   <chr>              <lgl>   <lgl>   <lgl>
#> 1 0033s00001BXHqfAAH TRUE    TRUE    NA   
#> 2 0033s00001BXHqgAAH TRUE    TRUE    NA

# 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
#> # A tibble: 2 × 3
#>   Id                 FirstName LastName        
#>   <chr>              <chr>     <chr>           
#> 1 0033s00001BXHqfAAH Test      Contact-Create-1
#> 2 0033s00001BXHqgAAH Test      Contact-Create-2

# delete these records using the Bulk 2.0 API
deleted_records <- sf_delete(queried_records$Id, "Contact", api_type = "Bulk 2.0")
deleted_records
#> # A tibble: 2 × 4
#>   Id                 sf__Id             sf__Created sf__Error
#>   <chr>              <chr>              <lgl>       <lgl>    
#> 1 0033s00001BXHqfAAH 0033s00001BXHqfAAH FALSE       NA       
#> 2 0033s00001BXHqgAAH 0033s00001BXHqgAAH FALSE       NA

Using the Metadata API

Salesforce is a very flexible platform in that it provides the Metadata API 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.

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: 68 × 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     255    xsd:string  picklist 
#> # … with 63 more rows

# show the picklist selection options for the Account Type field
acct_fields %>% 
  filter(label == "Account Type") %>% 
  .$picklistValues
#> [[1]]
#> # A tibble: 7 × 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

Future

Future APIs to support (roughly in priority order):

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


Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.

Top

salesforcer's People

Contributors

stevenmmortimer avatar weckstm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

salesforcer's Issues

Bad Request (HTTP 400). Failed to get an access token.

I am receiving the following error message:

Error in oauth2.0_access_token(endpoint, app, code = code, user_params = user_params, :
Bad Request (HTTP 400). Failed to get an access token.

This is the code that I am running:

suppressWarnings(suppressMessages(library(dplyr)))
library(salesforcer)
sf_auth()

  1. After running this code, a new browser window opens and I enter our custom domain (to our full Salesforce sandbox).
  2. This then takes me to okta (our single sign-on platform) and I sign-in using my okta credential.
  3. I then click "Allow"
  4. I am then taken to a page that says "Authentication complete. Please close this page and return to R."
  5. When I return to R, I get the error message: Error in oauth2.0_access_token(endpoint, app, code = code, user_params = user_params, : Bad Request (HTTP 400). Failed to get an access token.

I am using salesforcer version 0.1.3.9.000 and R version 3.6.1.

I'm not sure what I am doing wrong. Am I missing a step?

Is it possible to obtain "session" using salesforcer?

Hello, Is there a way to obtain "session" using salesforcer? (analog to rforcecom::rforcecom.login)

I ask because I would rather use salesforcer::sf_auth() to authenticate (withouth declaring my credentials in the code), but if I do I am not able to retrieve "session" - which, in turn, is an argument of the function rforcecom::rforcecom.getObjectDescription(session, objectName) which is very useful for me. Thanks!

InvalidSessionId

When I use OAuth2 for authentication, my bulk API 1.0 query works fine.

However, when I use username, password, security token to authenticate, I got
"Error in catch_errors(httr_response) : InvalidSessionId: Unable to find session id" error.

I can confirmed that my authentication works because "sf_user_info()" gave me correct result (which is only possible when authentication is successful.

I'm using salesforcer 0.1.2, and R version 3.5.1.

Package cuts value off and rounds

Hey!

First of all: thanks for this package! It made it very easy for me and my team to fetch salesforce data. But we experience a problem and you probably can help us: we save the google client id (f.e.: "123456789.123456789") of our users into SF, but when we try to fetch it via salesforcer it seems that the package cuts some numbers off. Sometime we only get "123456789.12345", sometime "123456789.1234" and so on. The biggest problem is that it is rounding the last number, so it can go from "123456789.123456789" to "123456789.12346".

image

Here I have an example with the ORGanizer for Salesforce Chrome Plugin. Thanks for your help!

Update picklist

Building on my previous question from two years ago(#7), I think I am still lost as to how we would update an existing field's picklist values. I am attempting to read the docs and map that to the R way of doing things here, but I must be missing something obvious. Your docs and package are absolutely fantastic, so what would you recommend that I look for a better path on adding to/or deleting from an existing picklist on a custom field.

I hope all is well on your end!

SOQL support for querying last "N" hours modified records

Hi,..
Trying to query records modified in last 1 hour as an example..
Code is scheduled to execute every hour.. So at 4 PM, it should fetch all 3PM onward records. For 12AM, it should fetch 11PM records onwards and so on

Trying below gives error ,
LastModifiedDate>= :Datetime.now().addMinutes(-60)")
that,
,Bind variables only allowed in Apex code

Is there a fix for this or an alternate option.

a. If I use,
LastModifiedDate = TODAY AND
HOUR_IN_DAY(LastModifiedDate) >",hr)

with
hr = hour(with_tz(Sys.time(),tzone = "America/Los_Angeles"))-1

it wont work for 12:00 AM time window..

Would appreciate any workaround or fix here..

Parsing error with error messages in upsert

When doing an upsert, I receive a warning with specific error messages. However, in the object created by sf_upsert, the error column is type logical and filled with NA.

See warning:

upsert <- sf_upsert(io_sf, 'Product_Component__c', 'Product_Component_ID__c', api_type = 'Bulk 1.0')

Warning: 4 parsing failures.

row col expected actual file
1519 Error 1/0/T/F/TRUE/FALSE DUPLICATE_VALUE:Duplicate external id specified: 63-12706-10-09360-76:Product_Component_ID__c -- literal data
1522 Error 1/0/T/F/TRUE/FALSE DUPLICATE_VALUE:Duplicate external id specified: 63-12706-10-09360-76:Product_Component_ID__c -- literal data
3832 Error 1/0/T/F/TRUE/FALSE DUPLICATE_VALUE:Duplicate external id specified: 63-74401-10-09360-76:Product_Component_ID__c -- literal data
3833 Error 1/0/T/F/TRUE/FALSE DUPLICATE_VALUE:Duplicate external id specified: 63-74401-10-09360-76:Product_Component_ID__c -- literal data

Updating campaigns

This is great tool. I don't see a way to interact with campaigns. Is this possible? or planned?

setPassword

wondering if it's possible to make admin utility calls like setPassword() using this package, or using R in general.

Reading Cached OAuth File

I have an R script that I'm trying to run on a regular schedule in Windows 10. Whenever I try to execute script from the Windows command line, I receive the following error:

Error: Failed to create local cache ('.httr-oauth-salesforcer')

I'm not quite sure how to address this issue. Do I have to authenticate to Salesforce from the command line as well? Whenever I execute the script from RStudio, it successfully runs.

Thanks in advance for your help.

I'm running R-3.6.2 on Windows 10 and I'm trying to upsert data using the Bulk 2.0 api.

cannot get textarea fields through SOQL

I'm using salesforcer:

session2 <- salesforcer::rforcecom.login(username, paste0(password, security_token),
apiVersion=getOption("salesforcer.api_version"))

I can connect to my SF repository and get field names for an object. the SQL statement "soql" below gets data only if I filter out field names that are "textarea". Otherwise the query hangs.

I'd like to have access to those textarea fields if possible.

FieldNames=getFieldNames(Object) %>% filter(!type=='textarea')
soql=paste('select', paste(FieldNames$name, collapse=','), 'from', Object)

Why sf_auth() not works for 'Environment Sandbox Partial'?

I have credentials for Salesforce API for 'production' and 'sandbox' data sets

library(salesforcer)

SF_USER_NAME <- "sf_user_name"
SF_PASSWORD <- "sf_password"
SF_TOKEN <- "sf_token"

sf_auth(username = SF_USER_NAME, password = SF_PASSWORD, security_token = SF_TOKEN)

The code is running fine for "production" settings and fails for "Environnement Sandbox Partial" ("sandbox") settings. The most strange thing that "sandbox" settings work for Python code.

Any ideas on how to fix this bug for 'sandbox'?

Thanks

Can't Add Records to just created Custom Object

I had an idea for a blog post, but I ran into an error that I didn't see coming and admittedly am not sure what I am doing, though its probably on my end.

In short, I am creating a custom object on the fly, adding a couple of fields, and then I want to throw data into those fields. Everything works as expected until I try to insert the test record, with the error telling me that the two fields do not exist, though they are clearly visible on the object within SFDC.

That said, if I point/click the new fields, I can add the same test record.

###################### define the core info for the custom object
base_obj_name <- "ExampleObject"
custom_object <- list()
custom_object$fullName <- paste0(base_obj_name, "__c")
custom_object$label <- paste0(gsub("_", " ", base_obj_name))
custom_object$pluralLabel <- paste0(base_obj_name, "s")

## define the core name field as an autonumber
custom_object$nameField <- list(displayFormat = 'CM-{0000}', 
                                label = paste0(base_obj_name, ' Number'), 
                                type = 'AutoNumber')

## additional settings for the custom object
custom_object$deploymentStatus <- 'Deployed'
custom_object$sharingModel <- 'ReadWrite'
custom_object$enableActivities <- 'true'
custom_object$description <- paste0(base_obj_name, " created by the Metadata API")
custom_object$FIELD_INTEGRITY_EXCEPTION <- 'Deployed'

## deploy the object into our org
custom_object_result <- sf_create_metadata(metadata_type = 'CustomObject',
                                           metadata = custom_object)


###################### create two fields

## custom field for database id -- assumes the data are being imported
cf1 <- tibble(fullName=c(paste0(base_obj_name, '__c.MyDbId__c')),
                        label=c('External Database Id'), 
                        type=c('Number'),
                        precision = 10,  ## how large is the number on both side of decimal
                        scale = 0,  ## how many to the right of the decimal
                        required="false",
                        unique="true",
                        externalId="true")
cf1_result <- sf_create_metadata(metadata_type = 'CustomField', 
                                 metadata = cf1)


## a long text field
cf2 <- tibble(fullName=c(paste0(base_obj_name, '__c.SomeText__c')),
                        label=c('Chat Message'), 
                        type=c('LongTextArea'),
                        length = 1000,
                        visibleLines = 4)
cf2_result <- sf_create_metadata(metadata_type = 'CustomField', 
                                 metadata = cf2)

## check the successes
cf1_result; cf2_result;

## put 1 record in
my_r = tibble(MyDbId__c = 1,
              SomeText__c = "Hello World")
add1 = sf_create(my_r, object_name = custom_object$fullName)

add1 shows me this error:

> add1$errors.message
[1] "ExampleObject__c: bad field names on insert/update call: SomeText__c, MyDbId__c"

But after pointing/clicking the fields, I can add the data, so its something about programmatically creating the fields that is tripping me up.

my_r2 = tibble(MyDbID2__c = 1,
              Some_Text2__c = "Hello World")
add2 = sf_create(my_r2, object_name = custom_object$fullName)

## status of attempt to put data into point/click fields
> add2
# A tibble: 1 x 2
  id                 success
  <chr>              <chr>  
1 a02f200000f8I9sAAE true   

example-object-error

API version requirement for sf_query_bulk

Hi Steve, this is more of a question than an issue.
I am using sf_query_bulk to extract data from Bulk API. But it errors out as "JSON content type is not supported". I see from the logging that it is using API version 35.0. It works fine when I am using it with my personal Salesforce developer account. (I installed package from CRAN in both the environments I mentioned.) One difference I see is that developer account's end points use API version 47.0. I tried to overwrite the client's API to 47.0 by using Rforcecom.login but it is not working. Does this package only support latest API versions and upgrading API version is something that needs to be done at the SalesForce administrator level?
Below is the query I am using in both the environments:
df <- sf_query_bulk(soqlQuery,
object_name = "Account",
queryall = FALSE,
guess_types = TRUE,
api_type = "Bulk 1.0",
interval_seconds = 5,
max_attempts = 100,
verbose = TRUE)

(This is my first encounter with Salesforce APIs, appreciate your help and it's been a great experience working with this library)

API Bulk 2.0 doesn't return any ID fields

When we create anything that is a child of another record type (i.e. create an opportunity on an account) then the returned data frame has 'NA' for all of the ID fields that were included to create the records (i.e. we get opportunity IDs back, but lose the account IDs).

Fix Parsing of Relationship Queries

Currently, salesforcer is providing weird results for relationship queries, including some unexplained "hanging" behavior (noted in #19 and #35). We should take a closer look into identifying and parsing these types of queries. Below are some odd examples currently happening with v0.1.3. Only Child-to-Parent queries made with the REST API appear to be working as expected (flattening).

Child-to-Parent Queries

REST API

sf_query("SELECT Name, Account.Id, Account.Name FROM Contact")
#> # A tibble: 3 x 3
#>   Name         Account.Id         Account.Name                   
#>   <chr>        <chr>              <chr>                          
#> 1 Ashley James 0016A0000035mJCQAY United Oil & Gas, UK           
#> 2 Avi Green    0016A0000035mJ9QAI United Oil & Gas Corp.         
#> 3 Babara Levy  0016A0000035mJAQAY Express Logistics and Transport

SOAP API

sf_query("SELECT Name, Account.Id, Account.Name FROM Contact", api_type="SOAP")
#> # A tibble: 3 x 3
#>   Id    Name         Account         
#>   <lgl> <chr>        <list>          
#> 1 NA    Ashley James <named list [4]>
#> 2 NA    Avi Green    <named list [4]>
#> 3 NA    Babara Levy  <named list [4]>

Parent-to-Child Queries

REST API

sf_query("SELECT Name, (SELECT LastName FROM Contacts) FROM Account")
#> # A tibble: 9 x 4
#>   Name                     Contacts.totalSi… Contacts.done Contacts.records
#>   <chr>                                <int> <lgl>         <list>          
#> 1 GenePoint                                1 TRUE          <named list [3]>
#> 2 United Oil & Gas, UK                     1 TRUE          <named list [3]>
#> 3 United Oil & Gas, Singa…                 2 TRUE          <named list [3]>
#> 4 Edge Communications                      2 TRUE          <named list [3]>
#> 5 Burlington Textiles Cor…                 1 TRUE          <named list [3]>
#> 6 Grand Hotels & Resorts …                 2 TRUE          <named list [3]>
#> 7 Express Logistics and T…                 2 TRUE          <named list [3]>
#> 8 University of Arizona                    1 TRUE          <named list [3]>
#> 9 United Oil & Gas Corp.                   3 TRUE          <named list [3]>

SOAP API

sf_query("SELECT Name, (SELECT LastName FROM Contacts) FROM Account", api_type="SOAP")
#> # A tibble: 24 x 4
#>    Id    Name                        Contacts         LastName
#>    <lgl> <chr>                       <list>           <chr>   
#>  1 NA    GenePoint                   <named list [4]> <NA>    
#>  2 NA    <NA>                        <NULL>           Frank   
#>  3 NA    United Oil & Gas, UK        <named list [4]> <NA>    
#>  4 NA    <NA>                        <NULL>           James   
#>  5 NA    United Oil & Gas, Singapore <named list [5]> <NA>    
#>  6 NA    <NA>                        <NULL>           D'Cruz  
#>  7 NA    <NA>                        <NULL>           Ripley  
#>  8 NA    Edge Communications         <named list [5]> <NA>    
#>  9 NA    <NA>                        <NULL>           Forbes  
#> 10 NA    <NA>                        <NULL>           Gonzalez
#> # … with 14 more rows

In both examples above there is a wonky way of flattening the nested results which either returns unnecessary attributes data (REST API) or mismatches the attributes with the child data results (SOAP API). This needs to be fixed.

sf_query() timeout on queries with more than 260 results

Maybe I don't understand SOQL well enough, but I tried below query with a limit of 260 and 270 and received the expected results with the former, a time-out with the latter.

Any ideas?

sf_query("SELECT o.Id, o.Owner.Name, o.LeadSource, o.Name, o.AccountId, o.CreatedDate, o.Probability, o.CloseDate, o.StageName, o.OwnerRole__c, Close_Reason__c, Reference_Opportunity__c, CampaignId, Campaign.Name, 
          (SELECT Id, ...,  FROM OpportunityLineItems) 
          FROM Opportunity o 
          WHERE o.OwnerRole__c like 'Sales%' OR o.OwnerRole__c like 'CustomerSuccess'
          LIMIT 260")

works

sf_query("SELECT o.Id, o.Owner.Name, o.LeadSource, o.Name, o.AccountId, o.CreatedDate, o.Probability, o.CloseDate, o.StageName, o.OwnerRole__c, Close_Reason__c, Reference_Opportunity__c, CampaignId, Campaign.Name, 
          (SELECT Id, ...,  FROM OpportunityLineItems) 
          FROM Opportunity o 
          WHERE o.OwnerRole__c like 'Sales%' OR o.OwnerRole__c like 'CustomerSuccess'
          LIMIT 270")

time-out

Text field with numbers separated by commas getting read as single number

I am trying to retrieve contract number field which is a text field in SFDC.
The field has following numbers populated separated by comma.
41748710,41748711
While querying through RForcecom, it appears as two numbers separated by comma.
But using salesforcer, it gets read as 4174871041748711 , a single number.. Can you please help fix this?

Fail on: Object Description and Fields Description

Hi, I'm using the sf_describe_object() and sf_describe_object_fields() quite frequently in order to retrieve the list of fileds and how they are related and connected to every other object in our SalesForce. Recently I started to had this issue while trying to retrieve the object's fields using sf_describe_object_fields()

# INPUT
obj.fields  <- sf_describe_object_fields(object_name = "Case")

# OUTPUT gives me the Error
Error in (function (..., row.names = NULL, check.rows = FALSE, check.names = TRUE,  : 
  arguments imply differing number of rows: 1, 0

Assuming that there are some fields that have unequal number of rows, therefore it could not convert the list to data.frame on the step of retrieving the Object description itself.

Any Idea of how to workaround this error?

sf_download_attachment() - output file names

Hello,
I hope you're doing well.
I'm using your package on a daily basis and it's awsome.
One feature might be useful to add though.
I want to download a lot of attachments, and a lot of them have the same names.
Function sf_download_attachment() overwrites them in the output.
Maybe you could add an argument where user defines what name should the output file have?
Regards,
Alek

Not able to update records with NULL values

Hi,
Have 2 issues using package while updating null values with sf_update function..
Ex: sf_update(test,"Case")

  1. While trying to write back NA date values
    When NA(formatted as date) values is to be written back, it shows following error..

HTTP error 500 on attempt 1 ...
backing off 1.00 seconds, retrying
HTTP error 500 on attempt 2 ...
backing off 3.00 seconds, retrying
HTTP error 500 on attempt 3 ...
backing off 0.56 seconds, retrying
HTTP error 500 on attempt 4 ...
backing off 6.10 seconds, retrying
Error in catch_errors(httr_response) :
'NA' is not a valid value for the type xsd:date

While using createBulkBatch method , this was smooth. But we cannot continue using bulk updates due to salesforce restrictions for number of bulk operations per day in our org.

  1. When Null values to be updated to picklist fields
    While using createBulkBatch updates, Null value written back will delete the existing values in the picklist field.
    Example:
    Before write back :
    Substatus = "Waiting for AI process"
    After writing back NA value,
    Substatus will be blank. History of record will say the substatus is deleted.

But while using sf_update, the "NA" value is getting written back. Is there a way to make this to be delete of existing value than writing back "NA" as a value?

Thanks a lot for all your support..

Download Attachment Body Content

Hi
Thank you for a great package. Very useful to me.

I would like to download attachment and I am struggling with the final step.

Here is what I do

sf_auth() b <- sf_query("SELECT Body, Name, Id FROM Attachment WHERE ParentId = 'XX'")

and I get a list of references back
>glimpse(b)

Body <chr> /services/data/v42.0/sobjects/Attachment/00P5700001SCWgXXXX/Body"
Name <chr> "Doc.pdf"
Id <chr> "00P570000X"

How do I download Doc.pdf which is hiding inside /services/data/v42.0/sobjects/Attachment/00P5700001SCWgXXXX/Body?

sf_auth prevents alternative login_url during OAuth

When suppplying an alternative login_url to sf_auth during OAuth, it still redirects to login.salesforce.com, which makes it impossible to OAuth to the Sandbox environment (test.salesforce.com).

I am not sure, but I suspect the following section:

sf_oauth_endpoints <- oauth_endpoint(request = NULL,
                                           base_url = "https://login.salesforce.com/services/oauth2",
                                           authorize = "authorize", access = "token", revoke = "revoke")

Can't log in using OAuth 2.0 authentication

I can log in using the basic username-password authentication shown in the documentation. However if I try login using OAuth 2.0 authentication running just the line of code sf__auth() I get the error:
Error in oauth_app("salesforce", key = consumer_key, secret = consumer_secret, : unused argument (redirect_uri = callback_url)
Can you assist? Many thanks.

api_type = "Bulk 1.0" (InvalidSessionId: Unable to find session id

Great package so far. Looking forward to the Reports & Dashboard APIs.

Was playing around with creating contacts, and got an error message when passing api_type="Bulk 1.0" through the sf_create() function . When removing the api_type argument, the records were created successfully.

library(salesforcer)

# Connect to Salesforce
sf_auth(username = "[redacted]"
        password = "[redacted]"
        security_token = "[redacted]"
        login_url = 'https://test.salesforce.com/')

# View User Info (Checking the authentication... redacted for this example)
sf_user_info()
#> $aboutMe
#> NULL

#> $displayName
#> [1] "Seth Goldman"

#> $username
#> [1] "[redacted]"

# Use Bulk API to create two new contacts
n <- 2
new_contacts <- tibble(FirstName = rep("Test",n),
                       LastName = paste0("Contact-Create-",1:n))
#> Error in tibble(FirstName = rep("Test", n), LastName = paste0("Contact-Create-", : could not find function "tibble"

create_contacts <- sf_create(new_contacts, object_name="Contact",api_type = "Bulk 1.0")
#> Error in is.data.frame(input_data): object 'new_contacts' not found

Created on 2018-07-06 by the reprex package (v0.2.0).

sf_create_attachment results in Error in FUN(X[[i]], ...) : invalid 'file' argument

Found that the function sf_create_attachment gives the error
Error in FUN(X[[i]], ...) : invalid 'file' argument
When checking the files exist:
files_exist <- sapply(head(dat[,column], n_check), file.exists)

IF you use a data.frame, changing it to build the object with tibble resolves the issue.

    attachment_details <- tibble(
        ParentId = caseRecords$id,
        Body = paste(getwd(), csvdata$File, sep="/"),
        Name = sub(".*/(.*)","\\1", csvdata$File)
    )
    print(attachment_details)
    result <- sf_create_attachment(attachment_details, api_type = "REST", verbose = FALSE)

Not sure if a bug to be fixed or at least documented warning

Some columns not available to rforcecom.query

I am able to successfully run a SOQL query like "SELECT Id, RelatedToId FROM EmailMessage" from the Salesforce Developer Console and the official Salesforce CLI.

When I run the same SOQL query through salesforcer (or Rforcecom) I get the message "No such column 'RelatedToId' on entity 'EmailMessage'." That error is consistent with the available fields I see from from the rforcecom.getObjectDescription(session, "EmailMessage") command, but I can't figure out why it is that the RelatedToId field is available from some query methods, but not Salesforcer.

Generalize a method for providing SOAP and REST headers

Currently, functions like sf_create and sf_update have API call header options hard-coded into the function definition. For example, all_or_none being the option that "Allows a call to roll back all changes unless all records are processed successfully."

The problem is that there are many more header options for each of these functions that the user may want to take advantage of. We should create a method similar to glm.control() that allows users to freely specify the options in the main function in this case (glm()) or supply it as a list directly to the control argument inside the glm() function.

sf_describe_object_fields() fails for some objects

Hello,
I'm getting an error when running sf_describe_object_fields("Object__c") for some objects.
The error message is:
"Error: All columns in a tibble must be 1d or 2d objects:
Column validFor is NULL"

For example this error occurs for "Account" and "Opportunity" objects
but not for "Event" or "Task" objects in my org.
Would you be able to give me any hints?
Maybe this error is already knowned?

Many thanks in advance

Cannot connect to UAT using sf_auth

Hi,
I am trying to use sf_auth authentication instead of basic authentication as recommended.
I am testing the code currently on sandbox environment.But while trying to run sf_auth(), a browser pops with salesforce production page.
I tried specifying login url with sf_auth, sf_auth(loginurl=uat.my.salesforce.com), with no luck. How can sf_auth be made working for sandbox environment?
Regards
Hareesh

oauth() closing problems

Hello,
I'm using oauth() function for authorisation purposes.
Problem is, that when I want to change enviornment from one staging to another,
I have to... reboot the computer. For some reason, deleting the .httr-oauth-salesforcer file
and .gitignore file doesn't help. R is still able to reconnect to salesforce enviornment.
Probably I'm doing something wrong.
But maybe it would be useful to add funcion like oauth.close() to the package, to be able to automatically clear the authorisation?

Many thanks

sf_token() triggering OAuth after establishing valid Basic auth session

The function sf_token() uses token_available() to determine if .state$token exists and if not, runs sf_auth(), which triggers the OAuth workflow to the browser or loads token from a cached .httr-oauth file. The sf_token() should be changed to also check if a "session" from a basic auth scenario exists and use that.

Error created for Bulk query with zero records in response

Hi
I am getting an error when running bulk query with zero response records. See below for trace

Error: 'Records not found for this query' does not exist in current working directory ('C:/dev/x').
9. stop("'", path, "' does not exist", if (!is_absolute_path(path)) paste0(" in current working directory ('", getwd(), "')"), ".", call. = FALSE)
8. check_path(path)
7. standardise_path(file)
6. read_delimited(file, tokenizer, col_names = col_names, col_types = col_types,
locale = locale, skip = skip, skip_empty_rows = skip_empty_rows,
comment = comment, n_max = n_max, guess_max = guess_max,
progress = progress)
5. read_csv(response_text)
4. sf_query_result_bulk(job_id = batch_query_info$jobId, batch_id = batch_query_info$id,
result_id = batch_query_details$result, api_type = "Bulk 1.0",
verbose = verbose)
3. sf_query_bulk() at sf_dump.R#179
2. extract_attachement_from_service(record_limit, db) at sf_dump.R#66

  1. main()

Would it be possible to get a nicer output from the package?

NULL Salesforce values causing Tibble error when listing object fields

I was receiving the below error on random objects when pulling object fields using sf_describe_object_fields. I traced it back to the function collapse_list_with_dupe_names, specifically "collapsed <- map_df(obj_field_dupes, as_tibble)". The dupe_field_names list contained lists with NULL values, so when converting to a tibble the error was thrown.

I wasn't able to find a good work around on the tibble package, so I put together the below to simply replace any NULLs with NAs and it now works beautifully.

collapsed <- map_df(obj_field_dupes, function(x) as_tibble(lapply(x, function(y) if(is.null(y)) NA else y)))

image

Setting Audit Fields (CreatedBy, CreatedDate, LastModifiedBy, LastModifiedDate) when uploading files

I am uploading files to SalesForce (sf_create_attachement) from a legacy system and want to retain the original file creation date.

According to https://salesforce.stackexchange.com/questions/140868/preserving-file-creation-date-and-last-modified-date-when-uploading-files-to-c/140903 it is possible by

Check Set audit field values for imported records

Step 1: You need to enable "Enable Set Audit Fields upon Record Creation and Update Records with Inactive Owners" User Permissions" permission from Setup-->Customize-->User Interface. This will allow you to set Created Date on a record.

Step 2: Also you need to give following permission to a user who is loading those data "Set audit fields, such as Created By and Last Modified By, when you create a record (API only)."

Is this something that is supported by SalesforceR
Or do you propose another approach

Thanks for your great work!

Nested Parent-to-Child Relationship Query Hangs

Thanks for the great library. Had been using bulk query for a while.

I hit into maximum limit of records returned per API call (2000 rows). The workaround is to break down into multiple queries to work around the limitation.

I had used python library simple-salesforce before, and never hit into such limitation. With some research, it seems python library implemented query more feature (pagination) which enable it to retrieve up to 50,000 rows per API call. The research leads me to:
.
https://help.salesforce.com/articleView?id=000339353&type=1&mode=1

Not sure if there is a plan to implement this here. Thanks.

Print XML or JSON on all calls when verbose is set to TRUE

Currently, only the Metadata and Bulk 1.0 API calls print their XML when verbose=TRUE. Make it so that the actual request body is printed in XML or JSON for the SOAP, REST, and Bulk 2.0 APIs as well.

This feature was initially requested in #8.

Error with Bulk Query After Timeout

Often I get a timeout error running the sf_query_bulk() function.

Function's Time Limit Exceeded. Aborting Job Now Error in match.arg(api_type) : 'arg' should be one of “Bulk 2.0”

It tells me to use the api_type of "Bulk 2.0" but if I try run
sf_query( ..., api_type = "Bulk 2.0")
I get the error
Error in match.arg(api_type) : 'arg' should be one of “REST”, “SOAP”, “Bulk 1.0”

Is there anyway to use Bulk 2.0?

Bulk 2.0 Cannot Create Records with Numerics

We're trying to run sf_create() on the Bulk 2.0 API, but whenever we run it with a dataframe that includes numeric variables, we get the following error: "Error: Column can't be converted from character to numeric". Have you ever experienced this before?

This looks like a dplyr bind_rows() error, and that function is used several times throughout the salesforcer package so we can't find where exactly it's breaking.

Parsing Failures

Hi Steven,

I'm having an issue loading an object from salesforce. The problem is one of the columns is expected to be a double and I need it to be a string. Here is my code:

salesforce_data <- sf_query_bulk(paste0("SELECT ",field_list," FROM my_object"), object_name = "my_object")

The record is about 130k rows and I get a parsing error on 254 of them where it expected a double:
Warning: 254 parsing failures. row col expected actual file 19845 my_field a double ag:23842931048080057 literal data 19846 my_field a double ag:23842931012580057 literal data 19847 my_field a double ag:23842931069470057 literal data 19848 my_field a double ag:23842931028810057 literal data 19849 my_field a double ag:23842931028810057 literal data ..... ................. ........ .................... ............ See problems(...) for more details.
capture

(254 more of the same)

All of the issues are of the same field, I would like the whole column to be brought through as string so I don't lose these rows - as they currently come through as null after the parsing failure.
Is there anyway to specify what type to bring the columns through as? Or bring them all through as strings?

Thanks,
Ryan

Enable PKChunking for sf_query_bulk

I am attempting to query a large table (~30M records) from Salesforce to R using the SalesforceR package, and hope to do so without splitting up into many queries.

The following query succeeds, if I limit the records:

OrderItemsSF <- sf_query_bulk("SELECT Id,Order,Product,Quantity FROM OrderItems LIMIT 1000000",object_name = "OrderItems", api_type = "Bulk 1.0", verbose = TRUE, max_attempts = 1000)

This query fails ("Error: column name 'result' must not be duplicated") whenever it takes longer than 10 minutes, which fits with SF's documentation saying bulk queries retry after 10 minutes. I believe the answer is to enable PKChunking to automatically separate my query into smaller batches, but I am having trouble finding a working syntax for this.

I have tried the following:

OrderItemsSF <- sf_query_bulk("SELECT Id,Order,Product,Quantity FROM OrderItems LIMIT 1000000",object_name = "OrderItems", api_type = "Bulk 1.0", verbose = TRUE, max_attempts = 1000, control = sf_control(PKChunkingHeader = list(Sforce-Enable-PKChunking= TRUE)))

This results in the error "Error in catch_errors(httr_response) : ClientInputError: Sforce-Enable-PkChunking doesn't have a valid value. The same error results from using FALSE, and according to the documentation (see link 2 at bottom), FALSE is the default for this parameter, so that doesn't make sense!

Other syntax attempts, like the one below, succeed on queries under 10 minutes and fail over 10 minutes, suggesting that R is ignoring this text and not actually PKChunking:

OrderItemsSF <- sf_query_bulk("SELECT Id,Order,Product,Quantity FROM OrderItems LIMIT 1000000",object_name = "OrderItems", api_type = "Bulk 1.0", verbose = TRUE, max_attempts = 1000, control = list(Sforce-Enable-PKChunking= TRUE))

I'm at a loss for what seems like a simple syntax issue, and can't find any specific examples online of PKChunking in a SalesforceR query, despite the documentation saying this can be done. I'd greatly appreciate some guidance here.

Proxy Support

Hey Steven

Thanks for your hard work on this package. It's just great and very appreciated.

When I am using httr I normally have to set the proxy manually (using proxy_settings function from httr) due to my company network particularities. Is that a way to set the proxy definition within salesforcer?

Thanks
Douglas

UNSUPPORTED_API_VERSION: Invalid Api version specified on URL

I've using 0.1.2 without problem, after update to 0.1.3 I got the following error

sf_auth()

user_info <- sf_user_info()

Error in catch_errors(httr_response) : UNSUPPORTED_API_VERSION: Invalid Api version specified on URL.

I tried to use basic auth with user and password it didn't work
I noticed if I use rforcecom.login with apiVersion = "45.0" it works.

is it a problem with the api version? how may use an api version using sf_auth() ?

Regards.

Clarification on sf_update success

Hi ,
Does the status of sf_update function reflect the status as returned from salesforce after a particular filed gets updated?

I have a repeating scenario as follows in production code..

When a record gets created, there is a field which says "waiting for ML process". My R code runs every minute scanning this status and picks records whichever is waiting for my process.. Once my process is complete, it updates status to "processed by ML".

Now couple of times for ~8-10 Hrs, I see same records get picked multiple times because the status change is not getting updated in salesforce.. The process log shows the data being sent out from process..

I am aware of success field in sf_update function.. Does this field reflect status returned by salesforce?

Are there scenarios where despite success value being true, value in saleforce field doesnot get updated?

Your help is highly appreciated.. Let me know if you need any more elaborations

Thanks in advance

Metadata Custom Field - Create Picklist

It appears that picklists are deprecated, or will be, in the API. That said, I simply want to create a custom field that acts like a single picklist via the APi.

I have attempted many variants, but the latest is:

cf3 <- tibble(fullName=c(paste0(base_obj_name, '__c.MessageIntent__c')),
                        label=c('Message Intent'), 
                        type = 'valueSet',
                        valueName = c("greet", "affirm"))
cf3_result <- sf_create_metadata(metadata_type = 'CustomField', 
                                 metadata = cf3)

and seemingly no matter how many combinations of parameters, I get some version of this error:

You must specify either picklist, globalPicklist, or valueSet

Perhaps its something obvious that I am overlooking, but any help will be appreciated.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.