The latest version of the salesforcer package (v0.1.3) is now available on CRAN and it is ready to help you better manage your Salesforce data. Along with a host of bug fixes this release has three big features:
Salesforce is an organizational tool and with many hands touching the system sometimes
you are called in to do some detective work to figure out which records have been
deleted or updated in your org. Luckily, Salesforce has your back. Their SOAP API has
two methods that will return the deleted or updated records within a timeframe. Those
two methods have been implemented in salesforcer 0.1.3 as sf_get_deleted()
and
sf_get_updated()
. Here is an example pulling out anything deleted yesterday:
library(tidyverse)
library(lubridate)
# if no timezone is provided UTC is assumed
deleted_recs <- sf_get_deleted("Contact", start = Sys.Date() - 1, end = Sys.Date())
# here is how to pull deleted using yesterday according to EDT with help from lubridate
deleted_recs <- sf_get_deleted("Contact",
start = ymd(Sys.Date()-1, tz="America/New_York"),
end = ymd(Sys.Date(), tz="America/New_York"))
deleted_recs
#> # A tibble: 125 x 2
#> deletedDate id
#> <dttm> <chr>
#> 1 2020-09-11 19:08:40 0033s000014AyIXAA0
#> 2 2020-09-11 19:08:40 0033s000014AyIbAAK
#> 3 2020-09-11 19:08:46 0033s000014AyIgAAK
#> 4 2020-09-11 19:11:08 0033s000014AyLFAA0
#> 5 2020-09-11 19:11:08 0033s000014AyLGAA0
#> # … with 120 more rows
Now if you see records in there that you may want back, now it’s as simple as calling
the sf_undelete()
function with the ids of the records you want.
# undelete the first three records from the list of records deleted yesterday
undeleted <- sf_undelete(deleted_recs$id[1:3])
undeleted
#> # A tibble: 3 x 2
#> id success
#> <chr> <lgl>
#> 1 0033s000014AyIXAA0 TRUE
#> 2 0033s000014AyIbAAK TRUE
#> 3 0033s000014AyIgAAK TRUE
You can double check that the records are actually no longer deleted by running a
query for them and checking the IsDeleted
field like this:
# check that the first three records are no longer deleted
is_not_deleted <- sf_query(sprintf("SELECT Id, IsDeleted
FROM Contact
WHERE Id IN ('%s')",
paste0(undeleted$id, collapse="','")))
is_not_deleted
#> # A tibble: 3 x 2
#> Id IsDeleted
#> <chr> <lgl>
#> 1 0033s000014AyIXAA0 FALSE
#> 2 0033s000014AyIbAAK FALSE
#> 3 0033s000014AyIgAAK FALSE
Now let’s say you really do want them deleted and by that, I mean permanently from your org so they do not count against your space quota. Now you can empty items in your Recycle Bin. Here is an example where we delete the records we just undeleted and then empty them from the Recycle Bin.
# delete the records to move them to the Recycle Bin, them empty them from it
deleted <- sf_delete(is_not_deleted$Id)
really_deleted <- sf_empty_recycle_bin(deleted$id)
really_deleted
#> # A tibble: 3 x 2
#> id success
#> <chr> <lgl>
#> 1 0033s000014AyIXAA0 TRUE
#> 2 0033s000014AyIbAAK TRUE
#> 3 0033s000014AyIgAAK TRUE
If you want to double-check the field values of a deleted record, just make sure to
set queryall = TRUE
in your query so that it returns deleted or archived records in the
resultset. Note that after records are deleted from the Recycle Bin using this call,
they can be queried using queryall = TRUE
for some time. Typically this time is
24 hours, but may be shorter or longer.
# permanently deleted records not there in regular query
not_visible <- sf_query(sprintf("SELECT Id, IsDeleted
FROM Contact
WHERE Id IN ('%s')",
paste0(really_deleted$id, collapse="','")))
not_visible
#> # A tibble: 0 x 0
# but still there if using queryall for ~24 hrs
still_visible <- sf_query(sprintf("SELECT Id, IsDeleted
FROM Contact
WHERE Id IN ('%s')",
paste0(really_deleted$id, collapse="','")),
queryall = TRUE)
still_visible
#> # A tibble: 3 x 2
#> Id IsDeleted
#> <chr> <lgl>
#> 1 0033s000014AyIXAA0 TRUE
#> 2 0033s000014AyIbAAK TRUE
#> 3 0033s000014AyIgAAK TRUE
Part of having a Salesforce instance means maintaining a high-level of data integrity.
That means not allowing duplicates to crop up and, if they do, merging and deleting
them quickly so that any activity with a record can be tracked in a single place.
In salesforcer 0.1.3 you can find duplicates using the Duplicate Rules already in
place in your org. In the example below I will create three records with the same
email address. I’ve already set up a Duplicate Rule in my org to recognize and prevent
records from being creating if they have the same email address as another record already
in Salesforce. When I run sf_find_duplicates()
it finds all three records.
# create the records
new_contacts <- tibble(FirstName = rep("Rick", 3),
LastName = rep("James", 3),
Email = rep("rick.james@gmail.com", 3),
Description = c("Musician", "Funk Legend", NA))
new_contacts
#> # A tibble: 3 x 4
#> FirstName LastName Email Description
#> <chr> <chr> <chr> <chr>
#> 1 Rick James rick.james@gmail.com Musician
#> 2 Rick James rick.james@gmail.com Funk Legend
#> 3 Rick James rick.james@gmail.com <NA>
# insert the records
new_recs <- sf_create(new_contacts, "Contact",
DuplicateRuleHeader = list(allowSave = TRUE,
includeRecordDetails = FALSE,
runAsCurrentUser = TRUE))
new_recs
#> # A tibble: 3 x 2
#> id success
#> <chr> <lgl>
#> 1 0033s000014B3IZAA0 TRUE
#> 2 0033s000014B3IaAAK TRUE
#> 3 0033s000014B3IbAAK TRUE
# find them as dupes
dupes_found <- sf_find_duplicates(search_criteria = list(Email = "rick.james@gmail.com"),
object_name = "Contact")
#> Using Contact rules:
#> - Standard_Rule_for_Contacts_with_Duplicate_Leads
#> - Contact_Dupe_Email
dupes_found
#> # A tibble: 6 x 2
#> sObject Id
#> <chr> <chr>
#> 1 Contact 0033s000013Xt92AAC
#> 2 Contact 0033s000014B35QAAS
#> 3 Contact 0033s000014B35RAAS
#> 4 Contact 0033s000014B3IZAA0
#> 5 Contact 0033s000014B3IaAAK
#> # … with 1 more row
The recordset from sf_find_duplicates()
shows the object that the records were found in
and their Ids. You will notice that those are the same Ids as what we received back when
creating the records to verify that we found those same three records based on the duplicated
email address. You will also notice in the call that there is a message showing which
rules were executed when searching for duplicates, one of them being a custom matching rule
I created called Contact_Dupe_Email
. In order for this function to work you must
have Duplicate Rules configured in your system. There is a lot of good documentation
online about how to set up these rules. Here is a link to one Salesforce Trailhead article
that I like: Resolve and Prevent Duplicate Data.
Now if I’d like to merge those three duplicates records into one I can do so using
the new sf_merge()
function. I need to pick one of the records as the master record
and supply its Id to the function. The other records’ Ids will be the victim_ids
.
All of the fields on the master record will supercede the others, but if you would
like to override some of those fields, then just specify them in the master_fields
argument. In this example, we are merging the second and third record into the first
record. However, I like the description “Funk Legend” from the second record to be
on the master record, so I just specify it in the master_fields
argument.
merge_summary <- sf_merge(master_id = dupes_found$Id[1],
victim_ids = dupes_found$Id[2:3],
object_name = "Contact",
master_fields = c("Description" = "Funk Legend"))
merge_summary
#> # A tibble: 1 x 5
#> id success mergedRecordIds updatedRelatedIds errors
#> <chr> <lgl> <list> <lgl> <lgl>
#> 1 0033s000013Xt92AAC TRUE <chr [2]> NA NA
You can check that the other two records that were merged into the master record have
now been deleted and that they are tagged in the MasterRecordId
field with the Id
of the master record they were merged into.
merge_confirm <- sf_query(sprintf("SELECT Id, MasterRecordId, IsDeleted
FROM Contact
WHERE Id IN ('%s')",
paste0(dupes_found$Id, collapse="','")),
queryall = TRUE)
merge_confirm
#> # A tibble: 6 x 3
#> Id IsDeleted MasterRecordId
#> <chr> <lgl> <chr>
#> 1 0033s000013Xt92AAC FALSE <NA>
#> 2 0033s000014B35QAAS TRUE 0033s000013Xt92AAC
#> 3 0033s000014B35RAAS TRUE 0033s000013Xt92AAC
#> 4 0033s000014B3IZAA0 FALSE <NA>
#> 5 0033s000014B3IaAAK FALSE <NA>
#> # … with 1 more row
You may have noticed an argument called all_or_none
in the previous CRAN release
of salesforcer. This argument is put into the header of the API call so that when
records are created/updated/deleted it will only complete if all records were successful,
otherwise all of the record changes are rolled back. This “All or None” behavior is just
one of over a dozen headers that the SOAP API accepts. The REST, Bulk, and Metadata APIs
also have their own headers which can be set. You can pass these controls into salesforcer
functions directly or by setting the control
argument that now appears in many functions.
For example, if you are inserting records and would like to bypass the Duplicate Rules
in your org, then set the DuplicateRuleHeader) like this:
new_contacts <- tibble(FirstName = rep("Test", 2),
LastName = rep("Contact", 2),
Email = rep("testcontact@gmail.com", 2))
# add control to allow the creation of records that violate a duplicate email rule
new_recs <- sf_create(new_contacts, object_name = "Contact",
DuplicateRuleHeader = list(allowSave = TRUE,
includeRecordDetails = FALSE,
runAsCurrentUser = TRUE))
new_recs
#> # A tibble: 2 x 2
#> id success
#> <chr> <lgl>
#> 1 0033s000014B3IjAAK TRUE
#> 2 0033s000014B3IkAAK TRUE
In the documentation you may notice that the control argument in the sf_create()
function definition
looks like this control = list(...)
. This allows you to include the control headers
as an argument right in the function or put them inside the sf_control
function.
An example of using the sf_control()
function looks like this:
new_recs <- sf_create(new_contacts, object_name = "Contact",
control=sf_control(DuplicateRuleHeader=list(allowSave=TRUE,
includeRecordDetails=FALSE,
runAsCurrentUser=TRUE)))
new_recs
#> # A tibble: 2 x 2
#> id success
#> <chr> <lgl>
#> 1 0033s000014B3HhAAK TRUE
#> 2 0033s000014B3HiAAK TRUE
The sf_control()
function works exactly like glm.control()
when using the glm()
function in the stats package. It will accept any of the extra arguments in the
function call and validate them to see if they are accepted by the API type and operation
that the function is performing. As mentioned before, there are over a dozen control
parameters that are now possible across the various APIs. Those are documented on the
pkgdown website’s documentation for sf_control()
.
There is also a vignette which describes all of this in more detail at Passing Control Args.
For a complete listing of all changes made in the 0.1.3 release of salesforcer please view the NEWS.md file on the pkgdown site. Bug reports and feature requests are welcome in the GitHub issues.
Thank you for your continued support!