diff --git a/DESCRIPTION b/DESCRIPTION index 89cc8469..90890a98 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,36 +1,37 @@ Package: salesforcer Title: An Implementation of 'Salesforce' APIs Using Tidy Principles Version: 0.1.2.9000 -Date: 2018-04-12 -Description: An implementation of the 'Salesforce' Platform APIs (REST, SOAP, +Date: 2019-06-09 +Description: Functions connecting to the 'Salesforce' Platform APIs (REST, SOAP, Bulk 1.0, Bulk 2.0, and Metadata) . - This package is an articulation of the most API methods into R. The API calls - return XML or JSON that is parsed tidy data structures. For more details please - see the 'Salesforces' API references and this package's website + This package is an articulation of the most Salesforce API methods into R. The + API calls return XML or JSON that is parsed tidy data structures. For more details + please see the 'Salesforce' API references and this package's website for more information, documentation, and examples. Authors@R: c( person(c("Steven", "M."), "Mortimer", , "reportmort@gmail.com", c("aut", "cre")), person("Takekatsu", "Hiramura", , "thira@plavox.info", c("ctb")), - person("Jennifer", "Bryan", , "jenny@rstudio.com", c("ctb")), - person("Joanna", "Zhao", , "joanna.zhao@alumni.ubc.ca", c("ctb")) + person("Jennifer", "Bryan", , "jenny@rstudio.com", c("ctb", "cph")), + person("Joanna", "Zhao", , "joanna.zhao@alumni.ubc.ca", c("ctb", "cph")) ) URL: https://github.com/StevenMMortimer/salesforcer BugReports: https://github.com/StevenMMortimer/salesforcer/issues Encoding: UTF-8 Depends: - R (>= 3.1.0) + R (>= 3.5.0) License: MIT + file LICENSE LazyData: true Imports: - methods, - httr, - dplyr, - xml2, - XML, + methods (>= 3.5.0), + XML (>= 3.98-1.19), + httr (>= 1.4.0), + dplyr (>= 0.8.0), + xml2 (>= 1.2.0), + readr (>= 1.3.1), + data.table (>= 1.12.0), jsonlite, purrr, - readr, lubridate Suggests: knitr, diff --git a/NAMESPACE b/NAMESPACE index 1c6349c5..549c821f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,9 +1,12 @@ # Generated by roxygen2: do not edit by hand export(VERB_n) +export(accepted_controls_by_api) +export(accepted_controls_by_operation) export(build_soap_xml_from_list) export(catch_errors) export(collapse_list_with_dupe_names) +export(filter_valid_controls) export(get_os) export(make_base_metadata_url) export(make_base_rest_url) @@ -36,6 +39,7 @@ export(rPATCH) export(rPOST) export(rPUT) export(remove_empty_linked_object_cols) +export(return_matching_controls) export(rforcecom.bulkAction) export(rforcecom.bulkQuery) export(rforcecom.create) @@ -59,6 +63,7 @@ export(sf_batch_details_bulk) export(sf_batch_status_bulk) export(sf_bulk_operation) export(sf_close_job_bulk) +export(sf_control) export(sf_create) export(sf_create_batches_bulk) export(sf_create_job_bulk) @@ -121,6 +126,7 @@ importFrom(XML,xmlSApply) importFrom(XML,xmlSize) importFrom(XML,xmlToList) importFrom(XML,xmlValue) +importFrom(data.table,rbindlist) importFrom(dplyr,"%>%") importFrom(dplyr,as_tibble) importFrom(dplyr,bind_rows) @@ -162,6 +168,7 @@ importFrom(methods,as) importFrom(purrr,map) importFrom(purrr,map_df) importFrom(purrr,map_dfc) +importFrom(purrr,modify) importFrom(purrr,modify_if) importFrom(readr,col_character) importFrom(readr,col_guess) diff --git a/NEWS.md b/NEWS.md index 05a21342..90080b0e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,7 @@ ### Features + * Upgraded to version 46.0 (Summer '19) from version 45.0 (Spring '19) * Add **RForcecom** backward compatibile version of `rforcecom.getObjectDescription()` * Add `sf_describe_object_fields()` which is a tidyier version of `rforcecom.getObjectDescription()` * Allow users to control whether query results are kept as all character or the @@ -11,6 +12,14 @@ * Add new utility functions `sf_set_password()` and `sf_reset_password()` (#11) * Add two new functions to check for duplicates (`sf_find_duplicates()`, `sf_find_duplicates_by_id()`) (#4) * Add new function to download attachments to disk (`sf_download_attachment()`) (#20) + * The `object_name` argument, required for bulk queries, will be inferred if left blank, + making it no longer a required argument + * Almost all functions in the package now have a `control` argument and dots (`...`) which + allows for more than a dozen different control parameters listed in `sf_control()` to be + fed into existing function calls to tweak the default behavior. For example, if you would + like to override duplicate rules then you can adjust the `DuplicateRuleHeader`. If you + would like to have certain assignment rule run on newly created records, then pass in the + `AssignmentRuleHeader` (#4, #5) ### Bug Fixes @@ -20,7 +29,16 @@ since that only supported aborting Bulk 2.0 jobs (#13) * Fix bug that had only production environment logins possible because of hard coding (@weckstm, #18) - * Make `sf_describe_object_fields()` more robust against nested list elements (#16) + * Make `sf_describe_object_fields()` more robust against nested list elements and + also return picklists as tibbles (#16) + * Fix bug where four of the bulk operation options (`content_type`, `concurrency_mode`, + `line_ending`, and `column_delimiter`) where not being passed down from + the top level generic functions like `sf_create()`, `sf_update()`, etc. However, + `line_ending` has now been moved into the `sf_control` function so it is no longer + explicitly listed for bulk operations as an argument. (@mitch-niche, #23) + * Ensure that for SOAP, REST, and Bulk 2.0 APIs the verbose argument prints out + the XML or JSON along with the URL of the call so it can be replicated via cURL or + some other programming language (#8) --- diff --git a/R/bulk-operation.R b/R/bulk-operation.R index 7662fc43..c9a5ac41 100644 --- a/R/bulk-operation.R +++ b/R/bulk-operation.R @@ -7,21 +7,19 @@ #' @template external_id_fieldname #' @template api_type #' @param content_type character; being one of 'CSV', 'ZIP_CSV', 'ZIP_XML', or 'ZIP_JSON' to -#' indicate the type of data being passed to the Bulk API. +#' indicate the type of data being passed to the Bulk APIs. For the Bulk 2.0 API the only +#' valid value (and the default) is 'CSV'. #' @param concurrency_mode character; either "Parallel" or "Serial" that specifies #' whether batches should be completed sequentially or in parallel. Use "Serial" #' only if lock contentions persist with in "Parallel" mode. Note: this argument is #' only used in the Bulk 1.0 API and will be ignored in calls using the Bulk 2.0 API. -#' @param line_ending character; indicating the line ending used for CSV job data, -#' marking the end of a data row. The default is NULL meaing that the line ending -#' is determined by the operating system using "CRLF" for Windows machines and -#' "LF" for Unix machines. Note: this argument is only used in the Bulk 2.0 API -#' and will be ignored in calls using the Bulk 1.0 API. #' @param column_delimiter character; indicating the column delimiter used for CSV job data. #' The default value is COMMA. Valid values are: "BACKQUOTE", "CARET", "COMMA", "PIPE", #' "SEMICOLON", and "TAB", but this package only accepts and uses "COMMA". Also, #' note that this argument is only used in the Bulk 2.0 API and will be ignored #' in calls using the Bulk 1.0 API. +#' @template control +#' @param ... arguments passed to \code{\link{sf_control}} #' @template verbose #' @return A \code{tbl_df} parameters defining the created job, including id #' @references \url{https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/} @@ -48,24 +46,35 @@ #' } #' @export sf_create_job_bulk <- function(operation = c("insert", "delete", "upsert", "update", - "hardDelete", "query"), + "hardDelete", "query", "queryall"), object_name, external_id_fieldname = NULL, api_type = c("Bulk 1.0", "Bulk 2.0"), content_type = c('CSV', 'ZIP_CSV', 'ZIP_XML', 'ZIP_JSON'), concurrency_mode = c("Parallel", "Serial"), - line_ending = NULL, column_delimiter = c('COMMA', 'TAB', 'PIPE', 'SEMICOLON', 'CARET', 'BACKQUOTE'), - verbose=FALSE){ + control = list(...), ..., + verbose = FALSE){ api_type <- match.arg(api_type) operation <- match.arg(operation) content_type <- match.arg(content_type) + + # determine how to pass along the control args + all_args <- list(...) + control_args <- return_matching_controls(control) + control_args$api_type <- api_type + control_args$operation <- operation + if("line_ending" %in% names(all_args)){ + # warn then set it in the control list + warning(paste0("The `line_ending` argument has been deprecated.\n", + "Please pass LineEndingHeader argument or use the `sf_control` function."), + call. = FALSE) + control_args$LineEndingHeader = list(`Sforce-Line-Ending` = all_args$line_ending) + } + if(api_type == "Bulk 1.0"){ - if(!missing(line_ending)){ - warning("Ignoring the line_ending argument which isn't used when calling the Bulk 1.0 API", call. = FALSE) - } if(!missing(column_delimiter)){ warning("Ignoring the column_delimiter argument which isn't used when calling the Bulk 1.0 API", call. = FALSE) } @@ -74,6 +83,7 @@ sf_create_job_bulk <- function(operation = c("insert", "delete", "upsert", "upda external_id_fieldname = external_id_fieldname, content_type = content_type, concurrency_mode = concurrency_mode, + control = control_args, ..., verbose = verbose) } else if(api_type == "Bulk 2.0"){ if(!(operation %in% c("insert", "delete", "upsert", "update"))){ @@ -89,8 +99,8 @@ sf_create_job_bulk <- function(operation = c("insert", "delete", "upsert", "upda object_name = object_name, external_id_fieldname = external_id_fieldname, content_type = content_type, - line_ending = line_ending, column_delimiter = column_delimiter, + control = control_args, ..., verbose = verbose) } else { stop("Unknown API type") @@ -106,17 +116,38 @@ sf_create_job_bulk <- function(operation = c("insert", "delete", "upsert", "upda #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal sf_create_job_bulk_v1 <- function(operation = c("insert", "delete", "upsert", "update", - "hardDelete", "query"), + "hardDelete", "query", "queryall"), object_name, external_id_fieldname = NULL, content_type = c('CSV', 'ZIP_CSV', 'ZIP_XML', 'ZIP_JSON'), concurrency_mode = c("Parallel", "Serial"), + control, ..., verbose = FALSE){ operation <- match.arg(operation) content_type <- match.arg(content_type) concurrency_mode <- match.arg(concurrency_mode) + control <- do.call("sf_control", control) + request_headers <- c("Accept"="application/xml", + "Content-Type"="application/xml") + if("BatchRetryHeader" %in% names(control)){ + request_headers <- c(request_headers, c("Sforce-Disable-Batch-Retry" = control$BatchRetryHeader[[1]])) + } + if("LineEndingHeader" %in% names(control)){ + stopitnot(control$LineEndingHeader[[1]] %in% c("CRLF", "LF")) + request_headers <- c(request_headers, c("Sforce-Line-Ending" = control$LineEndingHeader[[1]])) + } + if("PKChunkingHeader" %in% names(control)){ + if(is.logical(control$PKChunkingHeader[[1]])){ + request_headers <- c(request_headers, c("Sforce-Enable-PKChunking" = control$PKChunkingHeader[[1]])) + } else { + l <- control$PKChunkingHeader + value <- paste0(paste(names(l), unlist(l), sep="="), collapse = "; ") + request_headers <- c(request_headers, c("Sforce-Enable-PKChunking" = value)) + } + } + # build xml for Bulk 1.0 request body <- xml_new_document() body %>% @@ -143,8 +174,7 @@ sf_create_job_bulk_v1 <- function(operation = c("insert", "delete", "upsert", "u message(body) } httr_response <- rPOST(url = bulk_create_job_url, - headers = c("Accept"="application/xml", - "Content-Type"="application/xml"), + headers = request_headers, body = body) catch_errors(httr_response) response_parsed <- content(httr_response, encoding="UTF-8") @@ -167,20 +197,35 @@ sf_create_job_bulk_v2 <- function(operation = c("insert", "delete", "upsert", "u object_name, external_id_fieldname = NULL, content_type = 'CSV', - line_ending = NULL, column_delimiter = c('COMMA', 'TAB', 'PIPE', 'SEMICOLON', 'CARET', 'BACKQUOTE'), + control, ..., verbose=FALSE){ operation <- match.arg(operation) column_delimiter <- match.arg(column_delimiter) + if(content_type != "CSV"){ + stop("content_type = 'CSV' is currently the only supported format of returned bulk content") + } if(column_delimiter != "COMMA"){ stop("column_delimiter = 'COMMA' is currently the only supported file delimiter") } + control <- do.call("sf_control", control) + if("LineEndingHeader" %in% names(control)){ + line_ending <- control$LineEndingHeader$`Sforce-Line-Ending` + } else { + if(get_os() == 'windows'){ + line_ending <- "CRLF" + } else { + line_ending <- "LF" + } + } + if(operation == 'upsert'){ stopifnot(!is.null(external_id_fieldname)) - } + } + # form body from arguments request_body <- list(operation = operation, object = object_name, @@ -189,18 +234,11 @@ sf_create_job_bulk_v2 <- function(operation = c("insert", "delete", "upsert", "u lineEnding = line_ending, columnDelimiter = column_delimiter) request_body[sapply(request_body, is.null)] <- NULL - - if(is.null(line_ending)){ - if(get_os()=='windows'){ - request_body$lineEnding <- "CRLF" - } else { - request_body$lineEnding <- "LF" - } - } + stopifnot(request_body$lineEnding %in% c("LF", "CRLF")) - body <- toJSON(request_body, auto_unbox=TRUE, pretty=TRUE) + body <- toJSON(request_body, auto_unbox = TRUE, pretty = TRUE) - bulk_create_job_url <- make_bulk_create_job_url(api_type="Bulk 2.0") + bulk_create_job_url <- make_bulk_create_job_url(api_type = "Bulk 2.0") if (verbose){ message(bulk_create_job_url) message(body) @@ -231,7 +269,9 @@ sf_create_job_bulk_v2 <- function(operation = c("insert", "delete", "upsert", "u #' sf_abort_job_bulk(refreshed_job_info$id) #' } #' @export -sf_get_job_bulk <- function(job_id, api_type=c("Bulk 1.0", "Bulk 2.0"), verbose=FALSE){ +sf_get_job_bulk <- function(job_id, + api_type = c("Bulk 1.0", "Bulk 2.0"), + verbose = FALSE){ api_type <- match.arg(api_type) bulk_get_job_url <- make_bulk_get_job_url(job_id, api_type=api_type) if(verbose){ @@ -275,7 +315,9 @@ sf_get_job_bulk <- function(job_id, api_type=c("Bulk 1.0", "Bulk 2.0"), verbose= #' all_jobs_info <- sf_get_all_jobs_bulk() #' } #' @export -sf_get_all_jobs_bulk <- function(next_records_url=NULL, api_type=c("Bulk 2.0"), verbose=FALSE){ +sf_get_all_jobs_bulk <- function(next_records_url = NULL, + api_type = c("Bulk 2.0"), + verbose = FALSE){ api_type <- match.arg(api_type) if(!is.null(next_records_url)){ @@ -293,8 +335,7 @@ sf_get_all_jobs_bulk <- function(next_records_url=NULL, api_type=c("Bulk 2.0"), if(length(response_parsed$records) > 0){ resultset <- response_parsed$records %>% - map_df(as_tibble) %>% - type_convert(col_types = cols()) + map_df(as_tibble) } if(!response_parsed$done){ @@ -303,10 +344,15 @@ sf_get_all_jobs_bulk <- function(next_records_url=NULL, api_type=c("Bulk 2.0"), # check whether it has next record if(!is.null(next_records_url)){ - next_records <- sf_get_all_jobs_bulk(next_records_url=next_records_url) + next_records <- sf_get_all_jobs_bulk(next_records_url = next_records_url, + api_type = api_type, + verbose = verbose) resultset <- bind_rows(resultset, next_records) } + resultset <- resultset %>% + type_convert(col_types = cols()) + return(resultset) } @@ -374,9 +420,11 @@ sf_end_job_bulk <- function(job_id, #' sf_close_job_bulk(job_info$id) #' } #' @export -sf_close_job_bulk <- function(job_id, api_type=c("Bulk 1.0", "Bulk 2.0"), verbose = FALSE){ +sf_close_job_bulk <- function(job_id, + api_type = c("Bulk 1.0", "Bulk 2.0"), + verbose = FALSE){ api_type <- match.arg(api_type) - sf_end_job_bulk(job_id, end_type = "Closed", api_type = api_type, verbose=verbose) + sf_end_job_bulk(job_id, end_type = "Closed", api_type = api_type, verbose = verbose) } #' Signal Upload Complete to Bulk API Job @@ -396,10 +444,11 @@ sf_close_job_bulk <- function(job_id, api_type=c("Bulk 1.0", "Bulk 2.0"), verbos #' upload_info <- sf_upload_complete_bulk(job_id=job_info$id) #' } #' @export -sf_upload_complete_bulk <- function(job_id, api_type=c("Bulk 2.0"), +sf_upload_complete_bulk <- function(job_id, + api_type = c("Bulk 2.0"), verbose = FALSE){ api_type <- match.arg(api_type) - sf_end_job_bulk(job_id, end_type = "UploadComplete", api_type = api_type, verbose=verbose) + sf_end_job_bulk(job_id, end_type = "UploadComplete", api_type = api_type, verbose = verbose) } #' Abort Bulk API Job @@ -417,10 +466,11 @@ sf_upload_complete_bulk <- function(job_id, api_type=c("Bulk 2.0"), #' sf_abort_job_bulk(job_info$id) #' } #' @export -sf_abort_job_bulk <- function(job_id, api_type=c("Bulk 1.0", "Bulk 2.0"), +sf_abort_job_bulk <- function(job_id, + api_type = c("Bulk 1.0", "Bulk 2.0"), verbose = FALSE){ api_type <- match.arg(api_type) - sf_end_job_bulk(job_id, end_type = "Aborted", api_type = api_type, verbose=verbose) + sf_end_job_bulk(job_id, end_type = "Aborted", api_type = api_type, verbose = verbose) } #' Delete Bulk API Job @@ -435,10 +485,11 @@ sf_abort_job_bulk <- function(job_id, api_type=c("Bulk 1.0", "Bulk 2.0"), #' sf_delete_job_bulk(job_info$id) #' } #' @export -sf_delete_job_bulk <- function(job_id, api_type=c("Bulk 2.0"), - verbose=FALSE){ +sf_delete_job_bulk <- function(job_id, + api_type = c("Bulk 2.0"), + verbose = FALSE){ api_type <- match.arg(api_type) - bulk_delete_job_url <- make_bulk_delete_job_url(job_id, api_type=api_type) + bulk_delete_job_url <- make_bulk_delete_job_url(job_id, api_type = api_type) if(verbose){ message(bulk_delete_job_url) } @@ -484,13 +535,13 @@ sf_delete_job_bulk <- function(job_id, api_type=c("Bulk 2.0"), #' @export sf_create_batches_bulk <- function(job_id, input_data, - api_type=c("Bulk 1.0", "Bulk 2.0"), + api_type = c("Bulk 1.0", "Bulk 2.0"), verbose = FALSE){ api_type <- match.arg(api_type) if(api_type == "Bulk 1.0"){ - created_batches <- sf_create_batches_bulk_v1(job_id, input_data, verbose=verbose) + created_batches <- sf_create_batches_bulk_v1(job_id, input_data, verbose = verbose) } else if(api_type == "Bulk 2.0"){ - created_batches <- sf_create_batches_bulk_v2(job_id, input_data, verbose=verbose) + created_batches <- sf_create_batches_bulk_v2(job_id, input_data, verbose = verbose) } else { stop("Unknown API type") } @@ -503,10 +554,11 @@ sf_create_batches_bulk_v1 <- function(job_id, input_data, verbose = FALSE){ - job_status <- sf_get_job_bulk(job_id, api_type="Bulk 1.0", + job_status <- sf_get_job_bulk(job_id, + api_type = "Bulk 1.0", verbose = verbose) stopifnot(job_status$state == "Open") - input_data <- sf_input_data_validation(operation=job_status$operation, + input_data <- sf_input_data_validation(operation = job_status$operation, input_data) # Batch sizes should be adjusted based on processing times. Start with 5000 @@ -560,11 +612,12 @@ sf_create_batches_bulk_v1 <- function(job_id, #' @importFrom stats quantile sf_create_batches_bulk_v2 <- function(job_id, input_data, - verbose=FALSE){ + verbose = FALSE){ - job_status <- sf_get_job_bulk(job_id, api_type="Bulk 2.0", + job_status <- sf_get_job_bulk(job_id, + api_type = "Bulk 2.0", verbose = verbose) - input_data <- sf_input_data_validation(operation=job_status$operation, + input_data <- sf_input_data_validation(operation = job_status$operation, input_data) # A request can provide CSV data that does not in total exceed 150 MB of base64 @@ -634,9 +687,11 @@ sf_create_batches_bulk_v2 <- function(job_id, #' sf_get_job_bulk(job_info$id) #' } #' @export -sf_job_batches_bulk <- function(job_id, api_type=c("Bulk 1.0"), verbose=FALSE){ +sf_job_batches_bulk <- function(job_id, + api_type = c("Bulk 1.0"), + verbose = FALSE){ api_type <- match.arg(api_type) - bulk_batch_status_url <- make_bulk_batches_url(job_id, api_type=api_type) + bulk_batch_status_url <- make_bulk_batches_url(job_id, api_type = api_type) if(verbose){ message(bulk_batch_status_url) } @@ -678,10 +733,11 @@ sf_job_batches_bulk <- function(job_id, api_type=c("Bulk 1.0"), verbose=FALSE){ #' sf_get_job_bulk(job_info$id) #' } #' @export -sf_batch_status_bulk <- function(job_id, batch_id, api_type=c("Bulk 1.0"), - verbose=FALSE){ +sf_batch_status_bulk <- function(job_id, batch_id, + api_type = c("Bulk 1.0"), + verbose = FALSE){ api_type <- match.arg(api_type) - bulk_batch_status_url <- make_bulk_batch_status_url(job_id, batch_id, api_type=api_type) + bulk_batch_status_url <- make_bulk_batch_status_url(job_id, batch_id, api_type = api_type) if(verbose){ message(bulk_batch_status_url) } @@ -724,10 +780,11 @@ sf_batch_status_bulk <- function(job_id, batch_id, api_type=c("Bulk 1.0"), #' sf_close_job_bulk(job_info$id) #' } #' @export -sf_batch_details_bulk <- function(job_id, batch_id, api_type=c("Bulk 1.0"), +sf_batch_details_bulk <- function(job_id, batch_id, + api_type = c("Bulk 1.0"), verbose = FALSE){ api_type <- match.arg(api_type) - job_status <- sf_get_job_bulk(job_id, api_type=api_type, verbose=verbose) + job_status <- sf_get_job_bulk(job_id, api_type = api_type, verbose = verbose) bulk_batch_details_url <- make_bulk_batch_details_url(job_id, batch_id, api_type) if(verbose){ message(bulk_batch_details_url) @@ -782,17 +839,16 @@ sf_get_job_records_bulk <- function(job_id, record_types = c("successfulResults", "failedResults", "unprocessedRecords"), - combine_record_types=TRUE, + combine_record_types = TRUE, verbose=FALSE){ api_type <- match.arg(api_type) if(api_type == "Bulk 1.0"){ - batch_records <- sf_get_job_records_bulk_v1(job_id, verbose=verbose) + batch_records <- sf_get_job_records_bulk_v1(job_id, verbose = verbose) } else if(api_type == "Bulk 2.0"){ batch_records <- sf_get_job_records_bulk_v2(job_id, - record_types=record_types, - combine_record_types=combine_record_types, - verbose=verbose) - + record_types = record_types, + combine_record_types = combine_record_types, + verbose = verbose) } else { stop("Unknown API type") } @@ -800,14 +856,14 @@ sf_get_job_records_bulk <- function(job_id, } #' @importFrom dplyr bind_rows -sf_get_job_records_bulk_v1 <- function(job_id, verbose=FALSE){ - batches_info <- sf_job_batches_bulk(job_id, api_type="Bulk 1.0", verbose=verbose) +sf_get_job_records_bulk_v1 <- function(job_id, verbose = FALSE){ + batches_info <- sf_job_batches_bulk(job_id, api_type = "Bulk 1.0", verbose = verbose) # loop through all the batches resultset <- NULL for(i in 1:nrow(batches_info)){ this_batch_resultset <- sf_batch_details_bulk(job_id = job_id, batch_id = batches_info$id[i], - api_type="Bulk 1.0", + api_type = "Bulk 1.0", verbose = verbose) resultset <- bind_rows(resultset, this_batch_resultset) } @@ -821,13 +877,13 @@ sf_get_job_records_bulk_v2 <- function(job_id, record_types = c("successfulResults", "failedResults", "unprocessedRecords"), - combine_record_types=TRUE, - verbose=FALSE){ + combine_record_types = TRUE, + verbose = FALSE){ record_types <- match.arg(record_types, several.ok = TRUE) records <- list() for(r in record_types){ - bulk_job_records_url <- make_bulk_job_records_url(job_id, record_type = r, api_type="Bulk 2.0") + bulk_job_records_url <- make_bulk_job_records_url(job_id, record_type = r, api_type = "Bulk 2.0") if(verbose){ message(bulk_job_records_url) } @@ -861,14 +917,15 @@ sf_get_job_records_bulk_v2 <- function(job_id, #' @param operation character; string defining the type of operation being performed #' @template external_id_fieldname #' @template api_type -#' @param ... other arguments passed on to \code{\link{sf_create_job_bulk}} such as -#' \code{content_type}, \code{concurrency_mode}, \code{line_ending} or \code{column_delimiter}. #' @param wait_for_results logical; indicating whether to wait for the operation to complete #' so that the batch results of individual records can be obtained #' @param interval_seconds integer; defines the seconds between attempts to check #' for job completion #' @param max_attempts integer; defines then max number attempts to check for job #' completion before stopping +#' @template control +#' @param ... other arguments passed on to \code{\link{sf_control}} or \code{\link{sf_create_job_bulk}} +#' such as \code{content_type}, \code{concurrency_mode}, or \code{column_delimiter} #' @template verbose #' @return A \code{tbl_df} of the results of the bulk job #' @note With Bulk 2.0 the order of records in the response is not guaranteed to @@ -891,20 +948,35 @@ sf_bulk_operation <- function(input_data, "update", "hardDelete"), external_id_fieldname = NULL, api_type = c("Bulk 1.0", "Bulk 2.0"), - ..., wait_for_results = TRUE, interval_seconds = 3, max_attempts = 200, + control = list(...), ..., verbose = FALSE){ stopifnot(!missing(operation)) api_type <- match.arg(api_type) - job_info <- sf_create_job_bulk(operation, object_name = object_name, + # this code is redundant because it exists in the sf_create, sf_update, etc. wrappers, + # but it is possible that some people are creating jobs with this function instead of the + # others, so make sure that we do it here as well. It should be a relatively small performance + # hit given its a bulk operation + # determine how to pass along the control args + all_args <- list(...) + control_args <- return_matching_controls(control) + control_args$api_type <- api_type + control_args$operation <- operation + + job_info <- sf_create_job_bulk(operation, + object_name = object_name, external_id_fieldname = external_id_fieldname, - api_type = api_type, verbose = verbose, ...) - batches_info <- sf_create_batches_bulk(job_id = job_info$id, input_data, - api_type = api_type, verbose = verbose) + api_type = api_type, + control = control_args, ..., + verbose = verbose) + batches_info <- sf_create_batches_bulk(job_id = job_info$id, + input_data, + api_type = api_type, + verbose = verbose) if(wait_for_results){ status_complete <- FALSE @@ -917,7 +989,7 @@ sf_bulk_operation <- function(input_data, } } Sys.sleep(interval_seconds) - job_status <- sf_get_job_bulk(job_info$id, api_type=api_type, verbose=verbose) + job_status <- sf_get_job_bulk(job_info$id, api_type = api_type, verbose = verbose) if(api_type == "Bulk 1.0"){ if(job_status$state == 'Failed' | job_status$state == 'Aborted'){ stop(job_status$stateMessage) # what does this do if job is aborted? @@ -948,7 +1020,7 @@ sf_bulk_operation <- function(input_data, message("Function's Time Limit Exceeded. Aborting Job Now") res <- sf_abort_job_bulk(job_info$id, api_type = api_type, verbose = verbose) } else { - res <- sf_get_job_records_bulk(job_info$id, api_type=api_type, verbose=verbose) + res <- sf_get_job_records_bulk(job_info$id, api_type = api_type, verbose = verbose) # For Bulk 2.0 jobs -> INVALIDJOBSTATE: Closing already Completed Job not allowed if(api_type == "Bulk 1.0"){ close_job_info <- sf_close_job_bulk(job_info$id, api_type = api_type, verbose = verbose) diff --git a/R/bulk-query.R b/R/bulk-query.R index cd622fca..7cde0e36 100644 --- a/R/bulk-query.R +++ b/R/bulk-query.R @@ -26,17 +26,18 @@ #' @references \url{https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/} #' @examples #' \dontrun{ -#' my_query <- "SELECT Id, Name FROM Account LIMIT 10" -#' job_info <- sf_create_job_bulk(operation='query', object='Account') -#' query_info <- sf_submit_query_bulk(job_id=job_info$id, soql=my_query) +#' my_query <- "SELECT Id, Name FROM Account LIMIT 1000" +#' job_info <- sf_create_job_bulk(operation = 'query', object = 'Account') +#' query_info <- sf_submit_query_bulk(job_id = job_info$id, soql = my_query) #' } #' @export -sf_submit_query_bulk <- function(job_id, soql, +sf_submit_query_bulk <- function(job_id, + soql, api_type = c("Bulk 1.0"), verbose = FALSE){ api_type <- match.arg(api_type) - bulk_query_url <- make_bulk_query_url(job_id, api_type=api_type) + bulk_query_url <- make_bulk_query_url(job_id, api_type = api_type) if(verbose){ message(bulk_query_url) } @@ -74,9 +75,9 @@ sf_submit_query_bulk <- function(job_id, soql, #' @references \url{https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/} #' @examples #' \dontrun{ -#' my_query <- "SELECT Id, Name FROM Account LIMIT 10" -#' job_info <- sf_create_job_bulk(operation='query', object='Account') -#' query_info <- sf_submit_query_bulk(job_id=job_info$id, soql=my_query) +#' my_query <- "SELECT Id, Name FROM Account LIMIT 1000" +#' job_info <- sf_create_job_bulk(operation = 'query', object = 'Account') +#' query_info <- sf_submit_query_bulk(job_id = job_info$id, soql = my_query) #' result <- sf_batch_details_bulk(job_id = query_info$jobId, #' batch_id = query_info$id) #' recordset <- sf_query_result_bulk(job_id = query_info$jobId, @@ -123,6 +124,7 @@ sf_query_result_bulk <- function(job_id, batch_id, result_id, #' #' @template soql #' @template object_name +#' @template queryall #' @param guess_types logical; indicating whether or not to use \code{col_guess()} #' to try and cast the data returned in the query recordset. TRUE uses \code{col_guess()} #' and FALSE returns all values as character strings. @@ -131,33 +133,55 @@ sf_query_result_bulk <- function(job_id, batch_id, result_id, #' for job completion #' @param max_attempts integer; defines then max number attempts to check for job #' completion before stopping +#' @template control +#' @param ... arguments passed to \code{\link{sf_control}} #' @template verbose #' @return A \code{tbl_df} of the recordset returned by the query -#' @references \url{https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/} +#' @references \url{https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/asynch_api_bulk_query_intro.htm} #' @examples #' \dontrun{ -#' # select all Ids from Account object -#' ids <- sf_query_bulk(soql='SELECT Id FROM Account', object_name='Account') +#' # select all Ids from Account object (up to 1000) +#' ids <- sf_query_bulk(soql = 'SELECT Id FROM Account LIMIT 1000', +#' object_name = 'Account') #' } #' @export sf_query_bulk <- function(soql, - object_name, + object_name = NULL, + queryall = FALSE, guess_types = TRUE, api_type = c("Bulk 1.0"), interval_seconds = 5, max_attempts = 100, + control = list(...), ..., verbose = FALSE){ - # if(is.null(object_name)){ - # object_name <- gsub("(.*)from\\s+([A-Za-z_]+)\\b(.*)", "\\2", soql, ignore.case = TRUE, perl=TRUE) - # message(sprintf("Guessed target object_name from query string: %s", object_name)) - # } + if(is.null(object_name)){ + object_name <- gsub("SELECT(.*) FROM ([A-Za-z_]+)\\b(.*)", "\\2", soql, ignore.case = TRUE) + message(sprintf("Guessed target object_name from query string: %s", object_name)) + } + + listed_objects <- sf_list_objects() + valid_object_names <- sapply(listed_objects$sobjects, FUN=function(x){x$name}) + if(!object_name %in% valid_object_names){ + stop(sprintf("The supplied object name (%s) does not exist or the user does not have permission to view", + object_name)) + } + api_type <- match.arg(api_type) - job_info <- sf_create_job_bulk(operation = "query", + + # determine how to pass along the control args + control_args <- return_matching_controls(control) + control_args$api_type <- api_type + this_operation <- if(queryall) "queryall" else "query" + control_args$operation <- this_operation + + job_info <- sf_create_job_bulk(operation = this_operation, object_name = object_name, api_type = api_type, - verbose = verbose) - batch_query_info <- sf_submit_query_bulk(job_id = job_info$id, soql = soql, + control = control_args, + verbose = verbose, ...) + batch_query_info <- sf_submit_query_bulk(job_id = job_info$id, + soql = soql, api_type = api_type, verbose = verbose) status_complete <- FALSE diff --git a/R/create-metadata.R b/R/create-metadata.R index 76a83ea1..73bb8d84 100644 --- a/R/create-metadata.R +++ b/R/create-metadata.R @@ -11,7 +11,8 @@ #' @references \url{https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/} #' @template metadata_type #' @template metadata -#' @template all_or_none +#' @template control +#' @param ... arguments passed to \code{\link{sf_control}} #' @template verbose #' @return A \code{tbl_df} containing the creation result for each submitted metadata component #' @examples @@ -44,8 +45,9 @@ #' custom_metadata$deploymentStatus <- 'Deployed' #' # make a description to identify this easily in the UI setup tab #' custom_metadata$description <- 'created by the Metadata API' -#' new_custom_object <- sf_create_metadata(metadata_type='CustomObject', -#' metadata=custom_metadata, verbose=TRUE) +#' new_custom_object <- sf_create_metadata(metadata_type = 'CustomObject', +#' metadata = custom_metadata, +#' verbose = TRUE) #' #' # adding custom fields to our object #' # input formatted as a list @@ -67,23 +69,41 @@ #' metadata = custom_fields) #' } #' @export -sf_create_metadata <- function(metadata_type, metadata, all_or_none=FALSE, verbose=FALSE){ +sf_create_metadata <- function(metadata_type, + metadata, + control = list(...), ..., + verbose = FALSE){ which_operation <- "createMetadata" - # run some basic validation on the metadata to see if it conforms to WSDL standards metadata <- metadata_type_validator(obj_type=metadata_type, obj_data=metadata) + + # determine how to pass along the control args + all_args <- list(...) + control_args <- return_matching_controls(control) + control_args$api_type <- "Metadata" + control_args$operation <- "insert" + if("all_or_none" %in% names(all_args)){ + # warn then set it in the control list + warning(paste0("The `all_or_none` argument has been deprecated.\n", + "Please pass AllOrNoneHeader argument or use the `sf_control` function."), + call. = FALSE) + control_args$AllOrNoneHeader = list(allOrNone = tolower(all_args$all_or_none)) + } + control <- do.call("sf_control", control_args) # define the operation operation_node <- newXMLNode(which_operation, - namespaceDefinitions=c('http://soap.sforce.com/2006/04/metadata'), + namespaceDefinitions = c('http://soap.sforce.com/2006/04/metadata'), suppressNamespaceWarning = TRUE) # and add the metadata to it - xml_dat <- build_metadata_xml_from_list(input_data=metadata, metatype=metadata_type, root=operation_node) + xml_dat <- build_metadata_xml_from_list(input_data = metadata, + metatype = metadata_type, + root = operation_node) base_metadata_url <- make_base_metadata_url() - root <- make_soap_xml_skeleton(soap_headers=list(AllorNoneHeader = tolower(all_or_none)), metadata_ns=TRUE) - body_node <- newXMLNode("soapenv:Body", parent=root) + root <- make_soap_xml_skeleton(soap_headers = control, metadata_ns = TRUE) + body_node <- newXMLNode("soapenv:Body", parent = root) body_node <- addChildren(body_node, xml_dat) if(verbose) { @@ -104,4 +124,4 @@ sf_create_metadata <- function(metadata_type, metadata, all_or_none=FALSE, verbo type_convert(col_types = cols()) return(resultset) -} \ No newline at end of file +} diff --git a/R/create.R b/R/create.R index ab715cb9..864e93fe 100644 --- a/R/create.R +++ b/R/create.R @@ -5,49 +5,84 @@ #' @param input_data \code{named vector}, \code{matrix}, \code{data.frame}, or #' \code{tbl_df}; data can be coerced into a \code{data.frame} #' @template object_name -#' @template all_or_none #' @template api_type -#' @param ... other arguments passed on to \code{\link{sf_bulk_operation}}. +#' @template control +#' @param ... arguments passed to \code{\link{sf_control}} or further downstream +#' to \code{\link{sf_bulk_operation}} #' @template verbose #' @return \code{tbl_df} of records with success indicator +#' @note Because the SOAP and REST calls chunk data into batches of 200 records +#' the AllOrNoneHeader will only apply to the success or failure of every batch +#' of records and not all records submitted to the function. #' @examples #' \dontrun{ -#' n <- 3 +#' n <- 2 #' new_contacts <- tibble(FirstName = rep("Test", n), #' LastName = paste0("Contact", 1:n)) #' new_contacts_result <- sf_create(new_contacts, object_name="Contact") -#' new_contacts_result <- sf_create(new_contacts, object_name="Contact", api_type="REST") +#' +#' # add the control to allow the creation of records that might violate a duplicate rule +#' new_contacts_result <- sf_create(new_contacts, object_name="Contact", +#' DuplicateRuleHeader = list(allowSave = TRUE, +#' includeRecordDetails = FALSE, +#' runAsCurrentUser = TRUE)) +#' +#' # an example of how to specify using the Bulk 1.0 API to insert the records +#' new_contacts_result <- sf_create(new_contacts, object_name="Contact", api_type="Bulk 1.0") #' } #' @export sf_create <- function(input_data, object_name, - all_or_none = FALSE, api_type = c("SOAP", "REST", "Bulk 1.0", "Bulk 2.0"), - ..., + control = list(...), ..., verbose = FALSE){ api_type <- match.arg(api_type) - if(api_type == "REST"){ - resultset <- sf_create_rest(input_data=input_data, - object_name=object_name, - all_or_none=all_or_none, - verbose=verbose) - } else if(api_type == "SOAP"){ - resultset <- sf_create_soap(input_data=input_data, - object_name=object_name, - all_or_none=all_or_none, - verbose=verbose) + + # determine how to pass along the control args + all_args <- list(...) + control_args <- return_matching_controls(control) + control_args$api_type <- api_type + control_args$operation <- "insert" + if("all_or_none" %in% names(all_args)){ + # warn then set it in the control list + warning(paste0("The `all_or_none` argument has been deprecated.\n", + "Please pass AllOrNoneHeader argument or use the `sf_control` function."), + call. = FALSE) + control_args$AllOrNoneHeader = list(allOrNone = tolower(all_args$all_or_none)) + } + if("AssignmentRuleHeader" %in% names(control_args)){ + if(!object_name %in% c("Account", "Case", "Lead")){ + stop("The AssignmentRuleHeader can only be used when creating, updating, or upserting an Account, Case, or Lead") + } + } + + if(api_type == "SOAP"){ + resultset <- sf_create_soap(input_data = input_data, + object_name = object_name, + control = control_args, + verbose = verbose) + } else if(api_type == "REST"){ + resultset <- sf_create_rest(input_data = input_data, + object_name = object_name, + control = control_args, + verbose = verbose) } else if(api_type == "Bulk 1.0"){ - resultset <- sf_create_bulk_v1(input_data, object_name = object_name, verbose = verbose, ...) + resultset <- sf_create_bulk_v1(input_data, + object_name = object_name, + control = control_args, + verbose = verbose, ...) } else if(api_type == "Bulk 2.0"){ - resultset <- sf_create_bulk_v2(input_data, object_name = object_name, verbose = verbose, ...) + resultset <- sf_create_bulk_v2(input_data, + object_name = object_name, + control = control_args, + verbose = verbose, ...) } else { stop("Unknown API type") } return(resultset) } - #' Create Records using SOAP API #' #' @importFrom readr cols type_convert @@ -59,10 +94,13 @@ sf_create <- function(input_data, #' @importFrom utils head #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal -sf_create_soap <- function(input_data, object_name, all_or_none = FALSE, +sf_create_soap <- function(input_data, + object_name, + control, ..., verbose = FALSE){ - input_data <- sf_input_data_validation(operation='create', input_data) + input_data <- sf_input_data_validation(operation = 'create', input_data) + control <- do.call("sf_control", control) base_soap_url <- make_base_soap_url() if(verbose) { @@ -88,7 +126,7 @@ sf_create_soap <- function(input_data, object_name, all_or_none = FALSE, } } batched_data <- input_data[batch_id == batch, , drop=FALSE] - r <- make_soap_xml_skeleton(soap_headers=list(AllorNoneHeader = tolower(all_or_none))) + r <- make_soap_xml_skeleton(soap_headers = control) xml_dat <- build_soap_xml_from_list(input_data = batched_data, operation = "create", object_name = object_name, @@ -120,12 +158,27 @@ sf_create_soap <- function(input_data, object_name, all_or_none = FALSE, #' @importFrom utils head #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal -sf_create_rest <- function(input_data, object_name, all_or_none = FALSE, +sf_create_rest <- function(input_data, + object_name, + control, ..., verbose = FALSE){ - # This resource is available in API version 42.0 and later. stopifnot(as.numeric(getOption("salesforcer.api_version")) >= 42.0) input_data <- sf_input_data_validation(operation='create', input_data) + + control <- do.call("sf_control", control) + if("AllOrNoneHeader" %in% names(control)){ + all_or_none <- control$AllOrNoneHeader$allOrNone + } else { + all_or_none <- FALSE + } + request_headers <- c("Accept"="application/json", + "Content-Type"="application/json") + if("AssignmentRuleHeader" %in% names(control)){ + # take the first list element because it could be useDefaultRule (T/F) or assignmentRuleId + request_headers <- c(request_headers, c("Sforce-Auto-Assign" = control$AssignmentRuleHeader[[1]])) + } + # add attributes to insert multiple records at a time # https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_composite_sobjects_collections.htm?search_text=update%20multiple input_data$attributes <- lapply(1:nrow(input_data), FUN=function(x, obj){list(type=obj, referenceId=paste0("ref" ,x))}, obj=object_name) @@ -140,12 +193,11 @@ sf_create_rest <- function(input_data, object_name, all_or_none = FALSE, # this type of request can only handle 200 records at a time batch_size <- 200 row_num <- nrow(input_data) - batch_id <- (seq.int(row_num)-1) %/% batch_size + batch_id <- (seq.int(row_num) - 1) %/% batch_size if(verbose) { - message("Submitting data in ", max(batch_id)+1, " Batches") + message("Submitting data in ", max(batch_id) + 1, " Batches") } - message_flag <- unique(as.integer(quantile(0:max(batch_id), c(0.25,0.5,0.75,1)))) - + message_flag <- unique(as.integer(quantile(0:max(batch_id), c(0.25, 0.5, 0.75, 1)))) resultset <- NULL for(batch in seq(0, max(batch_id))){ if(verbose){ @@ -156,8 +208,7 @@ sf_create_rest <- function(input_data, object_name, all_or_none = FALSE, } batched_data <- input_data[batch_id == batch, , drop=FALSE] httr_response <- rPOST(url = composite_url, - headers = c("Accept"="application/json", - "Content-Type"="application/json"), + headers = request_headers, body = toJSON(list(allOrNone = tolower(all_or_none), records = batched_data), auto_unbox = TRUE)) @@ -176,16 +227,18 @@ sf_create_rest <- function(input_data, object_name, all_or_none = FALSE, #' #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal -sf_create_bulk_v1 <- function(input_data, object_name, all_or_none = FALSE, - ..., +sf_create_bulk_v1 <- function(input_data, + object_name, + control, ..., verbose = FALSE){ - # allor none? input_data <- sf_input_data_validation(operation = "create", input_data) + control <- do.call("sf_control", control) resultset <- sf_bulk_operation(input_data = input_data, object_name = object_name, operation = "insert", api_type = "Bulk 1.0", - verbose = verbose, ...) + control = control, ..., + verbose = verbose) return(resultset) } @@ -194,18 +247,19 @@ sf_create_bulk_v1 <- function(input_data, object_name, all_or_none = FALSE, #' #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal -sf_create_bulk_v2 <- function(input_data, object_name, all_or_none = FALSE, - ..., +sf_create_bulk_v2 <- function(input_data, + object_name, + control, ..., verbose = FALSE){ - # allor none? - #The order of records in the response is not guaranteed to match the ordering of records in the original job data. + # The order of records in the response is not guaranteed to match the ordering of + # records in the original job data. input_data <- sf_input_data_validation(operation = "create", input_data) + control <- do.call("sf_control", control) resultset <- sf_bulk_operation(input_data = input_data, object_name = object_name, operation = "insert", api_type = "Bulk 2.0", - verbose = verbose, ...) + control = control, ..., + verbose = verbose) return(resultset) } - - diff --git a/R/delete-metadata.R b/R/delete-metadata.R index e2e72a4e..a2b4dad9 100644 --- a/R/delete-metadata.R +++ b/R/delete-metadata.R @@ -6,7 +6,8 @@ #' @references \url{https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/} #' @template metadata_type #' @param object_names a character vector of names that we wish to read metadata for -#' @template all_or_none +#' @template control +#' @param ... arguments passed to \code{\link{sf_control}} #' @template verbose #' @return A \code{data.frame} containing the creation result for each submitted metadata component #' @seealso \link{sf_list_metadata} @@ -16,7 +17,10 @@ #' object_names = c('Custom_Account25__c')) #' } #' @export -sf_delete_metadata <- function(metadata_type, object_names, all_or_none=FALSE, verbose=FALSE){ +sf_delete_metadata <- function(metadata_type, + object_names, + control = list(...), ..., + verbose = FALSE){ stopifnot(all(is.character(object_names))) stopifnot(metadata_type %in% names(valid_metadata_list())) @@ -26,16 +30,31 @@ sf_delete_metadata <- function(metadata_type, object_names, all_or_none=FALSE, v names(object_list) <- rep('fullNames', length(object_list)) which_operation <- "deleteMetadata" + + # determine how to pass along the control args + all_args <- list(...) + control_args <- return_matching_controls(control) + control_args$api_type <- "Metadata" + control_args$operation <- "delete" + if("all_or_none" %in% names(all_args)){ + # warn then set it in the control list + warning(paste0("The `all_or_none` argument has been deprecated.\n", + "Please pass AllOrNoneHeader argument or use the `sf_control` function."), + call. = FALSE) + control_args$AllOrNoneHeader = list(allOrNone = tolower(all_args$all_or_none)) + } + control <- do.call("sf_control", control_args) + # define the operation operation_node <- newXMLNode(which_operation, - namespaceDefinitions=c('http://soap.sforce.com/2006/04/metadata'), + namespaceDefinitions = c('http://soap.sforce.com/2006/04/metadata'), suppressNamespaceWarning = TRUE) - type_node <- newXMLNode("type", metadata_type, parent=operation_node) + type_node <- newXMLNode("type", metadata_type, parent = operation_node) # and add the metadata to it - xml_dat <- build_metadata_xml_from_list(input_data=object_list, metatype=NULL, root=operation_node) + xml_dat <- build_metadata_xml_from_list(input_data = object_list, metatype = NULL, root = operation_node) base_metadata_url <- make_base_metadata_url() - root <- make_soap_xml_skeleton(soap_headers=list(AllorNoneHeader = tolower(all_or_none)), metadata_ns=TRUE) + root <- make_soap_xml_skeleton(soap_headers = control, metadata_ns = TRUE) body_node <- newXMLNode("soapenv:Body", parent=root) body_node <- addChildren(body_node, xml_dat) diff --git a/R/delete.R b/R/delete.R index 56dd2e08..2f308d60 100644 --- a/R/delete.R +++ b/R/delete.R @@ -1,60 +1,87 @@ #' Delete Records #' -#' Deletes one or more records to your organization’s data. +#' Deletes one or more records from your organization’s data. #' #' @param ids \code{vector}, \code{matrix}, \code{data.frame}, or #' \code{tbl_df}; if not a vector, there must be a column called Id (case-insensitive) #' that can be passed in the request #' @template object_name -#' @template all_or_none #' @template api_type -#' @param ... other arguments passed on to \code{\link{sf_bulk_operation}}. +#' @template control +#' @param ... arguments passed to \code{\link{sf_control}} or further downstream +#' to \code{\link{sf_bulk_operation}} #' @template verbose #' @return \code{tbl_df} of records with success indicator +#' @note Because the SOAP and REST calls chunk data into batches of 200 records +#' the AllOrNoneHeader will only apply to the success or failure of every batch +#' of records and not all records submitted to the function. #' @examples #' \dontrun{ #' n <- 3 #' new_contacts <- tibble(FirstName = rep("Test", n), #' LastName = paste0("Contact", 1:n)) -#' new_contacts_result1 <- sf_create(new_contacts, object_name="Contact") -#' deleted_contacts_result1 <- sf_delete(new_contacts_result1$id, -#' object_name="Contact") +#' new_records <- sf_create(new_contacts, object_name="Contact") +#' deleted_first <- sf_delete(new_records$id[1], object_name = "Contact") #' -#' new_contacts_result2 <- sf_create(new_contacts, "Contact") -#' deleted_contacts_result2 <- sf_delete(new_contacts_result2$id, -#' object_name="Contact", -#' api_type="Bulk") +#' # add the control to do an "All or None" deletion of the remaining records +#' deleted_rest <- sf_delete(new_records$id[2:3], object_name = "Contact", +#' AllOrNoneHeader = list(allOrNone = TRUE)) #' } #' @export sf_delete <- function(ids, object_name, - all_or_none = FALSE, api_type = c("REST", "SOAP", "Bulk 1.0", "Bulk 2.0"), - ..., + control = list(...), ..., verbose = FALSE){ api_type <- match.arg(api_type) - if(api_type == "REST"){ - resultset <- sf_delete_rest(ids=ids, object_name=object_name, - all_or_none=all_or_none, verbose=verbose) - } else if(api_type == "SOAP"){ - resultset <- sf_delete_soap(ids=ids, object_name=object_name, - all_or_none=all_or_none, verbose=verbose) + + # determine how to pass along the control args + all_args <- list(...) + control_args <- return_matching_controls(control) + control_args$api_type <- api_type + control_args$operation <- "delete" + if("all_or_none" %in% names(all_args)){ + # warn then set it in the control list + warning(paste0("The `all_or_none` argument has been deprecated.\n", + "Please pass AllOrNoneHeader argument or use the `sf_control` function."), + call. = FALSE) + control_args$AllOrNoneHeader = list(allOrNone = tolower(all_args$all_or_none)) + } + + if(api_type == "SOAP"){ + resultset <- sf_delete_soap(ids = ids, + object_name = object_name, + control = control_args, + verbose = verbose) + } else if(api_type == "REST"){ + resultset <- sf_delete_rest(ids = ids, + object_name = object_name, + control = control_args, + verbose = verbose) } else if(api_type == "Bulk 1.0"){ - resultset <- sf_delete_bulk_v1(ids=ids, object_name=object_name, verbose=verbose, ...) + resultset <- sf_delete_bulk_v1(ids = ids, + object_name = object_name, + control = control_args, + verbose = verbose, ...) } else if(api_type == "Bulk 2.0"){ - resultset <- sf_delete_bulk_v2(ids=ids, object_name=object_name, verbose=verbose, ...) + resultset <- sf_delete_bulk_v2(ids=ids, + object_name = object_name, + control = control_args, + verbose = verbose, ...) } else { stop("Unknown API type") } return(resultset) } - -sf_delete_soap <- function(ids, object_name, all_or_none = FALSE, +sf_delete_soap <- function(ids, + object_name, + control, ..., verbose = FALSE){ ids <- sf_input_data_validation(ids, operation='delete') + control <- do.call("sf_control", control) # limit this type of request to only 200 records at a time to prevent # the XML from exceeding a size limit @@ -80,7 +107,7 @@ sf_delete_soap <- function(ids, object_name, all_or_none = FALSE, } } batched_data <- ids[batch_id == batch, , drop=FALSE] - r <- make_soap_xml_skeleton(soap_headers=list(AllorNoneHeader = tolower(all_or_none))) + r <- make_soap_xml_skeleton(soap_headers = control) xml_dat <- build_soap_xml_from_list(input_data = batched_data, operation = "delete", object_name = object_name, @@ -102,10 +129,17 @@ sf_delete_soap <- function(ids, object_name, all_or_none = FALSE, return(resultset) } -sf_delete_rest <- function(ids, object_name, all_or_none = FALSE, +sf_delete_rest <- function(ids, + object_name, + control, ..., verbose = FALSE){ - ids <- sf_input_data_validation(ids, operation='delete') + control <- do.call("sf_control", control) + if("AllOrNoneHeader" %in% names(control)){ + all_or_none <- control$AllOrNoneHeader$allOrNone + } else { + all_or_none <- FALSE + } composite_url <- make_composite_url() if(verbose) { @@ -146,28 +180,32 @@ sf_delete_rest <- function(ids, object_name, all_or_none = FALSE, return(resultset) } -sf_delete_bulk_v1 <- function(ids, object_name, - ..., +sf_delete_bulk_v1 <- function(ids, + object_name, + control, ..., verbose = FALSE){ - # allor none? - ids <- sf_input_data_validation(ids, operation = 'delete') - resultset <- sf_bulk_operation(input_data = ids, object_name = object_name, + ids <- sf_input_data_validation(ids, operation = 'delete') + control <- do.call("sf_control", control) + resultset <- sf_bulk_operation(input_data = ids, + object_name = object_name, operation = "delete", api_type = "Bulk 1.0", - verbose=verbose, ...) + control = control, ..., + verbose = verbose) return(resultset) } -sf_delete_bulk_v2 <- function(ids, object_name, - ..., +sf_delete_bulk_v2 <- function(ids, + object_name, + control, ..., verbose = FALSE){ - # allor none? - ids <- sf_input_data_validation(ids, operation='delete') - resultset <- sf_bulk_operation(input_data = ids, object_name = object_name, + ids <- sf_input_data_validation(ids, operation = 'delete') + control <- do.call("sf_control", control) + resultset <- sf_bulk_operation(input_data = ids, + object_name = object_name, operation = "delete", api_type = "Bulk 2.0", - verbose=verbose, ...) + control = control, ..., + verbose = verbose) return(resultset) } - - diff --git a/R/describe.R b/R/describe.R index 26cdb4cd..222ce8c0 100644 --- a/R/describe.R +++ b/R/describe.R @@ -8,6 +8,8 @@ #' @importFrom xml2 xml_find_all xml_ns_strip as_list #' @template object_names #' @template api_type +#' @template control +#' @param ... arguments passed to \code{\link{sf_control}} #' @template verbose #' @return \code{list} #' @examples @@ -18,11 +20,12 @@ #' } #' @export sf_describe_objects <- function(object_names, - api_type = c("SOAP", "REST", "Bulk"), + api_type = c("SOAP", "REST"), + control = list(...), ..., verbose = FALSE){ - + which_api <- match.arg(api_type) - object_names <- tibble(sObjectType=unlist(object_names)) + object_names <- tibble(sObjectType = unlist(object_names)) listed_objects <- sf_list_objects() valid_object_names <- sapply(listed_objects$sobjects, FUN=function(x){x$name}) @@ -36,9 +39,7 @@ sf_describe_objects <- function(object_names, filter(sObjectType %in% valid_object_names) %>% as.data.frame() - # REST implementation if(which_api == "REST"){ - resultset <- list() for(i in 1:nrow(object_names)){ describe_object_url <- make_describe_objects_url(object_names[i,"sObjectType"]) @@ -46,16 +47,18 @@ sf_describe_objects <- function(object_names, httr_response <- rGET(url = describe_object_url) catch_errors(httr_response) response_parsed <- content(httr_response, as="parsed", encoding="UTF-8") - # need to fix!!!! + # TODO: Need to fix!!!! resultset[[i]] <- response_parsed$objectDescribe } - } else if(which_api == "SOAP"){ - # SOAP implementation - r <- make_soap_xml_skeleton() + control_args <- return_matching_controls(control) + control_args$api_type <- "SOAP" + control_args$operation <- "describeSObjects" + control <- do.call("sf_control", control_args) + r <- make_soap_xml_skeleton(soap_headers = control) xml_dat <- build_soap_xml_from_list(input_data = object_names$sObjectType, operation = "describeSObjects", - root=r) + root = r) base_soap_url <- make_base_soap_url() if(verbose) { message(base_soap_url) @@ -78,7 +81,7 @@ sf_describe_objects <- function(object_names, }) )) } else { - stop("Describe SObject is not available for the Bulk API, use REST or SOAP APIs.") + stop("Unknown API type") } return(resultset) } \ No newline at end of file diff --git a/R/query.R b/R/query.R index de0b00ff..68283b6a 100644 --- a/R/query.R +++ b/R/query.R @@ -5,16 +5,14 @@ #' #' @template soql #' @template object_name +#' @template queryall #' @template guess_types -#' @param queryall logical; indicating if the query recordset should include -#' deleted and archived records (available only when querying Task and Event records) -#' @param page_size numeric; a number between 200 and 2000 indicating the number of -#' records per page that are returned. Speed benchmarks should be done to better -#' understand the speed implications of choosing high or low values of this argument. #' @template api_type +#' @template control +#' @param ... arguments passed to \code{\link{sf_control}} or further downstream +#' to \code{\link{sf_query_bulk}}. #' @param next_records_url character (leave as NULL); a string used internally #' by the function to paginate through to more records until complete -#' @param ... Other arguments passed on to \code{\link{sf_query_bulk}}. #' @template verbose #' @return \code{tbl_df} of records #' @references \url{https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/} @@ -31,69 +29,89 @@ #' Additionally, Bulk API can't access or query compound address or compound geolocation fields. #' @examples #' \dontrun{ -#' sf_query("SELECT Id, Account.Name, Email FROM Contact LIMIT 10", verbose = TRUE) +#' sf_query("SELECT Id, Account.Name, Email FROM Contact LIMIT 10") #' } #' @export sf_query <- function(soql, object_name, - guess_types = TRUE, queryall = FALSE, - page_size = 1000, + guess_types = TRUE, api_type = c("REST", "SOAP", "Bulk 1.0"), + control = list(...), ..., next_records_url = NULL, - ..., verbose = FALSE){ api_type <- match.arg(api_type) + + # determine how to pass along the control args + all_args <- list(...) + control_args <- return_matching_controls(control) + control_args$api_type <- api_type + control_args$operation <- if(queryall) "queryall" else "query" + if("page_size" %in% names(all_args)){ + # warn then set it in the control list + warning(paste0("The `page_size` argument has been deprecated.\n", + "Please pass QueryOptions argument or use the `sf_control` function."), + call. = FALSE) + control_args$QueryOptions = list(batchSize = as.integer(all_args$page_size)) + } + if(api_type == "REST"){ - resultset <- sf_query_rest(soql=soql, - object_name=object_name, - guess_types=guess_types, - queryall=queryall, - page_size=page_size, - next_records_url=next_records_url, - verbose=verbose) + resultset <- sf_query_rest(soql = soql, + object_name = object_name, + queryall = queryall, + guess_types = guess_types, + control = control_args, + next_records_url = next_records_url, + verbose = verbose) } else if(api_type == "SOAP"){ - resultset <- sf_query_soap(soql=soql, - object_name=object_name, - guess_types=guess_types, - queryall=queryall, - page_size=page_size, - next_records_url=next_records_url, - verbose=verbose) + resultset <- sf_query_soap(soql = soql, + object_name = object_name, + queryall = queryall, + guess_types = guess_types, + control = control_args, + next_records_url = next_records_url, + verbose = verbose) } else if(api_type == "Bulk 1.0"){ if(missing(object_name)){ stop("object_name is missing. This argument must be provided when using the Bulk API.") } - resultset <- sf_query_bulk(soql=soql, object_name=object_name, guess_types=guess_types, verbose=verbose, ...) + resultset <- sf_query_bulk(soql = soql, + object_name = object_name, + queryall = queryall, + guess_types = guess_types, + control = control_args, + verbose = verbose, ...) } else { stop("Unknown API type") } return(resultset) } - #' @importFrom dplyr bind_rows as_tibble select matches tibble #' @importFrom httr content #' @importFrom jsonlite toJSON #' @importFrom readr type_convert cols col_guess sf_query_rest <- function(soql, object_name, - guess_types = TRUE, queryall = FALSE, - page_size = 1000, + guess_types = TRUE, + control, ..., next_records_url = NULL, - ..., verbose = FALSE){ query_url <- make_query_url(soql, queryall, next_records_url) if(verbose) message(query_url) + request_headers <- c("Accept"="application/json", + "Content-Type"="application/json") + if("QueryOptions" %in% names(control)){ + # take the first list element because it could be useDefaultRule (T/F) or assignmentRuleId + request_headers <- c(request_headers, c("Sforce-Query-Options" = control$QueryOptions$batchSize)) + } + # GET the url with the q (query) parameter set to the escaped SOQL string - httr_response <- rGET(url = query_url, - headers = c("Accept"="application/json", - "Content-Type"="application/json", - "Sforce-Query-Options"=sprintf("batchSize=%.0f", page_size))) + httr_response <- rGET(url = query_url, headers = request_headers) catch_errors(httr_response) response_parsed <- content(httr_response, "text", encoding="UTF-8") response_parsed <- fromJSON(response_parsed, flatten=TRUE) @@ -112,7 +130,7 @@ sf_query_rest <- function(soql, # check whether it has next record if(!is.null(next_records_url)){ - next_records <- sf_query_rest(next_records_url = next_records_url) + next_records <- sf_query_rest(next_records_url = next_records_url, control = control, ...) resultset <- bind_rows(resultset, next_records) } @@ -125,11 +143,9 @@ sf_query_rest <- function(soql, resultset <- resultset %>% type_convert(col_types = cols(.default = col_guess())) } - return(resultset) } - #' @importFrom dplyr bind_rows as_tibble select matches contains rename_at rename tibble #' @importFrom httr content #' @importFrom purrr map_df @@ -137,25 +153,24 @@ sf_query_rest <- function(soql, #' @importFrom xml2 xml_find_first xml_find_all xml_text xml_ns_strip sf_query_soap <- function(soql, object_name, - guess_types = TRUE, queryall = FALSE, - page_size = 1000, + guess_types = TRUE, + control, ..., next_records_url = NULL, - ..., verbose = FALSE){ if(!is.null(next_records_url)){ soap_action <- "queryMore" - r <- make_soap_xml_skeleton() + r <- make_soap_xml_skeleton(soap_headers = control) xml_dat <- build_soap_xml_from_list(input_data = next_records_url, operation = "queryMore", - root=r) + root = r) } else { soap_action <- "query" - r <- make_soap_xml_skeleton(soap_headers=list(QueryOptions=list(batchSize=page_size))) + r <- make_soap_xml_skeleton(soap_headers = control) xml_dat <- build_soap_xml_from_list(input_data = soql, operation = "query", - root=r) + root = r) } base_soap_url <- make_base_soap_url() @@ -199,7 +214,7 @@ sf_query_soap <- function(soql, xml_ns_strip() %>% xml_find_first('.//queryLocator') %>% xml_text() - next_records <- sf_query_soap(next_records_url = query_locator) + next_records <- sf_query_soap(next_records_url = query_locator, control = control, ...) resultset <- bind_rows(resultset, next_records) } @@ -212,9 +227,5 @@ sf_query_soap <- function(soql, resultset <- resultset %>% type_convert(col_types = cols(.default = col_guess())) } - return(resultset) } - -# async-queries/ -# https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/async_query_running_queries.htm diff --git a/R/read-metadata.R b/R/read-metadata.R index 4ae6c92e..9ad3c2d9 100644 --- a/R/read-metadata.R +++ b/R/read-metadata.R @@ -75,6 +75,7 @@ sf_read_metadata <- function(metadata_type, object_names, verbose=FALSE){ #' @importFrom readr type_convert cols #' @importFrom dplyr as_tibble #' @importFrom purrr modify_if +#' @importFrom data.table rbindlist #' @template object_name #' @note The tibble only contains the fields that the user can view, as defined by #' the user's field-level security settings. @@ -96,8 +97,9 @@ sf_describe_object_fields <- function(object_name){ obj_fields_dat <- obj_fields_list %>% # explicitly combine duplicated names because many tidyverse functions break whenever that occurs map(collapse_list_with_dupe_names) %>% - # convert the fields, some simple datatypes, some complex datatypes (lists) into one row each - map_df(~as_tibble(modify_if(., ~(length(.x) > 1 | is.list(.x)), list))) + map(~modify_if(., ~(length(.x) > 1 | is.list(.x)), list)) %>% + rbindlist(use.names=TRUE, fill=TRUE, idcol=NULL) %>% + as_tibble() # sort the columns by name as the API would return prior to the combining process above obj_fields_dat <- obj_fields_dat[,sort(names(obj_fields_dat))] @@ -131,16 +133,16 @@ collapse_list_with_dupe_names <- function(x){ target_idx <- which(names(x) == f) obj_field_dupes <- x[target_idx] if(all(sapply(obj_field_dupes, length) == 1)){ - collapsed <- paste0(unlist(obj_field_dupes), collapse = ",") + collapsed <- list(unname(unlist(obj_field_dupes))) } else { collapsed <- map_df(obj_field_dupes, as_tibble) %>% type_convert(col_types = cols()) %>% list() } # replace into first - x[head(target_idx,1)] <- collapsed + x[head(target_idx, 1)] <- collapsed # remove the rest - x[tail(target_idx,-1)] <- NULL + x[tail(target_idx, -1)] <- NULL } } return(x) diff --git a/R/retrieve.R b/R/retrieve.R index 1d9f6b7e..8104a21d 100644 --- a/R/retrieve.R +++ b/R/retrieve.R @@ -9,6 +9,8 @@ #' on the records #' @template object_name #' @template api_type +#' @template control +#' @param ... arguments passed to \code{\link{sf_control}} #' @template verbose #' @return \code{tibble} #' @examples @@ -26,15 +28,25 @@ sf_retrieve <- function(ids, fields, object_name, api_type = c("REST", "SOAP", "Bulk 1.0", "Bulk 2.0"), + control = list(...), ..., verbose = FALSE){ api_type <- match.arg(api_type) + control_args <- return_matching_controls(control) + control_args$api_type <- api_type + control_args$operation <- "retrieve" if(api_type == "REST"){ - resultset <- sf_retrieve_rest(ids=ids, fields=fields, object_name=object_name, - verbose = verbose) - } else if(api_type == "SOAP"){ - resultset <- sf_retrieve_soap(ids=ids, fields=fields, object_name=object_name, + resultset <- sf_retrieve_rest(ids = ids, + fields = fields, + object_name = object_name, + control = control_args, ..., verbose = verbose) + } else if(api_type == "SOAP"){ + resultset <- sf_retrieve_soap(ids = ids, + fields = fields, + object_name = object_name, + control = control_args, ..., + verbose = verbose) } else if(api_type == "Bulk 1.0"){ stop("Retrieve is not supported in Bulk API. For retrieving a large number of records use SOQL (queries) instead.") } else if(api_type == "Bulk 2.0"){ @@ -55,9 +67,12 @@ sf_retrieve <- function(ids, sf_retrieve_soap <- function(ids, fields, object_name, + control, ..., verbose = FALSE){ ids <- sf_input_data_validation(ids, operation='retrieve') + control <- do.call("sf_control", control) + # limit this type of request to only 200 records at a time to prevent # the XML from exceeding a size limit batch_size <- 200 @@ -82,7 +97,7 @@ sf_retrieve_soap <- function(ids, } batched_data <- ids[batch_id == batch, , drop=FALSE] - r <- make_soap_xml_skeleton() + r <- make_soap_xml_skeleton(soap_headers = control) xml_dat <- build_soap_xml_from_list(input_data = batched_data, operation = "retrieve", object_name = object_name, @@ -123,6 +138,7 @@ sf_retrieve_soap <- function(ids, sf_retrieve_rest <- function(ids, fields, object_name, + control, ..., verbose = FALSE){ ids <- sf_input_data_validation(ids, operation='retrieve') diff --git a/R/search.R b/R/search.R index 71a848a3..d8464c35 100644 --- a/R/search.R +++ b/R/search.R @@ -15,7 +15,7 @@ #' @template guess_types #' @template api_type #' @param parameterized_search_options \code{list}; a list of parameters for -#' controlling the search if not using SOSL. If using SOSL this is ignored. +#' controlling the search if not using SOSL. If using SOSL this argument is ignored. #' @template verbose #' @param ... arguments to be used to form the parameterized search options argument #' if it is not supplied directly. @@ -87,7 +87,7 @@ sf_search <- function(search_string, # TODO: should SOAP only take SOSL? if(!is_sosl){ stop(paste("The SOAP API only takes SOSL formatted search strings.", - "Set is_sosl=TRUE or Use \"REST\" if trying to do a free text search")) + "Set is_sosl=TRUE or set api_type='REST' if trying to do a free text search")) } r <- make_soap_xml_skeleton() xml_dat <- build_soap_xml_from_list(input_data = search_string, @@ -170,8 +170,7 @@ parameterized_search_control <- function(objects = NULL, fields_scope = c("ALL", "NAME", "EMAIL", "PHONE" ,"SIDEBAR"), fields = NULL, overall_limit = 2000, - spell_correction = TRUE - ){ + spell_correction = TRUE){ which_fields_scope <- match.arg(fields_scope) @@ -185,4 +184,4 @@ parameterized_search_control <- function(objects = NULL, result$overallLimit <- as.integer(overall_limit) result$spellCorrection <- tolower(spell_correction) return(result) -} \ No newline at end of file +} diff --git a/R/update-metadata.R b/R/update-metadata.R index 6c275a29..d46403db 100644 --- a/R/update-metadata.R +++ b/R/update-metadata.R @@ -11,7 +11,8 @@ #' @references \url{https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/} #' @template metadata_type #' @template metadata -#' @template all_or_none +#' @template control +#' @param ... arguments passed to \code{\link{sf_control}} #' @template verbose #' @return A \code{tbl_df} containing the creation result for each submitted metadata component #' @note The update key is based on the fullName parameter of the metadata, so updates are triggered @@ -42,22 +43,39 @@ #' metadata = update_metadata) #' } #' @export -sf_update_metadata <- function(metadata_type, metadata, all_or_none=FALSE, verbose=FALSE){ +sf_update_metadata <- function(metadata_type, + metadata, + control = list(...), ..., + verbose = FALSE){ which_operation <- "updateMetadata" # run some basic validation on the metadata to see if it conforms to WSDL standards - metadata <- metadata_type_validator(obj_type=metadata_type, obj_data=metadata) + metadata <- metadata_type_validator(obj_type = metadata_type, obj_data = metadata) + + # determine how to pass along the control args + all_args <- list(...) + control_args <- return_matching_controls(control) + control_args$api_type <- "Metadata" + control_args$operation <- "update" + if("all_or_none" %in% names(all_args)){ + # warn then set it in the control list + warning(paste0("The `all_or_none` argument has been deprecated.\n", + "Please pass AllOrNoneHeader argument or use the `sf_control` function."), + call. = FALSE) + control_args$AllOrNoneHeader = list(allOrNone = tolower(all_args$all_or_none)) + } + control <- do.call("sf_control", control_args) # define the operation operation_node <- newXMLNode(which_operation, - namespaceDefinitions=c('http://soap.sforce.com/2006/04/metadata'), + namespaceDefinitions = c('http://soap.sforce.com/2006/04/metadata'), suppressNamespaceWarning = TRUE) # and add the metadata to it - xml_dat <- build_metadata_xml_from_list(input_data=metadata, metatype=metadata_type, root=operation_node) + xml_dat <- build_metadata_xml_from_list(input_data = metadata, metatype = metadata_type, root = operation_node) base_metadata_url <- make_base_metadata_url() - root <- make_soap_xml_skeleton(soap_headers=list(AllorNoneHeader = tolower(all_or_none)), metadata_ns=TRUE) + root <- make_soap_xml_skeleton(soap_headers = control, metadata_ns = TRUE) body_node <- newXMLNode("soapenv:Body", parent=root) body_node <- addChildren(body_node, xml_dat) diff --git a/R/update.R b/R/update.R index c63309cf..ceda7b72 100644 --- a/R/update.R +++ b/R/update.R @@ -5,55 +5,90 @@ #' @param input_data \code{named vector}, \code{matrix}, \code{data.frame}, or #' \code{tbl_df}; data can be coerced into a \code{data.frame} #' @template object_name -#' @template all_or_none #' @template api_type -#' @param ... other arguments passed on to \code{\link{sf_bulk_operation}}. +#' @template control +#' @param ... arguments passed to \code{\link{sf_control}} or further downstream +#' to \code{\link{sf_bulk_operation}} #' @template verbose #' @return \code{tbl_df} of records with success indicator +#' @note Because the SOAP and REST calls chunk data into batches of 200 records +#' the AllOrNoneHeader will only apply to the success or failure of every batch +#' of records and not all records submitted to the function. #' @examples #' \dontrun{ -#' n <- 3 -#' new_contacts <- tibble(FirstName = rep("Test", n), -#' LastName = paste0("Contact", 1:n)) -#' new_contacts_result <- sf_create(new_contacts, "Contact") +#' n <- 2 +#' new_accts <- tibble(FirstName = rep("Test", n), +#' LastName = paste0("Account", 1:n)) +#' new_records <- sf_create(new_accts, "Account") #' -#' update_contacts <- tibble(FirstName = rep("TestTest", n), -#' LastName = paste0("Contact", 1:n), -#' Id = new_contacts_result$id) -#' updated_contacts_result1 <- sf_update(update_contacts, "Contact") -#' updated_contacts_result2 <- sf_update(update_contacts, "Contact", -#' api_type="Bulk") +#' updated_accts <- tibble(FirstName = rep("TestTest", n), +#' LastName = paste0("Account", 1:n), +#' Id = new_records$id) +#' +#' # update the accounts and ensure that all contacts and cases (open and closed) +#' # owned by the previous account owner are transferred to the new owner +#' update <- sf_update(updated_accts, "Account", +#' OwnerChangeOptions=list(options= +#' list(list(execute=TRUE, +#' type="TransferAllOwnedCases"), +#' list(execute=TRUE, +#' type="TransferOwnedOpenCases"), +#' list(execute=TRUE, +#' type="TransferContacts")))) #' } #' @export sf_update <- function(input_data, object_name, - all_or_none = FALSE, api_type = c("SOAP", "REST", "Bulk 1.0", "Bulk 2.0"), - ..., + control = list(...), ..., verbose = FALSE){ api_type <- match.arg(api_type) - if(api_type == "REST"){ - resultset <- sf_update_rest(input_data=input_data, - object_name=object_name, - all_or_none=all_or_none, - verbose=verbose) - } else if(api_type == "SOAP"){ - resultset <- sf_update_soap(input_data=input_data, - object_name=object_name, - all_or_none=all_or_none, - verbose=verbose) + + # determine how to pass along the control args + all_args <- list(...) + control_args <- return_matching_controls(control) + control_args$api_type <- api_type + control_args$operation <- "update" + if("all_or_none" %in% names(all_args)){ + # warn then set it in the control list + warning(paste0("The `all_or_none` argument has been deprecated.\n", + "Please pass AllOrNoneHeader argument or use the `sf_control` function."), + call. = FALSE) + control_args$AllOrNoneHeader = list(allOrNone = tolower(all_args$all_or_none)) + } + if("AssignmentRuleHeader" %in% names(control_args)){ + if(!object_name %in% c("Account", "Case", "Lead")){ + stop("The AssignmentRuleHeader can only be used when creating, updating, or upserting an Account, Case, or Lead") + } + } + + if(api_type == "SOAP"){ + resultset <- sf_update_soap(input_data = input_data, + object_name = object_name, + control = control_args, + verbose = verbose) + } else if(api_type == "REST"){ + resultset <- sf_update_rest(input_data = input_data, + object_name = object_name, + control = control_args, + verbose = verbose) } else if(api_type == "Bulk 1.0"){ - resultset <- sf_update_bulk_v1(input_data, object_name = object_name, verbose = verbose, ...) + resultset <- sf_update_bulk_v1(input_data, + object_name = object_name, + control = control_args, + verbose = verbose, ...) } else if(api_type == "Bulk 2.0"){ - resultset <- sf_update_bulk_v2(input_data, object_name = object_name, verbose = verbose, ...) + resultset <- sf_update_bulk_v2(input_data, + object_name = object_name, + control = control_args, + verbose = verbose, ...) } else { stop("Unknown API type") } return(resultset) } - #' Update Records using SOAP API #' #' @importFrom readr cols type_convert @@ -65,10 +100,13 @@ sf_update <- function(input_data, #' @importFrom utils head #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal -sf_update_soap <- function(input_data, object_name, all_or_none = FALSE, +sf_update_soap <- function(input_data, + object_name, + control, ..., verbose = FALSE){ input_data <- sf_input_data_validation(operation='update', input_data) + control <- do.call("sf_control", control) base_soap_url <- make_base_soap_url() if(verbose) { @@ -94,7 +132,7 @@ sf_update_soap <- function(input_data, object_name, all_or_none = FALSE, } } batched_data <- input_data[batch_id == batch, , drop=FALSE] - r <- make_soap_xml_skeleton(soap_headers=list(AllorNoneHeader = tolower(all_or_none))) + r <- make_soap_xml_skeleton(soap_headers = control) xml_dat <- build_soap_xml_from_list(input_data = batched_data, operation = "update", object_name = object_name, @@ -116,7 +154,6 @@ sf_update_soap <- function(input_data, object_name, all_or_none = FALSE, return(resultset) } - #' Update Records using REST API #' #' @importFrom readr cols type_convert @@ -126,12 +163,28 @@ sf_update_soap <- function(input_data, object_name, all_or_none = FALSE, #' @importFrom utils head #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal -sf_update_rest <- function(input_data, object_name, all_or_none = FALSE, +sf_update_rest <- function(input_data, + object_name, + control, ..., verbose = FALSE){ # This resource is available in API version 42.0 and later. stopifnot(as.numeric(getOption("salesforcer.api_version")) >= 42.0) input_data <- sf_input_data_validation(operation='update', input_data) + + control <- do.call("sf_control", control) + if("AllOrNoneHeader" %in% names(control)){ + all_or_none <- control$AllOrNoneHeader$allOrNone + } else { + all_or_none <- FALSE + } + request_headers <- c("Accept"="application/json", + "Content-Type"="application/json") + if("AssignmentRuleHeader" %in% names(control)){ + # take the first list element because it could be useDefaultRule (T/F) or assignmentRuleId + request_headers <- c(request_headers, c("Sforce-Auto-Assign" = control$AssignmentRuleHeader[[1]])) + } + input_data$attributes <- lapply(1:nrow(input_data), FUN=function(x, obj){list(type=obj, referenceId=paste0("ref" ,x))}, obj=object_name) #input_data$attributes <- list(rep(list(type=object_name), nrow(input_data)))[[1]] input_data <- input_data %>% select(attributes, everything()) @@ -163,8 +216,7 @@ sf_update_rest <- function(input_data, object_name, all_or_none = FALSE, } batched_data <- input_data[batch_id == batch, , drop=FALSE] httr_response <- rPATCH(url = composite_url, - headers = c("Accept"="application/json", - "Content-Type"="application/json"), + headers = request_headers, body = toJSON(list(allOrNone = tolower(all_or_none), records = batched_data), auto_unbox = TRUE)) @@ -178,42 +230,42 @@ sf_update_rest <- function(input_data, object_name, all_or_none = FALSE, return(resultset) } - #' Update Records using Bulk 1.0 API #' #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal -sf_update_bulk_v1 <- function(input_data, object_name, all_or_none = FALSE, - ..., +sf_update_bulk_v1 <- function(input_data, + object_name, + control, ..., verbose = FALSE){ - # allor none? input_data <- sf_input_data_validation(operation = "update", input_data) + control <- do.call("sf_control", control) resultset <- sf_bulk_operation(input_data = input_data, object_name = object_name, operation = "update", api_type = "Bulk 1.0", - verbose = verbose, ...) + control = control, ..., + verbose = verbose) return(resultset) } - #' Update Records using Bulk 2.0 API #' #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal -sf_update_bulk_v2 <- function(input_data, object_name, all_or_none = FALSE, - ..., +sf_update_bulk_v2 <- function(input_data, + object_name, + control, ..., verbose = FALSE){ - # allor none? - #The order of records in the response is not guaranteed to match the ordering of records in the original job data. - input_data <- sf_input_data_validation(operation = 'update', input_data) + # The order of records in the response is not guaranteed to match the ordering of + # records in the original job data. + input_data <- sf_input_data_validation(operation = "update", input_data) + control <- do.call("sf_control", control) resultset <- sf_bulk_operation(input_data = input_data, object_name = object_name, operation = "update", api_type = "Bulk 2.0", - verbose=verbose, ...) + control = control, ..., + verbose = verbose) return(resultset) } - - - diff --git a/R/upsert-metadata.R b/R/upsert-metadata.R index d8be7ca1..64565357 100644 --- a/R/upsert-metadata.R +++ b/R/upsert-metadata.R @@ -11,7 +11,8 @@ #' @references \url{https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/} #' @template metadata_type #' @template metadata -#' @template all_or_none +#' @template control +#' @param ... arguments passed to \code{\link{sf_control}} #' @template verbose #' @return A \code{tbl_df} containing the creation result for each submitted metadata component #' @note The upsert key is based on the fullName parameter of the metadata, so updates are triggered @@ -45,23 +46,40 @@ #' metadata = upsert_metadata) #' } #' @export -sf_upsert_metadata <- function(metadata_type, metadata, all_or_none=FALSE, verbose=FALSE){ +sf_upsert_metadata <- function(metadata_type, + metadata, + control = list(...), ..., + verbose = FALSE){ which_operation <- "upsertMetadata" # run some basic validation on the metadata to see if it conforms to WSDL standards - metadata <- metadata_type_validator(obj_type=metadata_type, obj_data=metadata) + metadata <- metadata_type_validator(obj_type = metadata_type, obj_data = metadata) + + # determine how to pass along the control args + all_args <- list(...) + control_args <- return_matching_controls(control) + control_args$api_type <- "Metadata" + control_args$operation <- "upsert" + if("all_or_none" %in% names(all_args)){ + # warn then set it in the control list + warning(paste0("The `all_or_none` argument has been deprecated.\n", + "Please pass AllOrNoneHeader argument or use the `sf_control` function."), + call. = FALSE) + control_args$AllOrNoneHeader = list(allOrNone = tolower(all_args$all_or_none)) + } + control <- do.call("sf_control", control_args) # define the operation operation_node <- newXMLNode(which_operation, - namespaceDefinitions=c('http://soap.sforce.com/2006/04/metadata'), + namespaceDefinitions = c('http://soap.sforce.com/2006/04/metadata'), suppressNamespaceWarning = TRUE) # and add the metadata to it - xml_dat <- build_metadata_xml_from_list(input_data=metadata, metatype=metadata_type, root=operation_node) + xml_dat <- build_metadata_xml_from_list(input_data = metadata, metatype = metadata_type, root = operation_node) base_metadata_url <- make_base_metadata_url() - root <- make_soap_xml_skeleton(soap_headers=list(AllorNoneHeader = tolower(all_or_none)), metadata_ns=TRUE) - body_node <- newXMLNode("soapenv:Body", parent=root) + root <- make_soap_xml_skeleton(soap_headers = control, metadata_ns = TRUE) + body_node <- newXMLNode("soapenv:Body", parent = root) body_node <- addChildren(body_node, xml_dat) if(verbose) { diff --git a/R/upsert.R b/R/upsert.R index ae703f23..e0e19615 100644 --- a/R/upsert.R +++ b/R/upsert.R @@ -6,9 +6,10 @@ #' \code{tbl_df}; data can be coerced into a \code{data.frame} #' @template object_name #' @template external_id_fieldname -#' @template all_or_none #' @template api_type -#' @param ... other arguments passed on to \code{\link{sf_bulk_operation}}. +#' @template control +#' @param ... arguments passed to \code{\link{sf_control}} or further downstream +#' to \code{\link{sf_bulk_operation}} #' @template verbose #' @return \code{tbl_df} of records with success indicator #' @examples @@ -35,27 +36,53 @@ sf_upsert <- function(input_data, object_name, external_id_fieldname, - all_or_none = FALSE, api_type = c("SOAP", "REST", "Bulk 1.0", "Bulk 2.0"), - ..., + control = list(...), ..., verbose = FALSE){ api_type <- match.arg(api_type) - if(api_type == "REST"){ - resultset <- sf_upsert_rest(input_data = input_data, object_name = object_name, + + # determine how to pass along the control args + all_args <- list(...) + control_args <- return_matching_controls(control) + control_args$api_type <- api_type + control_args$operation <- "upsert" + if("all_or_none" %in% names(all_args)){ + # warn then set it in the control list + warning(paste0("The `all_or_none` argument has been deprecated.\n", + "Please pass AllOrNoneHeader argument or use the `sf_control` function."), + call. = FALSE) + control_args$AllOrNoneHeader = list(allOrNone = tolower(all_args$all_or_none)) + } + if("AssignmentRuleHeader" %in% names(control_args)){ + if(!object_name %in% c("Account", "Case", "Lead")){ + stop("The AssignmentRuleHeader can only be used when creating, updating, or upserting an Account, Case, or Lead") + } + } + + if(api_type == "SOAP"){ + resultset <- sf_upsert_soap(input_data = input_data, + object_name = object_name, external_id_fieldname = external_id_fieldname, - all_or_none = all_or_none, verbose = verbose) - } else if(api_type == "SOAP"){ - resultset <- sf_upsert_soap(input_data = input_data, object_name = object_name, + control = control_args, + verbose = verbose) + } else if(api_type == "REST"){ + resultset <- sf_upsert_rest(input_data = input_data, + object_name = object_name, external_id_fieldname = external_id_fieldname, - all_or_none = all_or_none, verbose = verbose) + control = control_args, + verbose = verbose) } else if(api_type == "Bulk 1.0"){ - resultset <- sf_upsert_bulk_v1(input_data = input_data, object_name = object_name, - external_id_fieldname = external_id_fieldname, + resultset <- sf_upsert_bulk_v1(input_data = input_data, + object_name = object_name, + external_id_fieldname = external_id_fieldname, + control = control_args, verbose = verbose, ...) } else if(api_type == "Bulk 2.0"){ - resultset <- sf_upsert_bulk_v2(input_data = input_data, object_name = object_name, - external_id_fieldname = external_id_fieldname, + resultset <- sf_upsert_bulk_v2(input_data = input_data, + object_name = object_name, + external_id_fieldname = external_id_fieldname, + control = control_args, verbose = verbose, ...) } else { stop("Unknown API type") @@ -63,7 +90,6 @@ sf_upsert <- function(input_data, return(resultset) } - #' Upsert Records using SOAP API #' #' @importFrom readr cols type_convert @@ -75,12 +101,14 @@ sf_upsert <- function(input_data, #' @importFrom utils head #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal -sf_upsert_soap <- function(input_data, object_name, +sf_upsert_soap <- function(input_data, + object_name, external_id_fieldname, - all_or_none = FALSE, + control, ..., verbose = FALSE){ input_data <- sf_input_data_validation(operation='upsert', input_data) + control <- do.call("sf_control", control) base_soap_url <- make_base_soap_url() if(verbose) { @@ -106,7 +134,7 @@ sf_upsert_soap <- function(input_data, object_name, } } batched_data <- input_data[batch_id == batch, , drop=FALSE] - r <- make_soap_xml_skeleton(soap_headers=list(AllorNoneHeader = tolower(all_or_none))) + r <- make_soap_xml_skeleton(soap_headers = control) xml_dat <- build_soap_xml_from_list(input_data = batched_data, operation = "upsert", object_name = object_name, @@ -129,7 +157,6 @@ sf_upsert_soap <- function(input_data, object_name, return(resultset) } - #' Upsert Records using REST API #' #' @importFrom readr cols type_convert @@ -139,15 +166,27 @@ sf_upsert_soap <- function(input_data, object_name, #' @importFrom utils head #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal -sf_upsert_rest <- function(input_data, object_name, - external_id_fieldname, - all_or_none = FALSE, +sf_upsert_rest <- function(input_data, + object_name, + external_id_fieldname, + control, ..., verbose = FALSE){ # This resource is available in API version 42.0 and later. stopifnot(as.numeric(getOption("salesforcer.api_version")) >= 42.0) input_data <- sf_input_data_validation(operation='upsert', input_data) + control <- do.call("sf_control", control) + if("AllOrNoneHeader" %in% names(control)){ + stop("'All or None' control is not honored when upserting over the REST API. Try using the SOAP API instead.") + } + request_headers <- c("Accept"="application/json", + "Content-Type"="application/json") + if("AssignmentRuleHeader" %in% names(control)){ + # take the first list element because it could be useDefaultRule (T/F) or assignmentRuleId + request_headers <- c(request_headers, c("Sforce-Auto-Assign" = control$AssignmentRuleHeader[[1]])) + } + composite_batch_url <- make_composite_batch_url() if(verbose) { message(composite_batch_url) @@ -180,12 +219,11 @@ sf_upsert_rest <- function(input_data, object_name, url=paste0("v", getOption("salesforcer.api_version"), "/sobjects/", object_name, "/", external_id_fieldname, "/", this_id), - richInput=as.list(temp_batched_data[i,])) + richInput = as.list(temp_batched_data[i,])) } - request_body <- list(batchRequests=inner_requests) + request_body <- list(batchRequests = inner_requests) httr_response <- rPOST(url = composite_batch_url, - headers = c("Accept"="application/json", - "Content-Type"="application/json"), + headers = request_headers, body = toJSON(request_body, auto_unbox = TRUE)) catch_errors(httr_response) @@ -202,46 +240,47 @@ sf_upsert_rest <- function(input_data, object_name, type_convert(col_types = cols()) return(resultset) } - #' Upsert Records using Bulk 1.0 API #' #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal -sf_upsert_bulk_v1 <- function(input_data, object_name, +sf_upsert_bulk_v1 <- function(input_data, + object_name, external_id_fieldname, - all_or_none = FALSE, - ..., + control, ..., verbose = FALSE){ - # allor none? input_data <- sf_input_data_validation(operation = "upsert", input_data) + control <- do.call("sf_control", control) resultset <- sf_bulk_operation(input_data = input_data, object_name = object_name, external_id_fieldname = external_id_fieldname, operation = "upsert", api_type = "Bulk 1.0", - verbose = verbose, ...) + control = control, ..., + verbose = verbose) return(resultset) } - #' Upsert Records using Bulk 2.0 API #' #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal -sf_upsert_bulk_v2 <- function(input_data, object_name, +sf_upsert_bulk_v2 <- function(input_data, + object_name, external_id_fieldname, - all_or_none = FALSE, - ..., + control, ..., verbose = FALSE){ - # allor none? - #The order of records in the response is not guaranteed to match the ordering of records in the original job data. - input_data <- sf_input_data_validation(operation = 'upsert', input_data) + # The order of records in the response is not guaranteed to match the ordering of + # records in the original job data. + input_data <- sf_input_data_validation(operation = "upsert", input_data) + control <- do.call("sf_control", control) resultset <- sf_bulk_operation(input_data = input_data, object_name = object_name, external_id_fieldname = external_id_fieldname, operation = "upsert", api_type = "Bulk 2.0", - verbose = verbose, ...) + control = control, ..., + verbose = verbose) return(resultset) } diff --git a/R/utils-control.R b/R/utils-control.R new file mode 100644 index 00000000..ec7c1148 --- /dev/null +++ b/R/utils-control.R @@ -0,0 +1,332 @@ +#' Auxiliary for Controlling Calls to Salesforce APIs +#' +#' Typically only used internally by functions when control parameters are passed +#' through via dots (...), but it can be called directly to control the behavior +#' of API calls. This function behaves exactly like \code{\link[stats:glm.control]{glm.control}} +#' for the \code{\link[stats:glm]{glm}} function. +#' +#' @importFrom purrr modify modify_if map +#' @param AllOrNoneHeader \code{list}; containing the \code{allOrNone} element with +#' a value of \code{TRUE} or \code{FALSE}. This control specifies whether a call rolls back all changes +#' unless all records are processed successfully. This control is available in +#' SOAP, REST, and Metadata APIs for the following functions: \code{\link{sf_create}}, +#' \code{\link{sf_delete}}, \code{\link{sf_update}}, \code{\link{sf_upsert}}, \code{\link{sf_create_metadata}}, +#' \code{\link{sf_delete_metadata}}, \code{\link{sf_update_metadata}}, \code{\link{sf_upsert_metadata}}. +#' For more information, read the Salesforce documentation +#' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_allornoneheader.htm}{here}. +#' @param AllowFieldTruncationHeader \code{list}; containing the \code{allowFieldTruncation} +#' element with a value of \code{TRUE} or \code{FALSE}. This control specifies the truncation behavior +#' for some field types in SOAP API version 15.0 and later for the following functions: +#' \code{\link{sf_create}}, \code{\link{sf_update}}, \code{\link{sf_upsert}}. For +#' more information, read the Salesforce documentation +#' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_allowfieldtruncation.htm}{here}. +#' @param AssignmentRuleHeader \code{list}; containing the \code{useDefaultRule} +#' element with a value of \code{TRUE} or \code{FALSE} or the \code{assignmentRuleId} element. +#' This control specifies the assignment rule to use when creating or updating an +#' Account, Case, or Lead for the following functions: \code{\link{sf_create}}, +#' \code{\link{sf_update}}, \code{\link{sf_upsert}}. For more information, read the Salesforce documentation +#' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_assignmentruleheader.htm}{here}. +#' @param DisableFeedTrackingHeader \code{list}; containing the \code{disableFeedTracking} +#' element with a value of \code{TRUE} or \code{FALSE}. This control specifies whether +#' the changes made in the current call are tracked in feeds for SOAP API calls made +#' with the following functions: \code{\link{sf_create}}, \code{\link{sf_delete}}, +#' \code{\link{sf_update}}, \code{\link{sf_upsert}}. For more information, read the Salesforce documentation +#' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_disablefeedtracking.htm}{here}. +#' @param DuplicateRuleHeader \code{list}; containing the \code{allowSave}, +#' \code{includeRecordDetails}, and \code{runAsCurrentUser} elements each with a +#' value of \code{TRUE} or \code{FALSE}. This control specifies how duplicate rules should be applied +#' when using the following functions: \code{\link{sf_create}}, \code{\link{sf_update}}, +#' \code{\link{sf_upsert}}. For more information, read the Salesforce documentation +#' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_duplicateruleheader.htm}{here}. +#' @param EmailHeader \code{list}; containing the \code{triggerAutoResponseEmail}, +#' \code{triggerOtherEmail}, and \code{triggerUserEmail} elements each with a +#' value of \code{TRUE} or \code{FALSE}. This control determines if an email notification should be sent +#' when a request is processed by SOAP API calls made with the following functions: +#' \code{\link{sf_create}}, \code{\link{sf_delete}}, \code{\link{sf_update}}, \code{\link{sf_upsert}}, +#' \code{\link{sf_reset_password}}. For more information, read the Salesforce documentation +#' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_emailheader.htm}{here}. +#' @param LocaleOptions \code{list}; containing the \code{language} element. This control +#' specifies the language of the labels returned by the \code{\link{sf_describe_objects}} +#' function using the SOAP API. The value must be a valid user locale (language and country), such as +#' \code{de_DE} or \code{en_GB}. For more information, read the Salesforce documentation +#' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_localeheader.htm}{here}. +#' The list of valid user locales is available +#' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_objects_categorynodelocalization.htm#languagelocalekey_desc}{here}. +#' @param MruHeader \code{list}; containing the \code{updateMru} element with a value +#' of \code{TRUE} or \code{FALSE}. This control indicates whether to update the list +#' of most recently used items (\code{TRUE}) or not (\code{FALSE}) in the Recent Items +#' section of the sidebar in the Salesforce user interface. This works for SOAP API calls +#' made with the following functions: \code{\link{sf_create}}, \code{\link{sf_update}}, +#' \code{\link{sf_upsert}}, \code{\link{sf_retrieve}}, \code{\link{sf_query}}. For more +#' information, read the Salesforce documentation +#' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_mruheader.htm}{here}. +#' @param OwnerChangeOptions \code{list}; containing the \code{options} element. +#' This control specifies the details of ownership of attachments and notes when a +#' record’s owner is changed. This works for SOAP API calls made with the following functions: +#' \code{\link{sf_update}}, \code{\link{sf_upsert}}. For more information, read the Salesforce documentation +#' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_ownerchangeoptions.htm}{here}. +#' @param QueryOptions \code{list}; containing the \code{batchSize} element. +#' This control specifies the batch size for query results . This works for SOAP or +#' REST API calls made with the following functions: \code{\link{sf_query}}, +#' \code{\link{sf_retrieve}}. For more information, read the Salesforce documentation +#' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_queryoptions.htm}{here}. +#' @param UserTerritoryDeleteHeader \code{list}; containing the \code{transferToUserId} element. +#' This control specifies a user to whom open opportunities are assigned when the current +#' owner is removed from a territory. This works for the \code{\link{sf_delete}} function +#' using the SOAP API. For more information, read the Salesforce documentation +#' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_userterritorydeleteheader.htm}{here}. +#' @param BatchRetryHeader \code{list}; containing the \code{Sforce-Disable-Batch-Retry} element. +#' When you create a bulk job, the Batch Retry control lets you disable retries +#' for unfinished batches included in the job. This works for most operations run through +#' the Bulk 1.0 API (e.g. \code{sf_create(., api_type = "Bulk 1.0")}) or creating +#' a Bulk 1.0 job with \code{\link{sf_create_job_bulk}}. For more information, read the Salesforce documentation +#' \href{https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/async_api_headers_disable_batch_retry.htm}{here}. +#' @param LineEndingHeader \code{list}; containing the \code{Sforce-Line-Ending} element. +#' When you’re creating a bulk upload job, the Line Ending control lets you +#' specify whether line endings are read as line feeds (LFs) or as carriage returns +#' and line feeds (CRLFs) for fields of type Text Area and Text Area (Long). This +#' works for most operations run through the Bulk APIs or creating a Bulk 1.0 +#' job with \code{\link{sf_create_job_bulk}}. For more information, read the +#' Salesforce documentation +#' \href{https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/async_api_headers_line_ending.htm}{here}. +#' @param PKChunkingHeader \code{list}; containing the \code{Sforce-Enable-PKChunking} element. +#' Use the PK Chunking control to enable automatic primary key (PK) chunking +#' for a bulk query job. This works for queries run through the Bulk 1.0 API either via +#' \code{sf_query(., api_type = "Bulk 1.0")}) or \code{\link{sf_query_bulk}}. For +#' more information, read the Salesforce documentation +#' \href{https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/async_api_headers_enable_pk_chunking.htm}{here}. +#' @template api_type +#' @template operation +#' @examples +#' \dontrun{ +#' this_control <- sf_control(DuplicateRuleHeader=list(allowSave=TRUE, +#' includeRecordDetails=FALSE, +#' runAsCurrentUser=TRUE)) +#' new_contact <- c(FirstName = "Test", LastName = "Contact-Create") +#' new_record <- sf_create(new_contact, "Contact", control = this_control) +#' +#' # specifying the controls directly and are picked up by dots +#' new_record <- sf_create(new_contact, "Contact", +#' DuplicateRuleHeader = list(allowSave=TRUE, +#' includeRecordDetails=FALSE, +#' runAsCurrentUser=TRUE)) +#' } +#' @export +sf_control <- function(AllOrNoneHeader=list(allOrNone=FALSE), + AllowFieldTruncationHeader=list(allowFieldTruncation=FALSE), + AssignmentRuleHeader=list(useDefaultRule=TRUE), + DisableFeedTrackingHeader=list(disableFeedTracking=FALSE), + DuplicateRuleHeader=list(allowSave=FALSE, + includeRecordDetails=FALSE, + runAsCurrentUser=TRUE), + EmailHeader=list(triggerAutoResponseEmail=FALSE, + triggerOtherEmail=FALSE, + triggerUserEmail=TRUE), + LocaleOptions=list(language="en_US"), + MruHeader=list(updateMru=FALSE), + OwnerChangeOptions=list(options=list(list(execute=TRUE, + type="EnforceNewOwnerHasReadAccess"), + list(execute=FALSE, + type="KeepAccountTeam"), + list(execute=FALSE, + type="KeepSalesTeam"), + list(execute=FALSE, + type="KeepSalesTeamGrantCurrentOwnerReadWriteAccess"), + list(execute=FALSE, + type="SendEmail"), + list(execute=FALSE, + type="TransferAllOwnedCases"), + list(execute=TRUE, + type="TransferContacts"), + list(execute=TRUE, + type="TransferContracts"), + list(execute=FALSE, + type="TransferNotesAndAttachments"), + list(execute=TRUE, + type="TransferOpenActivities"), + list(execute=TRUE, + type="TransferOrders"), + list(execute=FALSE, + type="TransferOtherOpenOpportunities"), + list(execute=FALSE, + type="TransferOwnedClosedOpportunities"), + list(execute=FALSE, + type="TransferOwnedOpenCases"), + list(execute=FALSE, + type="TransferOwnedOpenOpportunities"))), + QueryOptions=list(batchSize=1000), + UserTerritoryDeleteHeader=list(transferToUserId=NA), + BatchRetryHeader=list(`Sforce-Disable-Batch-Retry`=FALSE), + LineEndingHeader=list(`Sforce-Line-Ending`=NA), + PKChunkingHeader=list(`Sforce-Enable-PKChunking`=FALSE), + api_type = NULL, + operation = NULL){ + + # determine which elements were supplied, dropping the function call itself + supplied_arguments <- as.list(match.call()[-1]) + # drop api_type and operation existing + supplied_arguments[["api_type"]] <- NULL + supplied_arguments[["operation"]] <- NULL + + # now eval them to no longer be objects of class "call" + supplied_arguments <- supplied_arguments %>% + map(eval) %>% + # convert boolean args to lowercase + modify(~modify_if(., is.logical, tolower)) + + # check that they are all lists + if(!all(sapply(supplied_arguments, is.list))){ + stop("All control arguments must be lists. Review the argument defaults in 'sf_control()' for help formatting.") + } + + # check that the controls valid for the API and operation + supplied_arguments <- filter_valid_controls(supplied_arguments, api_type = api_type) + supplied_arguments <- filter_valid_controls(supplied_arguments, operation = operation) + + return(supplied_arguments) +} + +#' Return the Accepted Control Arguments by API Type +#' +#' @note This function is meant to be used internally. Only use when debugging. +#' @keywords internal +#' @export +accepted_controls_by_api <- function(api_type = c("SOAP", "REST", "Bulk 1.0", "Bulk 2.0", "Metadata")){ + this_api_type <- match.arg(api_type) + switch(this_api_type, + "SOAP" = c("AllOrNoneHeader", "AllowFieldTruncationHeader", "AssignmentRuleHeader", + "DisableFeedTrackingHeader", "DuplicateRuleHeader", + "EmailHeader", "LocaleOptions", + "MruHeader", "OwnerChangeOptions", + "QueryOptions", "UserTerritoryDeleteHeader"), + "REST" = c("AllOrNoneHeader", "AssignmentRuleHeader", "QueryOptions"), + "Bulk 1.0" = c("LineEndingHeader", "BatchRetryHeader", "PKChunkingHeader"), + "Bulk 2.0" = c("LineEndingHeader"), + "Metadata" = c("AllOrNoneHeader"), + character(0)) +} + +#' Return the Accepted Control Arguments by Operation +#' +#' @note This function is meant to be used internally. Only use when debugging. +#' @keywords internal +#' @export +accepted_controls_by_operation <- function(operation = c("delete", "hardDelete", "insert", "update", "upsert", + "query", "queryall", "retrieve", "resetPassword", + "describeSObjects")){ + record_creation_controls <- c("AllOrNoneHeader", "AllowFieldTruncationHeader", + "AssignmentRuleHeader", "DisableFeedTrackingHeader", + "DuplicateRuleHeader", "EmailHeader", "MruHeader") + query_controls <- c("MruHeader", "QueryOptions", "PKChunkingHeader") + bulk_controls <- c("BatchRetryHeader", "LineEndingHeader") + this_operation <- match.arg(operation) + switch(this_operation, + "delete" = c("AllOrNoneHeader", "DisableFeedTrackingHeader", "EmailHeader", + "UserTerritoryDeleteHeader", bulk_controls), + "hardDelete" = bulk_controls, + "insert" = c(record_creation_controls, bulk_controls), + "update" = c(record_creation_controls, bulk_controls, "OwnerChangeOptions"), + "upsert" = c(record_creation_controls, bulk_controls, "OwnerChangeOptions"), + "query" = query_controls, + "queryall" = query_controls, + "retrieve" = c("MruHeader"), + "resetPassword" = c("EmailHeader"), + "describeSObjects" = c("LocaleOptions"), + character(0)) +} + +#' Filter Out Control Arguments by API or Operation +#' +#' @note This function is meant to be used internally. Only use when debugging. +#' @keywords internal +#' @export +filter_valid_controls <- function(supplied, api_type = NULL, operation = NULL){ + if(!is.null(api_type)){ + valid <- accepted_controls_by_api(api_type) + # provide a warning before dropping + if(length(setdiff(names(supplied), valid)) > 0){ + warning(sprintf("Ignoring the following controls which are not used in the %s API: %s", + api_type, + paste0(setdiff(names(supplied), valid), collapse=", ")), + call. = TRUE) + } + supplied <- supplied[intersect(names(supplied), valid)] + } + + if(!is.null(operation)){ + valid <- accepted_controls_by_operation(operation) + # provide a warning before dropping + if(length(setdiff(names(supplied), valid)) > 0){ + warning(sprintf("Ignoring the following controls which are not used in the '%s' operation: %s", + operation, + paste0(setdiff(names(supplied), valid), collapse=", ")), + call. = TRUE) + } + supplied <- supplied[intersect(names(supplied), valid)] + } + + return(supplied) +} + +#' Of All Args Return Ones Matching Control Arguments +#' +#' @note This function is meant to be used internally. Only use when debugging. +#' @keywords internal +#' @export +return_matching_controls <- function(args){ + possible_controls <- formals(sf_control) + idx <- names(args) %in% names(possible_controls) + return(args[idx]) +} + +# add reverse compatibility for functions that have an explicit argument for +# all_or_none (DOING!) +# line_ending + +# REST Translations +# AllorNoneHeader = (allOrNone, currently in our batched data process) +# AssignmentRuleHeader = Sforce-Auto-Assign +# DuplicateRuleHeader = NONE! +# QueryOptions = Sforce-Query-Options + +# Bulk 1.0 Translations +# AssignmentRuleHeader = assignmentRuleId (in the job info) (only if id?) +# AllorNoneHeader? = Unknown if SOAP works for Bulk 1.0 as well +# DuplicateRuleHeader? = Unknown if SOAP works for Bulk 1.0 as well + +# Bulk 2.0 Translations (does not use any headers) +# Just line ending? + + +# HELD BACK CONTROL OPTIONS BECAUSE THEY MAY BE CONFUSING OR NOT FEASIBLE GIVEN +# THE WAY THE PACKAGE IS CURRENTLY WRITTEN: + +#CallOptions=list(client=NA, defaultNamespace=NA) +#' CallOptions \code{list}; containing the \code{client} and \code{defaultNamespace} +#' elements. This control specifies the call options needed to work with a specific client. +#' This control is only available for use with the Partner WSDL and works for most all functions +#' in this package. For more information, read the Salesforce documentation +#' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_calloptions.htm}{here}. + +#LimitInfoHeader=list(current="20", +# limit="250", +# type="API REQUESTS") +#' LimitInfoHeader \code{list}; containing the \code{current}, \code{limit}, +#' and \code{type} elements. This control determines if limit information for the organization should +#' be returned in the header each request response. This works for most all functions, +#' except \code{\link{sf_auth()}}. For more information, read the Salesforce documentation +#' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_limitinfo.htm}{here}. + +#PackageVersionHeader=list(packageVersions=NA) +#' PackageVersionHeader \code{list}; containing the \code{packageVersions} element. +#' This header specifies the package version for each installed managed package in +#' API version 16.0 and later. This works for most all functions in this package. +#' For more information, read the Salesforce documentation +#' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_packageversionheader.htm}{here}. + +#ContentTypeHeader=list(`Content-Type`="application/xml") +#' ContentTypeHeader \code{list}; containing the \code{Content-Type} element. +#' This header specifies the format for your request and response. Set the value of +#' this header to match the contentType of the job you’re working with. This works +#' for most all functions with the Bulk 1.0 API. For more information, read the Salesforce documentation +#' \href{https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/async_api_headers_content_type.htm}{here}. diff --git a/R/utils-org.R b/R/utils-org.R index 8011492f..3b9c9a78 100644 --- a/R/utils-org.R +++ b/R/utils-org.R @@ -105,14 +105,22 @@ sf_set_password <- function(user_id, password, verbose=FALSE){ #' @importFrom httr content #' @importFrom xml2 xml_ns_strip xml_find_all xml_text #' @param user_id character; the unique Salesforce Id assigned to the User +#' @template control +#' @param ... arguments passed to \code{\link{sf_control}} #' @template verbose #' @return \code{list} #' @examples #' \dontrun{ -#' sf_reset_password(user_id = "0056A000000ZZZaaBBB") +#' # reset a user's password and ensure that an email is triggered to them +#' sf_reset_password(user_id = "0056A000000ZZZaaBBB", +#' EmailHeader = list(triggerAutoResponseEmail = FALSE, +#' triggerOtherEmail = FALSE, +#' triggerUserEmail = TRUE)) #' } #' @export -sf_reset_password <- function(user_id, verbose=FALSE){ +sf_reset_password <- function(user_id, + control = list(...), ..., + verbose = FALSE){ base_soap_url <- make_base_soap_url() if(verbose) { @@ -120,10 +128,14 @@ sf_reset_password <- function(user_id, verbose=FALSE){ } # build the body - r <- make_soap_xml_skeleton() - xml_dat <- build_soap_xml_from_list(input_data = list(userId=user_id), + control_args <- return_matching_controls(control) + control_args$api_type <- "SOAP" + control_args$operation <- "resetPassword" + control <- do.call("sf_control", control_args) + r <- make_soap_xml_skeleton(soap_headers = control) + xml_dat <- build_soap_xml_from_list(input_data = list(userId = user_id), operation = "resetPassword", - root=r) + root = r) httr_response <- rPOST(url = base_soap_url, headers = c("SOAPAction"="create", diff --git a/R/utils-xml.R b/R/utils-xml.R index e6fb4fa0..b4c9ca26 100644 --- a/R/utils-xml.R +++ b/R/utils-xml.R @@ -181,75 +181,57 @@ build_soap_xml_from_list <- function(input_data, # this is so we have something to create the root node stopifnot(!is.null(root_name) | !is.null(root)) which_operation <- match.arg(operation) - input_data <- sf_input_data_validation(input_data, operation=which_operation) + input_data <- sf_input_data_validation(input_data, operation = which_operation) - if (is.null(root)) + if(is.null(root)){ root <- newXMLNode(root_name, namespaceDefinitions = ns) + } - body_node <- newXMLNode("soapenv:Body", parent=root) + body_node <- newXMLNode("soapenv:Body", parent = root) operation_node <- newXMLNode(sprintf("urn:%s", which_operation), - parent=body_node) + parent = body_node) if(which_operation == "upsert"){ stopifnot(!is.null(external_id_fieldname)) - external_field_node <- newXMLNode("urn:externalIDFieldName", - external_id_fieldname, - parent=operation_node) + external_field_node <- newXMLNode("urn:externalIDFieldName", external_id_fieldname, + parent = operation_node) } if(which_operation == "retrieve"){ stopifnot(!is.null(object_name)) stopifnot(!is.null(fields)) - field_list_node <- newXMLNode("urn:fieldList", - paste0(fields, collapse=","), - parent=operation_node) - sobject_type_node <- newXMLNode("urn:sObjectType", - object_name, - parent=operation_node) - } + field_list_node <- newXMLNode("urn:fieldList", paste0(fields, collapse=","), + parent = operation_node) + sobject_type_node <- newXMLNode("urn:sObjectType", object_name, + parent = operation_node) + } if(which_operation %in% c("search", "query")){ - element_name <- if(which_operation == "search") "urn:searchString" else "urn:queryString" - this_node <- newXMLNode(element_name, - input_data[1,1], - parent=operation_node) - + this_node <- newXMLNode(element_name, input_data[1,1], + parent = operation_node) } else if(which_operation == "queryMore"){ - - this_node <- newXMLNode("urn:queryLocator", - input_data[1,1], - parent=operation_node) - - } else if(which_operation %in% c("delete","retrieve","findDuplicatesByIds")){ - + this_node <- newXMLNode("urn:queryLocator", input_data[1,1], + parent = operation_node) + } else if(which_operation %in% c("delete", "retrieve", "findDuplicatesByIds")){ for(i in 1:nrow(input_data)){ - this_node <- newXMLNode("urn:ids", - input_data[i,"Id"], - parent=operation_node) + this_node <- newXMLNode("urn:ids", input_data[i,"Id"], + parent = operation_node) } - } else if(which_operation == "describeSObjects"){ - for(i in 1:nrow(input_data)){ - this_node <- newXMLNode("urn:sObjectType", - input_data[i,"sObjectType"], - parent=operation_node) + this_node <- newXMLNode("urn:sObjectType", input_data[i,"sObjectType"], + parent = operation_node) } - } else if(which_operation == "setPassword"){ - this_node <- newXMLNode("userId", - input_data$userId, - parent=operation_node) - this_node <- newXMLNode("password", - input_data$password, - parent=operation_node) + this_node <- newXMLNode("userId", input_data$userId, + parent = operation_node) + this_node <- newXMLNode("password", input_data$password, + parent = operation_node) } else if(which_operation == "resetPassword"){ - this_node <- newXMLNode("userId", - input_data$userId, - parent=operation_node) + this_node <- newXMLNode("userId", input_data$userId, + parent = operation_node) } else { - for(i in 1:nrow(input_data)){ list <- as.list(input_data[i,,drop=FALSE]) this_row_node <- newXMLNode("urn:sObjects", parent=operation_node) @@ -318,7 +300,7 @@ build_metadata_xml_from_list <- function(input_data, if (typeof(input_data[[i]]) == "list"){ build_metadata_xml_from_list(input_data=input_data[[i]], root=this, metatype=NULL) } - else{ + else { xmlValue(this) <- input_data[[i]] } } diff --git a/R/utils.R b/R/utils.R index b796d12b..d6b57be6 100644 --- a/R/utils.R +++ b/R/utils.R @@ -57,7 +57,7 @@ get_os <- function(){ #' @export sf_input_data_validation <- function(input_data, operation=''){ - # TODO: Automatic date validation + # TODO: Add Automatic date validation # https://developer.salesforce.com/docs/atlas.en-us.api_bulk_v2.meta/api_bulk_v2/datafiles_date_format.htm if(!is.data.frame(input_data)){ @@ -156,87 +156,3 @@ remove_empty_linked_object_cols <- function(dat, api_type = c("SOAP", "REST")){ } return(dat) } - -api_headers <- function(api_type=NULL, - AllorNoneHeader=list(allOrNone=FALSE), - AllowFieldTruncationHeader=list(allowFieldTruncation=FALSE), - AssignmentRuleHeader=list(useDefaultRule=TRUE), - CallOptions=list(client=NA, defaultNamespace=NA), - DisableFeedTrackingHeader=list(disableFeedTracking=FALSE), - DuplicateRuleHeader=list(allowSave=FALSE, - includeRecordDetails=FALSE, - runAsCurrentUser=TRUE), - EmailHeader=list(triggerAutoResponseEmail=FALSE, - triggerOtherEmail=FALSE, - triggerUserEmail=TRUE), - LimitInfoHeader=list(current="20", - limit="250", - type="API REQUESTS"), - LocaleOptions=list(language=NA), - LoginScopeHeader=list(organizationId=NA, - portalId=NA), - MruHeader=list(updateMru=FALSE), - OwnerChangeOptions=list(options=list(list(execute=FALSE, - type="EnforceNewOwnerHasReadAccess"), - list(execute=TRUE, - type="KeepSalesTeam"), - list(execute=FALSE, - type="KeepSalesTeamGrantCurrentOwnerReadWriteAccess"), - list(execute=TRUE, - type="TransferOpenActivities"), - list(execute=FALSE, - type="TransferNotesAndAttachments"), - list(execute=TRUE, - type="TransferOtherOpenOpportunities"), - list(execute=TRUE, - type="TransferOwnedOpenOpportunities"), - list(execute=TRUE, - type="TransferContracts"), - list(execute=TRUE, - type="TransferOrders"), - list(execute=TRUE, - type="TransferContacts"))), - PackageVersionHeader=list(packageVersions=NA), - QueryOptions=list(batchSize=500), - SessionHeader=list(sessionId=NA), - UserTerritoryDeleteHeader=list(transferToUserId=NA), - ContentTypeHeader=list(`Content-Type`="application/xml"), - BatchRetryHeader=list(`Sforce-Disable-Batch-Retry`=FALSE), - LineEndingHeader=list(`Sforce-Line-Ending`=NA), - PKChunkingHeader=list(`Sforce-Enable-PKChunking`=FALSE)){ - - # check if its in the supplied and known list - # tailor the search to the API - - api_type <- match.arg(api_type) - - if(!is.null()){ - if(api_type == "SOAP"){ - - } else if(api_type == "REST"){ - - } else if(api_type == "Bulk 1.0"){ - - } else { - # do nothing - } - } - - sf_user_info()$userLocale - - list() -} - - - -# TESTING -# # if x is used, then it must be supplied or given a default -# # Error in zz() : argument "x" is missing, with no default -# zz <- function(x,y){ -# if(missing(x)){ -# x <- 2 -# } -# xx <- x -# return(5) -# } - diff --git a/R/zzz.R b/R/zzz.R index 1a537685..9b357201 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -2,7 +2,7 @@ op <- options() op.salesforcer <- list( - salesforcer.api_version = "45.0", + salesforcer.api_version = "46.0", salesforcer.login_url = "https://login.salesforce.com", salesforcer.consumer_key = "3MVG9CEn_O3jvv0yRMQezJ8PwesiIknRU9v9j778rv78UvJ2JTQzSG.QduxyMxYaldoNEhO0eVvw4ogCT58c5", salesforcer.consumer_secret = "3471656211653393546", diff --git a/README.Rmd b/README.Rmd index c4820f80..0b388cd3 100644 --- a/README.Rmd +++ b/README.Rmd @@ -16,7 +16,8 @@ knitr::opts_chunk$set( [![Build Status](https://travis-ci.org/StevenMMortimer/salesforcer.svg?branch=master)](https://travis-ci.org/StevenMMortimer/salesforcer) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/StevenMMortimer/salesforcer?branch=master&svg=true)](https://ci.appveyor.com/project/StevenMMortimer/salesforcer) -[![CRAN_Status_Badge](http://www.r-pkg.org/badges/version/salesforcer)](http://cran.r-project.org/package=salesforcer) +[![CRAN Status Badge](https://www.r-pkg.org/badges/version/salesforcer)](https://cran.r-project.org/package=salesforcer) +[![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/master/graph/badge.svg)](https://codecov.io/gh/StevenMMortimer/salesforcer?branch=master) **salesforcer** is an R package that connects to Salesforce Platform APIs using @@ -33,9 +34,12 @@ Package features include: * 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()`) + * 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) @@ -60,6 +64,16 @@ devtools::install_github("StevenMMortimer/salesforcer") If you encounter a clear bug, please file a minimal reproducible example on [GitHub](https://github.com/StevenMMortimer/salesforcer/issues). +## Vignettes + +The README below outlines the package functionality, but review the vignettes for +more detailed examples on usage. + + * [Getting Started](https://StevenMMortimer.github.io/salesforcer/articles/getting-started.html) + * [Working with Bulk API](https://StevenMMortimer.github.io/salesforcer/articles/working-with-bulk-api.html) + * [Transitioning from RForcecom](https://StevenMMortimer.github.io/salesforcer/articles/transitioning-from-RForcecom.html) + * [Passing Control Args](https://StevenMMortimer.github.io/salesforcer/articles/passing-control-args.html) + ## Usage ### Authenticate @@ -240,6 +254,11 @@ 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 ``` If you prefer to be more precise about collecting and formatting the field data you @@ -251,9 +270,10 @@ describe_obj_result <- sf_describe_objects(object_names=c('Account', 'Contact')) # confirm that the Account object is queryable describe_obj_result[[1]][c('label', 'queryable')] # show the different picklist values for the Account Type field -the_type_field <- describe_obj_result[[1]][[59]] -the_type_field$label -map_df(the_type_field[which(names(the_type_field) == "picklistValues")], as_tibble) +all_fields <- describe_obj_result[[1]][names(describe_obj_result[[1]]) == "fields"] +the_type_field_idx <- which(sapply(all_fields, FUN=function(x){x$label}) == "Account Type") +acct_type_field <- all_fields[[the_type_field_idx]] +map_df(acct_type_field[which(names(acct_type_field) == "picklistValues")], as_tibble) ``` It is recommended that you try out the various metadata functions `sf_read_metadata()`, @@ -294,6 +314,30 @@ deleted_custom_object_result <- sf_delete_metadata(metadata_type = 'CustomObject object_names = c('Custom_Account1__c')) ``` +Note that newly created custom fields are not editable by default, meaning that you +will not be able to insert records into them until updating the field level security +of your user profile. Run the following code to determine the user profiles in your +org and updating the field permissions on an object that you may have created with the +example code above. + +```{r metadata-crud-field-security, eval=FALSE} +# get list of user proviles in order to get the "fullName" parameter correct in the next call +my_queries <- list(list(type='Profile')) +profiles_list <- sf_list_metadata(queries=my_queries) + +# update the field level security to "editable" for your fields +prof_update <- sf_update_metadata(metadata_type='Profile', + metadata=list(fullName='Admin', + fieldPermissions=list(field=paste0(custom_object$fullName, '.CustomField3__c'), + editable='true'), + fieldPermissions=list(field=paste0(custom_object$fullName, '.CustomField4__c'), + editable='true'))) + +# now try inserting values into that custom object's fields +my_new_data = tibble(CustomField3__c = "Hello World", CustomField4__c = "Hello World") +added_record <- sf_create(my_new_data, object_name = custom_object$fullName) +``` + ```{r, include=FALSE, message = FALSE, eval=FALSE} # There are methods as part of the REST API that will return metatdata. #sf_describe_global() diff --git a/README.md b/README.md index 0da3ae38..b5d5c5fe 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,10 @@ Status](https://travis-ci.org/StevenMMortimer/salesforcer.svg?branch=master)](https://travis-ci.org/StevenMMortimer/salesforcer) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/StevenMMortimer/salesforcer?branch=master&svg=true)](https://ci.appveyor.com/project/StevenMMortimer/salesforcer) -[![CRAN\_Status\_Badge](http://www.r-pkg.org/badges/version/salesforcer)](http://cran.r-project.org/package=salesforcer) +[![CRAN Status +Badge](https://www.r-pkg.org/badges/version/salesforcer)](https://cran.r-project.org/package=salesforcer) +[![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/master/graph/badge.svg)](https://codecov.io/gh/StevenMMortimer/salesforcer?branch=master) @@ -31,10 +34,13 @@ Package features include: `rforcecom.query()`, `rforcecom.create()` - Basic utility calls (`sf_user_info()`, `sf_server_timestamp()`, `sf_list_objects()`) + - 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) @@ -60,6 +66,20 @@ devtools::install_github("StevenMMortimer/salesforcer") If you encounter a clear bug, please file a minimal reproducible example on [GitHub](https://github.com/StevenMMortimer/salesforcer/issues). +## Vignettes + +The README below outlines the package functionality, but review the +vignettes for more detailed examples on usage. + + - [Getting + Started](https://StevenMMortimer.github.io/salesforcer/articles/getting-started.html) + - [Working with Bulk + API](https://StevenMMortimer.github.io/salesforcer/articles/working-with-bulk-api.html) + - [Transitioning from + RForcecom](https://StevenMMortimer.github.io/salesforcer/articles/transitioning-from-RForcecom.html) + - [Passing Control + Args](https://StevenMMortimer.github.io/salesforcer/articles/passing-control-args.html) + ## Usage ### Authenticate @@ -119,8 +139,8 @@ created_records #> # A tibble: 2 x 2 #> id success #> -#> 1 0036A00000taMJ6QAM TRUE -#> 2 0036A00000taMJ7QAM TRUE +#> 1 0036A00000wzh7AQAQ TRUE +#> 2 0036A00000wzh7BQAQ TRUE ``` ### Query @@ -147,8 +167,8 @@ queried_records #> # A tibble: 2 x 4 #> Id Account FirstName LastName #> -#> 1 0036A00000taMJ6QAM NA Test Contact-Create-1 -#> 2 0036A00000taMJ7QAM NA Test Contact-Create-2 +#> 1 0036A00000wzh7AQAQ NA Test Contact-Create-1 +#> 2 0036A00000wzh7BQAQ NA Test Contact-Create-2 ``` ### Update @@ -173,8 +193,8 @@ updated_records #> # A tibble: 2 x 2 #> id success #> -#> 1 0036A00000taMJ6QAM TRUE -#> 2 0036A00000taMJ7QAM TRUE +#> 1 0036A00000wzh7AQAQ TRUE +#> 2 0036A00000wzh7BQAQ TRUE ``` ### Bulk Operations @@ -300,6 +320,22 @@ acct_fields %>% select(name, label, length, soapType, type) #> 9 BillingState Billing State/Province 80 xsd:string string #> 10 BillingPostalCode Billing Zip/Postal Code 20 xsd:string string #> # … with 57 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 +#> +#> 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 +#> 6 TRUE FALSE Technology Partner Technology Partner +#> 7 TRUE FALSE Other Other ``` If you prefer to be more precise about collecting and formatting the @@ -318,11 +354,20 @@ describe_obj_result[[1]][c('label', 'queryable')] #> $queryable #> [1] "true" # show the different picklist values for the Account Type field -the_type_field <- describe_obj_result[[1]][[59]] -the_type_field$label -#> NULL -map_df(the_type_field[which(names(the_type_field) == "picklistValues")], as_tibble) -#> # A tibble: 0 x 0 +all_fields <- describe_obj_result[[1]][names(describe_obj_result[[1]]) == "fields"] +the_type_field_idx <- which(sapply(all_fields, FUN=function(x){x$label}) == "Account Type") +acct_type_field <- all_fields[[the_type_field_idx]] +map_df(acct_type_field[which(names(acct_type_field) == "picklistValues")], as_tibble) +#> # A tibble: 7 x 4 +#> active defaultValue label value +#> +#> 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 +#> 6 true false Technology Partner Technology Partner +#> 7 true false Other Other ``` It is recommended that you try out the various metadata functions @@ -365,6 +410,32 @@ deleted_custom_object_result <- sf_delete_metadata(metadata_type = 'CustomObject object_names = c('Custom_Account1__c')) ``` +Note that newly created custom fields are not editable by default, +meaning that you will not be able to insert records into them until +updating the field level security of your user profile. Run the +following code to determine the user profiles in your org and updating +the field permissions on an object that you may have created with the +example code +above. + +``` r +# get list of user proviles in order to get the "fullName" parameter correct in the next call +my_queries <- list(list(type='Profile')) +profiles_list <- sf_list_metadata(queries=my_queries) + +# update the field level security to "editable" for your fields +prof_update <- sf_update_metadata(metadata_type='Profile', + metadata=list(fullName='Admin', + fieldPermissions=list(field=paste0(custom_object$fullName, '.CustomField3__c'), + editable='true'), + fieldPermissions=list(field=paste0(custom_object$fullName, '.CustomField4__c'), + editable='true'))) + +# now try inserting values into that custom object's fields +my_new_data = tibble(CustomField3__c = "Hello World", CustomField4__c = "Hello World") +added_record <- sf_create(my_new_data, object_name = custom_object$fullName) +``` + ## Future Future APIs to support: diff --git a/_pkgdown.yml b/_pkgdown.yml index 7e454539..3a851e2e 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -21,6 +21,8 @@ navbar: href: articles/getting-started.html - text: Working with Bulk API href: articles/working-with-bulk-api.html + - text: Passing Control Args + href: articles/passing-control-args.html - text: Transitioning from RForcecom href: articles/transitioning-from-RForcecom.html - text: Reference @@ -81,6 +83,7 @@ reference: - '`sf_list_api_limits`' - '`sf_list_objects`' - '`sf_server_timestamp`' + - '`sf_control`' - title: Backward Compatibility with RForcecom desc: Functions that mirror RForcecom to ease code transitions between salesforcer and RForcecom contents: diff --git a/cran-comments.md b/cran-comments.md index f0fb2e5f..8e7970ce 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -1,8 +1,8 @@ ## Test environments -* local OS X install, R 3.4.3 -* ubuntu 12.04 (on travis-ci), R 3.4.3, R-oldrel, R-devel. -* win-builder (devel) +* local OS X install, R 3.5.2 +* ubuntu 12.04 (on travis-ci), R 3.5.3, R-oldrel, R-devel. +* win-builder (release) ## R CMD check results diff --git a/docs/CONDUCT.html b/docs/CONDUCT.html index b91de2d7..f4dfaca3 100644 --- a/docs/CONDUCT.html +++ b/docs/CONDUCT.html @@ -98,6 +98,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/LICENSE-text.html b/docs/LICENSE-text.html index 3e68e4e1..51e0c26b 100644 --- a/docs/LICENSE-text.html +++ b/docs/LICENSE-text.html @@ -98,6 +98,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/articles/getting-started.html b/docs/articles/getting-started.html index c26d5da0..33ef7dc3 100644 --- a/docs/articles/getting-started.html +++ b/docs/articles/getting-started.html @@ -59,6 +59,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -147,8 +150,8 @@

    #> # A tibble: 2 x 2 #> id success #> <chr> <lgl> -#> 1 0036A00000taMNmQAM TRUE -#> 2 0036A00000taMNnQAM TRUE +#> 1 0036A00000wzhFCQAY TRUE +#> 2 0036A00000wzhFDQAY TRUE +#> 1 0036A00000wzhFCQAY Test Contact-Create-1 +#> 2 0036A00000wzhFDQAY Test Contact-Create-2 +#> 1 0036A00000wzhFCQAY NA Test Contact-Create-1 +#> 2 0036A00000wzhFDQAY NA Test Contact-Create-2 +#> 1 0036A00000wzhFCQAY TRUE +#> 2 0036A00000wzhFDQAY TRUE +#> 1 0036A00000wzhFCQAY TRUE <list [0]> +#> 2 0036A00000wzhFDQAY TRUE <list [0]> +#> 1 FALSE 0036A00000wzhFHQAY TRUE +#> 2 FALSE 0036A00000wzhFIQAY TRUE +#> 3 TRUE 0036A00000wzhFMQAY TRUE

    diff --git a/docs/articles/index.html b/docs/articles/index.html index f920ed06..0f49ad2d 100644 --- a/docs/articles/index.html +++ b/docs/articles/index.html @@ -98,6 +98,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -154,6 +157,7 @@

    All vignettes

    diff --git a/docs/articles/passing-control-args.html b/docs/articles/passing-control-args.html new file mode 100644 index 00000000..2f0e7c59 --- /dev/null +++ b/docs/articles/passing-control-args.html @@ -0,0 +1,223 @@ + + + + + + + +Passing Control Args • salesforcer + + + + + + + + + + + + +
    +
    + + + +
    +
    + + + + +

    If you’re inserting records from R you may want to turn off the assignment rules or even bypass duplicate rules and alerts to save records. Beginning in Version 0.1.3 of the salesforcer package many functions have a control argument that will allow you to fine tune the behavior of calls to the Salesforce APIs. This vignette will introduce the different options you can control and how to pass them into the salesforcer functions you’re already familiar with.

    +
    +

    +The new control argument

    +

    This new feature can be seen in the sf_create (and many other functions) as control=list(...). The dots mean that you can pass any number of controls directly into the function. For example, the following code will create a record, but prevent its creation from showing up in the Chatter feeds by setting the DisableFeedTrackingHeader.

    + +

    You will notice that the argument DisableFeedTrackingHeader can be included right into the function without any documentation existing for it in the sf_create function. This is because the dots (...) allow you to pass over a dozen different control parameters and that documentation would be tedious to create and maintain over multiple functions in the package. However, you will notice in the documentation entry for the control argument there is a link to a function called sf_control which you can use to directly to pass into control or simply to review its documentation of all the possible control parameters and their defaults. This is where you can review the various control options in more detail before trying to set them.

    +

    You may have also noticed that the argument DisableFeedTrackingHeader was formatted as a list with an element inside called disableFeedTracking set to TRUE. This may seem redundant but there are two reasons for this. First, this is exactly how the Salesforce APIs documents these options, which are typically referred to as “headers” because they are passed as a named header of the HTTP request and then the header fields and values are provided for that header. Second, some headers have multiple fields and values so a list is the only way to provide multiple named fields and values under a single header entity. For example, the DuplicateRuleHeader, which controls whether the duplicate rules can be overridden when inserting records from the API, has three fields: allowSave, includeRecordDetails, and runAsCurrentUser. Supplying all three requires a list-like structure, which may seem redundant in other cases, but is necessary to follow.

    + +

    Finally, you will notice in the example call that the api_type argument is set to “SOAP”. This is because the DisableFeedTrackingHeader is a control that is only available when making calls via the SOAP API. You will receive a warning when trying to set control parameters for APIs or operations that do not recognize that control. For example, the following code tries to set the BatchRetryHeader for a call to the SOAP API which does not acknowledge that control. That control is only used with the Bulk 1.0 API since its records as submitted in batches and automatic retry can be controlled.

    + +
    +
    +

    +Creating the control argument with sf_control

    +

    If this type of control structure is new to you, take a look at the documentation for the glm and glm.control functions. The way these two functions behave is exactly how functions like sf_create and sf_control work with each other. As demonstrated above you can pass any number of arbitrary controls into the function and they are all gathered up into the control by control = list(...). However, you can specify the control directly like this:

    + +
    +
    +

    +Backwards compatibility for all_or_none and other named arguments

    +

    You may already be taking advantage of the all_or_none or line_ending arguments which are control arguments that were explicity included in functions. These argument essentially hard coded values to pass the AllOrNoneHeader and LineEndingHeader control parameters. Starting with the 0.1.3 release it is no longer necessary and preferable not to have an argument like all_or_none listed explicity as an argument since it can be provided in the control argument. Note: the all_or_none argument and other explicit control arguments will still be available in salesforcer 0.1.3 but will provide a deprecated warning. They will be removed in the next CRAN release of the package so it will be important to update your code now if you are explicitly passing these arguments and see a deprecation warning.

    +
    + +
    + + + +
    + + +
    + +
    +

    Site built with pkgdown.

    +
    + +
    +
    + + + + + diff --git a/docs/articles/transitioning-from-RForcecom.html b/docs/articles/transitioning-from-RForcecom.html index 871baec5..4158aae4 100644 --- a/docs/articles/transitioning-from-RForcecom.html +++ b/docs/articles/transitioning-from-RForcecom.html @@ -59,6 +59,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -130,7 +133,7 @@

    #> sessionID instanceURL #> "{MASKED}" "https://na50.salesforce.com/" #> apiVersion -#> "45.0" +#> "46.0" # replicated in salesforcer package session2 <- salesforcer::rforcecom.login(username, paste0(password, security_token), @@ -140,7 +143,7 @@

    #> sessionID instanceURL #> "{MASKED}" "https://na50.salesforce.com/" #> apiVersion -#> "45.0"

    +#> "46.0"

    Note that we must set the API version here because calls to session will not create a new sessionId and then we are stuck with version 35.0 (the default from RForcecom::rforcecom.login). Some functions in salesforcer implement API calls that are only available after version 35.0.

    +#> 1 0036A00000wzhFgQAI TRUE

    Here is an example showing the reduction in code of using salesforcer if you would like to create multiple records.

    +#> 1 0036A00000wzhFvQAI TRUE +#> 2 0036A00000wzhFwQAI TRUE +#> 1 0036A00000wzFJpQAM NA +#> 2 0036A00000wzFK9QAM NA +#> 3 0036A00000wzFLMQA2 NA +#> 4 0036A00000kHn9kQAC NA +#> 5 0036A00000kHn9lQAC NA +#> # compoundFieldName <chr>, createable <chr>, custom <chr>, +#> # defaultedOnCreate <chr>, defaultValue <list>, +#> # deprecatedAndHidden <chr>, digits <chr>, externalId <chr>, +#> # extraTypeInfo <chr>, filterable <chr>, groupable <chr>, +#> # idLookup <chr>, label <chr>, length <chr>, name <chr>, +#> # nameField <chr>, namePointing <chr>, nillable <chr>, +#> # permissionable <chr>, picklistValues <list>, +#> # polymorphicForeignKey <chr>, precision <chr>, queryByDistance <chr>, +#> # referenceTo <chr>, relationshipName <chr>, restrictedPicklist <chr>, +#> # scale <chr>, searchPrefilterable <chr>, soapType <chr>, +#> # sortable <chr>, type <chr>, unique <chr>, updateable <chr>

    In the future more features will be migrated from RForcecom to make the transition as seamless as possible.

    diff --git a/docs/articles/working-with-bulk-api.html b/docs/articles/working-with-bulk-api.html index 188cafd9..c61dd3c7 100644 --- a/docs/articles/working-with-bulk-api.html +++ b/docs/articles/working-with-bulk-api.html @@ -59,6 +59,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -134,16 +137,16 @@

    #> # A tibble: 2 x 3 #> id success errors #> <chr> <lgl> <list> -#> 1 0036A00000taMNEQA2 TRUE <list [0]> -#> 2 0036A00000taMNFQA2 TRUE <list [0]> +#> 1 0036A00000wzhG0QAI TRUE <list [0]> +#> 2 0036A00000wzhG1QAI TRUE <list [0]> # Bulk bulk_created_records <- sf_create(new_contacts, object_name="Contact", api_type="Bulk 1.0") bulk_created_records #> # A tibble: 2 x 4 #> Id Success Created Error #> <chr> <lgl> <lgl> <lgl> -#> 1 0036A00000taMOQQA2 TRUE TRUE NA -#> 2 0036A00000taMORQA2 TRUE TRUE NA +#> 1 0036A00000wzhG5QAI TRUE TRUE NA +#> 2 0036A00000wzhG6QAI TRUE TRUE NA

    There are some differences in the way each API returns response information; however, the end result is exactly the same for these two calls. Also, note that this package utilizes the Bulk 2.0 API for most bulk calls except for bulk queries since Salesforce has not yet implemented it in 2.0.

    Here is a simple workflow of adding, querying, and deleting records using the Bulk 1.0 API.

    +#> 1 0036A00000wzhGPQAY TRUE FALSE NA +#> 2 0036A00000wzhGQQAY TRUE FALSE NA diff --git a/docs/authors.html b/docs/authors.html index 2a779f2d..eefee54a 100644 --- a/docs/authors.html +++ b/docs/authors.html @@ -98,6 +98,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -158,11 +161,11 @@

    Authors

  • -

    Jennifer Bryan. Contributor. +

    Jennifer Bryan. Contributor, copyright holder.

  • -

    Joanna Zhao. Contributor. +

    Joanna Zhao. Contributor, copyright holder.

  • diff --git a/docs/index.html b/docs/index.html index 877b73da..3101dee5 100644 --- a/docs/index.html +++ b/docs/index.html @@ -12,11 +12,11 @@ - @@ -65,6 +65,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -138,12 +141,14 @@
  • Basic utility calls (sf_user_info(), sf_server_timestamp(), sf_list_objects())
  • +
  • Passing API call control parameters such as, “All or None”, “Duplicate Rule”, “Assignment Rule” execution and many more
  • Table of Contents

    If you encounter a clear bug, please file a minimal reproducible example on GitHub.

    +
    +

    +Vignettes

    +

    The README below outlines the package functionality, but review the vignettes for more detailed examples on usage.

    + +
    +#> 1 0036A00000wzhEnQAI TRUE +#> 2 0036A00000wzhEoQAI TRUE +#> 1 0036A00000wzhEnQAI NA Test Contact-Create-1 +#> 2 0036A00000wzhEoQAI NA Test Contact-Create-2 +#> 1 0036A00000wzhEnQAI TRUE +#> 2 0036A00000wzhEoQAI TRUE +#> # … with 57 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 +#> <lgl> <lgl> <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 +#> 6 TRUE FALSE Technology Partner Technology Partner +#> 7 TRUE FALSE Other Other

    If you prefer to be more precise about collecting and formatting the field data you can work directly with the nested lists that the APIs return. In this example we look at the picklist values of fields on the Account object.

    +all_fields <- describe_obj_result[[1]][names(describe_obj_result[[1]]) == "fields"] +the_type_field_idx <- which(sapply(all_fields, FUN=function(x){x$label}) == "Account Type") +acct_type_field <- all_fields[[the_type_field_idx]] +map_df(acct_type_field[which(names(acct_type_field) == "picklistValues")], as_tibble) +#> # 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 +#> 6 true false Technology Partner Technology Partner +#> 7 true false Other Other

    It is recommended that you try out the various metadata functions sf_read_metadata(), sf_list_metadata(), sf_describe_metadata(), and sf_describe_objects() in order to see which information best suits your use case.

    Where the Metadata API really shines is when it comes to CRUD operations on metadata. In this example we will create an object, add fields to it, then delete that object.

    +

    Note that newly created custom fields are not editable by default, meaning that you will not be able to insert records into them until updating the field level security of your user profile. Run the following code to determine the user profiles in your org and updating the field permissions on an object that you may have created with the example code above.

    +
    @@ -436,7 +493,8 @@

    Dev status

    • Build Status
    • AppVeyor Build Status
    • -
    • CRAN_Status_Badge
    • +
    • CRAN_Status_Badge
    • +
    • Monthly Downloads
    • Coverage Status
    diff --git a/docs/news/index.html b/docs/news/index.html index eb3c6cdf..ba37ac0d 100644 --- a/docs/news/index.html +++ b/docs/news/index.html @@ -98,6 +98,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -156,6 +159,7 @@

    Features

      +
    • Upgraded to version 46.0 (Summer ’19) from version 45.0 (Spring ’19)
    • Add RForcecom backward compatibile version of rforcecom.getObjectDescription()
    • Add sf_describe_object_fields() which is a tidyier version of rforcecom.getObjectDescription() @@ -165,6 +169,8 @@

    • Add new utility functions sf_set_password() and sf_reset_password() (#11)
    • Add two new functions to check for duplicates (sf_find_duplicates(), sf_find_duplicates_by_id()) (#4)
    • Add new function to download attachments to disk (sf_download_attachment()) (#20)
    • +
    • The object_name argument, required for bulk queries, will be inferred if left blank, making it no longer a required argument
    • +
    • Almost all functions in the package now have a control argument and dots (...) which allows for more than a dozen different control parameters listed in sf_control() to be fed into existing function calls to tweak the default behavior. For example, if you would like to override duplicate rules then you can adjust the DuplicateRuleHeader. If you would like to have certain assignment rule run on newly created records, then pass in the AssignmentRuleHeader (#4, #5)
    @@ -174,7 +180,9 @@

  • Fix bug where Username/Password authenticated sessions where not working with api_type = “Bulk 1.0”
  • Fix bug where Bulk 1.0 queries that timeout hit an error while trying to abort since that only supported aborting Bulk 2.0 jobs (#13)
  • Fix bug that had only production environment logins possible because of hard coding (@weckstm, #18)
  • -
  • Make sf_describe_object_fields() more robust against nested list elements (#16)
  • +
  • Make sf_describe_object_fields() more robust against nested list elements and also return picklists as tibbles (#16)
  • +
  • Fix bug where four of the bulk operation options (content_type, concurrency_mode, line_ending, and column_delimiter) where not being passed down from the top level generic functions like sf_create(), sf_update(), etc. However, line_ending has now been moved into the sf_control function so it is no longer explicitly listed for bulk operations as an argument. (@mitch-niche, #23)
  • +
  • Ensure that for SOAP, REST, and Bulk 2.0 APIs the verbose argument prints out the XML or JSON along with the URL of the call so it can be replicated via cURL or some other programming language (#8)

  • diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index 28d0aed6..298410e9 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -3,6 +3,7 @@ pkgdown: 1.1.0 pkgdown_sha: ~ articles: getting-started: getting-started.html + passing-control-args: passing-control-args.html transitioning-from-RForcecom: transitioning-from-RForcecom.html working-with-bulk-api: working-with-bulk-api.html urls: diff --git a/docs/reference/VERB_n.html b/docs/reference/VERB_n.html index 39a4a2db..ac5c070f 100644 --- a/docs/reference/VERB_n.html +++ b/docs/reference/VERB_n.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/accepted_controls_by_api.html b/docs/reference/accepted_controls_by_api.html new file mode 100644 index 00000000..b1cb6ee3 --- /dev/null +++ b/docs/reference/accepted_controls_by_api.html @@ -0,0 +1,216 @@ + + + + + + + + +Return the Accepted Control Arguments by API Type — accepted_controls_by_api • salesforcer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + +
    + +
    +
    + + +
    + +

    Return the Accepted Control Arguments by API Type

    + +
    + +
    accepted_controls_by_api(api_type = c("SOAP", "REST", "Bulk 1.0",
    +  "Bulk 2.0", "Metadata"))
    + +

    Note

    + +

    This function is meant to be used internally. Only use when debugging.

    + + +
    + +
    + +
    + + +
    +

    Site built with pkgdown.

    +
    + +
    +
    + + + + + + + + + diff --git a/docs/reference/accepted_controls_by_operation.html b/docs/reference/accepted_controls_by_operation.html new file mode 100644 index 00000000..6a2916c1 --- /dev/null +++ b/docs/reference/accepted_controls_by_operation.html @@ -0,0 +1,217 @@ + + + + + + + + +Return the Accepted Control Arguments by Operation — accepted_controls_by_operation • salesforcer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + +
    + +
    +
    + + +
    + +

    Return the Accepted Control Arguments by Operation

    + +
    + +
    accepted_controls_by_operation(operation = c("delete", "hardDelete",
    +  "insert", "update", "upsert", "query", "queryall", "retrieve",
    +  "resetPassword", "describeSObjects"))
    + +

    Note

    + +

    This function is meant to be used internally. Only use when debugging.

    + + +
    + +
    + +
    + + +
    +

    Site built with pkgdown.

    +
    + +
    +
    + + + + + + + + + diff --git a/docs/reference/build_metadata_xml_from_list.html b/docs/reference/build_metadata_xml_from_list.html index 88e83e2f..2a7e0fff 100644 --- a/docs/reference/build_metadata_xml_from_list.html +++ b/docs/reference/build_metadata_xml_from_list.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/build_soap_xml_from_list.html b/docs/reference/build_soap_xml_from_list.html index 6d0496d0..d02c3005 100644 --- a/docs/reference/build_soap_xml_from_list.html +++ b/docs/reference/build_soap_xml_from_list.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/catch_errors.html b/docs/reference/catch_errors.html index 612a127b..17528a30 100644 --- a/docs/reference/catch_errors.html +++ b/docs/reference/catch_errors.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/collapse_list_with_dupe_names.html b/docs/reference/collapse_list_with_dupe_names.html index a09ae855..ed0b57d9 100644 --- a/docs/reference/collapse_list_with_dupe_names.html +++ b/docs/reference/collapse_list_with_dupe_names.html @@ -102,6 +102,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/filter_valid_controls.html b/docs/reference/filter_valid_controls.html new file mode 100644 index 00000000..28fccaea --- /dev/null +++ b/docs/reference/filter_valid_controls.html @@ -0,0 +1,215 @@ + + + + + + + + +Filter Out Control Arguments by API or Operation — filter_valid_controls • salesforcer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + +
    + +
    +
    + + +
    + +

    Filter Out Control Arguments by API or Operation

    + +
    + +
    filter_valid_controls(supplied, api_type = NULL, operation = NULL)
    + +

    Note

    + +

    This function is meant to be used internally. Only use when debugging.

    + + +
    + +
    + +
    + + +
    +

    Site built with pkgdown.

    +
    + +
    +
    + + + + + + + + + diff --git a/docs/reference/get_os.html b/docs/reference/get_os.html index 792e662f..ac2808de 100644 --- a/docs/reference/get_os.html +++ b/docs/reference/get_os.html @@ -101,6 +101,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/index.html b/docs/reference/index.html index 9201ece5..81931c07 100644 --- a/docs/reference/index.html +++ b/docs/reference/index.html @@ -98,6 +98,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -381,6 +384,12 @@

    sf_server_timestamp()

    Salesforce Server Timestamp

    + + + +

    sf_control()

    + +

    Auxiliary for Controlling Calls to Salesforce APIs

    diff --git a/docs/reference/is_legit_token.html b/docs/reference/is_legit_token.html index d2b2918c..836bb09e 100644 --- a/docs/reference/is_legit_token.html +++ b/docs/reference/is_legit_token.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_base_metadata_url.html b/docs/reference/make_base_metadata_url.html index 34b57322..5121ad9a 100644 --- a/docs/reference/make_base_metadata_url.html +++ b/docs/reference/make_base_metadata_url.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_base_rest_url.html b/docs/reference/make_base_rest_url.html index 08d8fbc5..d9f2d577 100644 --- a/docs/reference/make_base_rest_url.html +++ b/docs/reference/make_base_rest_url.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_base_soap_url.html b/docs/reference/make_base_soap_url.html index e905e965..8a61b7f4 100644 --- a/docs/reference/make_base_soap_url.html +++ b/docs/reference/make_base_soap_url.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_bulk_batch_details_url.html b/docs/reference/make_bulk_batch_details_url.html index 37b97146..6635ddbc 100644 --- a/docs/reference/make_bulk_batch_details_url.html +++ b/docs/reference/make_bulk_batch_details_url.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_bulk_batch_status_url.html b/docs/reference/make_bulk_batch_status_url.html index c4114f7e..0dd819c7 100644 --- a/docs/reference/make_bulk_batch_status_url.html +++ b/docs/reference/make_bulk_batch_status_url.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_bulk_batches_url.html b/docs/reference/make_bulk_batches_url.html index e21ea33e..16620985 100644 --- a/docs/reference/make_bulk_batches_url.html +++ b/docs/reference/make_bulk_batches_url.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_bulk_create_job_url.html b/docs/reference/make_bulk_create_job_url.html index 31c28599..97f38455 100644 --- a/docs/reference/make_bulk_create_job_url.html +++ b/docs/reference/make_bulk_create_job_url.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_bulk_delete_job_url.html b/docs/reference/make_bulk_delete_job_url.html index 6cf9a221..cb4fcd3d 100644 --- a/docs/reference/make_bulk_delete_job_url.html +++ b/docs/reference/make_bulk_delete_job_url.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_bulk_end_job_generic_url.html b/docs/reference/make_bulk_end_job_generic_url.html index c4420c79..f28b7f4f 100644 --- a/docs/reference/make_bulk_end_job_generic_url.html +++ b/docs/reference/make_bulk_end_job_generic_url.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_bulk_get_all_jobs_url.html b/docs/reference/make_bulk_get_all_jobs_url.html index d9158739..f66dbcf7 100644 --- a/docs/reference/make_bulk_get_all_jobs_url.html +++ b/docs/reference/make_bulk_get_all_jobs_url.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_bulk_get_job_url.html b/docs/reference/make_bulk_get_job_url.html index fc81c7f4..71626864 100644 --- a/docs/reference/make_bulk_get_job_url.html +++ b/docs/reference/make_bulk_get_job_url.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_bulk_job_records_url.html b/docs/reference/make_bulk_job_records_url.html index 7249cc82..98e08b64 100644 --- a/docs/reference/make_bulk_job_records_url.html +++ b/docs/reference/make_bulk_job_records_url.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_bulk_query_result_url.html b/docs/reference/make_bulk_query_result_url.html index b50c5bcd..7e18b690 100644 --- a/docs/reference/make_bulk_query_result_url.html +++ b/docs/reference/make_bulk_query_result_url.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_bulk_query_url.html b/docs/reference/make_bulk_query_url.html index c1a4c7b5..c74de6e6 100644 --- a/docs/reference/make_bulk_query_url.html +++ b/docs/reference/make_bulk_query_url.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_chatter_users_url.html b/docs/reference/make_chatter_users_url.html index 1e2b163c..7999819d 100644 --- a/docs/reference/make_chatter_users_url.html +++ b/docs/reference/make_chatter_users_url.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_composite_batch_url.html b/docs/reference/make_composite_batch_url.html index 79220d4a..05ec22ce 100644 --- a/docs/reference/make_composite_batch_url.html +++ b/docs/reference/make_composite_batch_url.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_composite_url.html b/docs/reference/make_composite_url.html index 81e1f4eb..4bc5a820 100644 --- a/docs/reference/make_composite_url.html +++ b/docs/reference/make_composite_url.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_describe_objects_url.html b/docs/reference/make_describe_objects_url.html index 78b348e0..1cbd97d2 100644 --- a/docs/reference/make_describe_objects_url.html +++ b/docs/reference/make_describe_objects_url.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_login_url.html b/docs/reference/make_login_url.html index 1c580e82..dc5f4bb4 100644 --- a/docs/reference/make_login_url.html +++ b/docs/reference/make_login_url.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_parameterized_search_url.html b/docs/reference/make_parameterized_search_url.html index 42e04298..04e54331 100644 --- a/docs/reference/make_parameterized_search_url.html +++ b/docs/reference/make_parameterized_search_url.html @@ -101,6 +101,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_query_url.html b/docs/reference/make_query_url.html index 89a82bf9..16a2c025 100644 --- a/docs/reference/make_query_url.html +++ b/docs/reference/make_query_url.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_search_url.html b/docs/reference/make_search_url.html index 294d0eca..df0f6c22 100644 --- a/docs/reference/make_search_url.html +++ b/docs/reference/make_search_url.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/make_soap_xml_skeleton.html b/docs/reference/make_soap_xml_skeleton.html index dea26885..902509b3 100644 --- a/docs/reference/make_soap_xml_skeleton.html +++ b/docs/reference/make_soap_xml_skeleton.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/metadata_type_validator.html b/docs/reference/metadata_type_validator.html index 3c6638e1..01970459 100644 --- a/docs/reference/metadata_type_validator.html +++ b/docs/reference/metadata_type_validator.html @@ -101,6 +101,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/parameterized_search_control.html b/docs/reference/parameterized_search_control.html index f797512e..ca588102 100644 --- a/docs/reference/parameterized_search_control.html +++ b/docs/reference/parameterized_search_control.html @@ -101,6 +101,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/rDELETE.html b/docs/reference/rDELETE.html index 3a56484e..e73d812c 100644 --- a/docs/reference/rDELETE.html +++ b/docs/reference/rDELETE.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/rGET.html b/docs/reference/rGET.html index ae52731b..d7ed1a61 100644 --- a/docs/reference/rGET.html +++ b/docs/reference/rGET.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/rPATCH.html b/docs/reference/rPATCH.html index b42a7067..9c2bee8c 100644 --- a/docs/reference/rPATCH.html +++ b/docs/reference/rPATCH.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/rPOST.html b/docs/reference/rPOST.html index 18bbfda6..7e182ec8 100644 --- a/docs/reference/rPOST.html +++ b/docs/reference/rPOST.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/rPUT.html b/docs/reference/rPUT.html index fab2b623..55774c16 100644 --- a/docs/reference/rPUT.html +++ b/docs/reference/rPUT.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/remove_empty_linked_object_cols.html b/docs/reference/remove_empty_linked_object_cols.html index fbb5f115..0088e80b 100644 --- a/docs/reference/remove_empty_linked_object_cols.html +++ b/docs/reference/remove_empty_linked_object_cols.html @@ -102,6 +102,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/return_matching_controls.html b/docs/reference/return_matching_controls.html new file mode 100644 index 00000000..7157775f --- /dev/null +++ b/docs/reference/return_matching_controls.html @@ -0,0 +1,215 @@ + + + + + + + + +Of All Args Return Ones Matching Control Arguments — return_matching_controls • salesforcer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + +
    + +
    +
    + + +
    + +

    Of All Args Return Ones Matching Control Arguments

    + +
    + +
    return_matching_controls(args)
    + +

    Note

    + +

    This function is meant to be used internally. Only use when debugging.

    + + +
    + +
    + +
    + + +
    +

    Site built with pkgdown.

    +
    + +
    +
    + + + + + + + + + diff --git a/docs/reference/rforcecom.bulkAction.html b/docs/reference/rforcecom.bulkAction.html index 205d15da..13f1e395 100644 --- a/docs/reference/rforcecom.bulkAction.html +++ b/docs/reference/rforcecom.bulkAction.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/rforcecom.bulkQuery.html b/docs/reference/rforcecom.bulkQuery.html index a1602d7f..27b60899 100644 --- a/docs/reference/rforcecom.bulkQuery.html +++ b/docs/reference/rforcecom.bulkQuery.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/rforcecom.create.html b/docs/reference/rforcecom.create.html index 7900f7b0..c763cbaa 100644 --- a/docs/reference/rforcecom.create.html +++ b/docs/reference/rforcecom.create.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/rforcecom.delete.html b/docs/reference/rforcecom.delete.html index fc8eb249..2dbc3740 100644 --- a/docs/reference/rforcecom.delete.html +++ b/docs/reference/rforcecom.delete.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/rforcecom.getObjectDescription.html b/docs/reference/rforcecom.getObjectDescription.html index 0ed48014..e89b4639 100644 --- a/docs/reference/rforcecom.getObjectDescription.html +++ b/docs/reference/rforcecom.getObjectDescription.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/rforcecom.getServerTimestamp.html b/docs/reference/rforcecom.getServerTimestamp.html index 1b115d97..9aa58db2 100644 --- a/docs/reference/rforcecom.getServerTimestamp.html +++ b/docs/reference/rforcecom.getServerTimestamp.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/rforcecom.login.html b/docs/reference/rforcecom.login.html index 63844177..fa3e8855 100644 --- a/docs/reference/rforcecom.login.html +++ b/docs/reference/rforcecom.login.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/rforcecom.query.html b/docs/reference/rforcecom.query.html index 34dd6529..533e3b9a 100644 --- a/docs/reference/rforcecom.query.html +++ b/docs/reference/rforcecom.query.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/rforcecom.retrieve.html b/docs/reference/rforcecom.retrieve.html index d17293b9..29a3ffa3 100644 --- a/docs/reference/rforcecom.retrieve.html +++ b/docs/reference/rforcecom.retrieve.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/rforcecom.search.html b/docs/reference/rforcecom.search.html index c9528607..f61c261b 100644 --- a/docs/reference/rforcecom.search.html +++ b/docs/reference/rforcecom.search.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/rforcecom.update.html b/docs/reference/rforcecom.update.html index 9cead755..6b32d121 100644 --- a/docs/reference/rforcecom.update.html +++ b/docs/reference/rforcecom.update.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/rforcecom.upsert.html b/docs/reference/rforcecom.upsert.html index b09a1ba6..f28ffdd3 100644 --- a/docs/reference/rforcecom.upsert.html +++ b/docs/reference/rforcecom.upsert.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/salesforcer.html b/docs/reference/salesforcer.html index bf014e42..b57ea00f 100644 --- a/docs/reference/salesforcer.html +++ b/docs/reference/salesforcer.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/salesforcer_state.html b/docs/reference/salesforcer_state.html index a78224d2..00e3e5e3 100644 --- a/docs/reference/salesforcer_state.html +++ b/docs/reference/salesforcer_state.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/session_id_available.html b/docs/reference/session_id_available.html index 539f5332..c4cfe392 100644 --- a/docs/reference/session_id_available.html +++ b/docs/reference/session_id_available.html @@ -101,6 +101,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_abort_job_bulk.html b/docs/reference/sf_abort_job_bulk.html index 41322947..f5e8063f 100644 --- a/docs/reference/sf_abort_job_bulk.html +++ b/docs/reference/sf_abort_job_bulk.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_access_token.html b/docs/reference/sf_access_token.html index e20d516c..4a0edd82 100644 --- a/docs/reference/sf_access_token.html +++ b/docs/reference/sf_access_token.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_auth.html b/docs/reference/sf_auth.html index e21e18d4..610f680f 100644 --- a/docs/reference/sf_auth.html +++ b/docs/reference/sf_auth.html @@ -105,6 +105,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_auth_check.html b/docs/reference/sf_auth_check.html index dc5623d0..f09a4af3 100644 --- a/docs/reference/sf_auth_check.html +++ b/docs/reference/sf_auth_check.html @@ -105,6 +105,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_auth_refresh.html b/docs/reference/sf_auth_refresh.html index 9ecc181c..df398a13 100644 --- a/docs/reference/sf_auth_refresh.html +++ b/docs/reference/sf_auth_refresh.html @@ -102,6 +102,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_batch_details_bulk.html b/docs/reference/sf_batch_details_bulk.html index 72943d82..b8b35867 100644 --- a/docs/reference/sf_batch_details_bulk.html +++ b/docs/reference/sf_batch_details_bulk.html @@ -101,6 +101,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_batch_status_bulk.html b/docs/reference/sf_batch_status_bulk.html index 959546ec..d3485a31 100644 --- a/docs/reference/sf_batch_status_bulk.html +++ b/docs/reference/sf_batch_status_bulk.html @@ -101,6 +101,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_bulk_operation.html b/docs/reference/sf_bulk_operation.html index 4cf5c22f..0b5623f3 100644 --- a/docs/reference/sf_bulk_operation.html +++ b/docs/reference/sf_bulk_operation.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -161,8 +164,8 @@

    Run Bulk Operation

    sf_bulk_operation(input_data, object_name, operation = c("insert",
       "delete", "upsert", "update", "hardDelete"),
       external_id_fieldname = NULL, api_type = c("Bulk 1.0", "Bulk 2.0"),
    -  ..., wait_for_results = TRUE, interval_seconds = 3,
    -  max_attempts = 200, verbose = FALSE)
    + wait_for_results = TRUE, interval_seconds = 3, max_attempts = 200, + control = list(...), ..., verbose = FALSE)

    Arguments

    @@ -191,11 +194,6 @@

    Arg

    - - - - @@ -211,6 +209,17 @@

    Arg

    + + + + + + + + diff --git a/docs/reference/sf_close_job_bulk.html b/docs/reference/sf_close_job_bulk.html index 175e248b..49fd9952 100644 --- a/docs/reference/sf_close_job_bulk.html +++ b/docs/reference/sf_close_job_bulk.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_control.html b/docs/reference/sf_control.html new file mode 100644 index 00000000..47aa1670 --- /dev/null +++ b/docs/reference/sf_control.html @@ -0,0 +1,406 @@ + + + + + + + + +Auxiliary for Controlling Calls to Salesforce APIs — sf_control • salesforcer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + +
    + +
    +
    + + +
    + +

    Typically only used internally by functions when control parameters are passed +through via dots (...), but it can be called directly to control the behavior +of API calls. This function behaves exactly like glm.control +for the glm function.

    + +
    + +
    sf_control(AllOrNoneHeader = list(allOrNone = FALSE),
    +  AllowFieldTruncationHeader = list(allowFieldTruncation = FALSE),
    +  AssignmentRuleHeader = list(useDefaultRule = TRUE),
    +  DisableFeedTrackingHeader = list(disableFeedTracking = FALSE),
    +  DuplicateRuleHeader = list(allowSave = FALSE, includeRecordDetails =
    +  FALSE, runAsCurrentUser = TRUE),
    +  EmailHeader = list(triggerAutoResponseEmail = FALSE, triggerOtherEmail
    +  = FALSE, triggerUserEmail = TRUE), LocaleOptions = list(language =
    +  "en_US"), MruHeader = list(updateMru = FALSE),
    +  OwnerChangeOptions = list(options = list(list(execute = TRUE, type =
    +  "EnforceNewOwnerHasReadAccess"), list(execute = FALSE, type =
    +  "KeepAccountTeam"), list(execute = FALSE, type = "KeepSalesTeam"),
    +  list(execute = FALSE, type =
    +  "KeepSalesTeamGrantCurrentOwnerReadWriteAccess"), list(execute = FALSE,
    +  type = "SendEmail"), list(execute = FALSE, type =
    +  "TransferAllOwnedCases"), list(execute = TRUE, type =
    +  "TransferContacts"), list(execute = TRUE, type = "TransferContracts"),
    +  list(execute = FALSE, type = "TransferNotesAndAttachments"),
    +  list(execute = TRUE, type = "TransferOpenActivities"), list(execute =
    +  TRUE, type = "TransferOrders"), list(execute = FALSE, type =
    +  "TransferOtherOpenOpportunities"), list(execute = FALSE, type =
    +  "TransferOwnedClosedOpportunities"), list(execute = FALSE, type =
    +  "TransferOwnedOpenCases"), list(execute = FALSE, type =
    +  "TransferOwnedOpenOpportunities"))), QueryOptions = list(batchSize =
    +  1000), UserTerritoryDeleteHeader = list(transferToUserId = NA),
    +  BatchRetryHeader = list(`Sforce-Disable-Batch-Retry` = FALSE),
    +  LineEndingHeader = list(`Sforce-Line-Ending` = NA),
    +  PKChunkingHeader = list(`Sforce-Enable-PKChunking` = FALSE),
    +  api_type = NULL, operation = NULL)
    + +

    Arguments

    +
    api_type

    character; one of "REST", "SOAP", "Bulk 1.0", "Bulk 2.0", or "Chatter" indicating which API to use when making the request

    ...

    other arguments passed on to sf_create_job_bulk such as -content_type, concurrency_mode, line_ending or column_delimiter.

    wait_for_resultsmax_attempts

    integer; defines then max number attempts to check for job completion before stopping

    control

    list; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for sf_control

    ...

    other arguments passed on to sf_control or sf_create_job_bulk +such as content_type, concurrency_mode, or column_delimiter

    verbose
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AllOrNoneHeader

    list; containing the allOrNone element with +a value of TRUE or FALSE. This control specifies whether a call rolls back all changes +unless all records are processed successfully. This control is available in +SOAP, REST, and Metadata APIs for the following functions: sf_create, +sf_delete, sf_update, sf_upsert, sf_create_metadata, +sf_delete_metadata, sf_update_metadata, sf_upsert_metadata. +For more information, read the Salesforce documentation +here.

    AllowFieldTruncationHeader

    list; containing the allowFieldTruncation +element with a value of TRUE or FALSE. This control specifies the truncation behavior +for some field types in SOAP API version 15.0 and later for the following functions: +sf_create, sf_update, sf_upsert. For +more information, read the Salesforce documentation +here.

    AssignmentRuleHeader

    list; containing the useDefaultRule +element with a value of TRUE or FALSE or the assignmentRuleId element. +This control specifies the assignment rule to use when creating or updating an +Account, Case, or Lead for the following functions: sf_create, +sf_update, sf_upsert. For more information, read the Salesforce documentation +here.

    DisableFeedTrackingHeader

    list; containing the disableFeedTracking +element with a value of TRUE or FALSE. This control specifies whether +the changes made in the current call are tracked in feeds for SOAP API calls made +with the following functions: sf_create, sf_delete, +sf_update, sf_upsert. For more information, read the Salesforce documentation +here.

    DuplicateRuleHeader

    list; containing the allowSave, +includeRecordDetails, and runAsCurrentUser elements each with a +value of TRUE or FALSE. This control specifies how duplicate rules should be applied +when using the following functions: sf_create, sf_update, +sf_upsert. For more information, read the Salesforce documentation +here.

    EmailHeader

    list; containing the triggerAutoResponseEmail, +triggerOtherEmail, and triggerUserEmail elements each with a +value of TRUE or FALSE. This control determines if an email notification should be sent +when a request is processed by SOAP API calls made with the following functions: +sf_create, sf_delete, sf_update, sf_upsert, +sf_reset_password. For more information, read the Salesforce documentation +here.

    LocaleOptions

    list; containing the language element. This control +specifies the language of the labels returned by the sf_describe_objects +function using the SOAP API. The value must be a valid user locale (language and country), such as +de_DE or en_GB. For more information, read the Salesforce documentation +here. +The list of valid user locales is available +here.

    MruHeader

    list; containing the updateMru element with a value +of TRUE or FALSE. This control indicates whether to update the list +of most recently used items (TRUE) or not (FALSE) in the Recent Items +section of the sidebar in the Salesforce user interface. This works for SOAP API calls +made with the following functions: sf_create, sf_update, +sf_upsert, sf_retrieve, sf_query. For more +information, read the Salesforce documentation +here.

    OwnerChangeOptions

    list; containing the options element. +This control specifies the details of ownership of attachments and notes when a +record’s owner is changed. This works for SOAP API calls made with the following functions: +sf_update, sf_upsert. For more information, read the Salesforce documentation +here.

    QueryOptions

    list; containing the batchSize element. +This control specifies the batch size for query results . This works for SOAP or +REST API calls made with the following functions: sf_query, +sf_retrieve. For more information, read the Salesforce documentation +here.

    UserTerritoryDeleteHeader

    list; containing the transferToUserId element. +This control specifies a user to whom open opportunities are assigned when the current +owner is removed from a territory. This works for the sf_delete function +using the SOAP API. For more information, read the Salesforce documentation +here.

    BatchRetryHeader

    list; containing the Sforce-Disable-Batch-Retry element. +When you create a bulk job, the Batch Retry control lets you disable retries +for unfinished batches included in the job. This works for most operations run through +the Bulk 1.0 API (e.g. sf_create(., api_type = "Bulk 1.0")) or creating +a Bulk 1.0 job with sf_create_job_bulk. For more information, read the Salesforce documentation +here.

    LineEndingHeader

    list; containing the Sforce-Line-Ending element. +When you’re creating a bulk upload job, the Line Ending control lets you +specify whether line endings are read as line feeds (LFs) or as carriage returns +and line feeds (CRLFs) for fields of type Text Area and Text Area (Long). This +works for most operations run through the Bulk APIs or creating a Bulk 1.0 +job with sf_create_job_bulk. For more information, read the +Salesforce documentation +here.

    PKChunkingHeader

    list; containing the Sforce-Enable-PKChunking element. +Use the PK Chunking control to enable automatic primary key (PK) chunking +for a bulk query job. This works for queries run through the Bulk 1.0 API either via +sf_query(., api_type = "Bulk 1.0")) or sf_query_bulk. For +more information, read the Salesforce documentation +here.

    api_type

    character; one of "REST", "SOAP", "Bulk 1.0", "Bulk 2.0", or +"Chatter" indicating which API to use when making the request

    operation

    character; a string defining the type of operation being +performed (e.g. "insert", "update", "upsert", "delete")

    + + +

    Examples

    +
    # NOT RUN {
    +this_control <- sf_control(DuplicateRuleHeader=list(allowSave=TRUE,
    +                                                    includeRecordDetails=FALSE,
    +                                                    runAsCurrentUser=TRUE))
    +new_contact <- c(FirstName = "Test", LastName = "Contact-Create")
    +new_record <- sf_create(new_contact, "Contact", control = this_control)
    +
    +# specifying the controls directly and are picked up by dots
    +new_record <- sf_create(new_contact, "Contact",
    +                        DuplicateRuleHeader = list(allowSave=TRUE,
    +                                                   includeRecordDetails=FALSE,
    +                                                   runAsCurrentUser=TRUE))
    +# }
    + + + + +
    + + +
    +

    Site built with pkgdown.

    +
    + +
    + + + + + + + + + + diff --git a/docs/reference/sf_create.html b/docs/reference/sf_create.html index 378af772..675a3866 100644 --- a/docs/reference/sf_create.html +++ b/docs/reference/sf_create.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -158,9 +161,8 @@

    Create Records

    -
    sf_create(input_data, object_name, all_or_none = FALSE,
    -  api_type = c("SOAP", "REST", "Bulk 1.0", "Bulk 2.0"), ...,
    -  verbose = FALSE)
    +
    sf_create(input_data, object_name, api_type = c("SOAP", "REST",
    +  "Bulk 1.0", "Bulk 2.0"), control = list(...), ..., verbose = FALSE)

    Arguments

    @@ -174,20 +176,22 @@

    Arg

    - - - - + + + + - + @@ -199,14 +203,28 @@

    Value

    tbl_df of records with success indicator

    +

    Note

    + +

    Because the SOAP and REST calls chunk data into batches of 200 records +the AllOrNoneHeader will only apply to the success or failure of every batch +of records and not all records submitted to the function.

    +

    Examples

    # NOT RUN {
    -n <- 3
    +n <- 2
     new_contacts <- tibble(FirstName = rep("Test", n),
                            LastName = paste0("Contact", 1:n))
     new_contacts_result <- sf_create(new_contacts, object_name="Contact")
    -new_contacts_result <- sf_create(new_contacts, object_name="Contact", api_type="REST")
    +
    +# add the control to allow the creation of records that might violate a duplicate rule
    +new_contacts_result <- sf_create(new_contacts, object_name="Contact",
    +                                 DuplicateRuleHeader = list(allowSave = TRUE,
    +                                                            includeRecordDetails = FALSE,
    +                                                            runAsCurrentUser = TRUE))
    +
    +# an example of how to specify using the Bulk 1.0 API to insert the records                                                           
    +new_contacts_result <- sf_create(new_contacts, object_name="Contact", api_type="Bulk 1.0")
     # }
    -
    sf_create_bulk_v1(input_data, object_name, all_or_none = FALSE, ...,
    -  verbose = FALSE)
    +
    sf_create_bulk_v1(input_data, object_name, control, ..., verbose = FALSE)

    Note

    diff --git a/docs/reference/sf_create_bulk_v2.html b/docs/reference/sf_create_bulk_v2.html index f6dbcd8c..307f4bc5 100644 --- a/docs/reference/sf_create_bulk_v2.html +++ b/docs/reference/sf_create_bulk_v2.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -158,8 +161,7 @@

    Create Records using Bulk 2.0 API

    -
    sf_create_bulk_v2(input_data, object_name, all_or_none = FALSE, ...,
    -  verbose = FALSE)
    +
    sf_create_bulk_v2(input_data, object_name, control, ..., verbose = FALSE)

    Note

    diff --git a/docs/reference/sf_create_job_bulk.html b/docs/reference/sf_create_job_bulk.html index a61e025d..9f06e7f8 100644 --- a/docs/reference/sf_create_job_bulk.html +++ b/docs/reference/sf_create_job_bulk.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -159,11 +162,12 @@

    Create Bulk API Job

    sf_create_job_bulk(operation = c("insert", "delete", "upsert", "update",
    -  "hardDelete", "query"), object_name, external_id_fieldname = NULL,
    -  api_type = c("Bulk 1.0", "Bulk 2.0"), content_type = c("CSV",
    -  "ZIP_CSV", "ZIP_XML", "ZIP_JSON"), concurrency_mode = c("Parallel",
    -  "Serial"), line_ending = NULL, column_delimiter = c("COMMA", "TAB",
    -  "PIPE", "SEMICOLON", "CARET", "BACKQUOTE"), verbose = FALSE)
    + "hardDelete", "query", "queryall"), object_name, + external_id_fieldname=NULL, api_type=c("Bulk 1.0", "Bulk 2.0"), + content_type=c("CSV", "ZIP_CSV", "ZIP_XML", "ZIP_JSON"), + concurrency_mode=c("Parallel", "Serial"), + column_delimiter=c("COMMA", "TAB", "PIPE", "SEMICOLON", "CARET", + "BACKQUOTE"), control=list(...), ..., verbose=FALSE)

    Arguments

    object_name

    character; the name of one Salesforce objects that the function is operating against (e.g. "Account", "Contact", "CustomObject__c")

    all_or_none

    logical; allows a call to roll back all changes unless all -records are processed successfully

    api_type

    character; one of "REST", "SOAP", "Bulk 1.0", "Bulk 2.0", or "Chatter" indicating which API to use when making the request

    control

    list; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for sf_control

    ...

    other arguments passed on to sf_bulk_operation.

    arguments passed to sf_control or further downstream +to sf_bulk_operation

    verbose
    @@ -192,7 +196,8 @@

    Arg

    +indicate the type of data being passed to the Bulk APIs. For the Bulk 2.0 API the only +valid value (and the default) is 'CSV'.

    @@ -200,14 +205,6 @@

    Arg whether batches should be completed sequentially or in parallel. Use "Serial" only if lock contentions persist with in "Parallel" mode. Note: this argument is only used in the Bulk 1.0 API and will be ignored in calls using the Bulk 2.0 API.

    -

    - - - @@ -217,6 +214,16 @@

    Arg note that this argument is only used in the Bulk 2.0 API and will be ignored in calls using the Bulk 1.0 API.

    + + + + + + + + diff --git a/docs/reference/sf_create_job_bulk_v1.html b/docs/reference/sf_create_job_bulk_v1.html index 4be7ab8f..9bfc76b2 100644 --- a/docs/reference/sf_create_job_bulk_v1.html +++ b/docs/reference/sf_create_job_bulk_v1.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -159,10 +162,10 @@

    Create Job using Bulk 1.0 API

    sf_create_job_bulk_v1(operation = c("insert", "delete", "upsert",
    -  "update", "hardDelete", "query"), object_name,
    +  "update", "hardDelete", "query", "queryall"), object_name,
       external_id_fieldname = NULL, content_type = c("CSV", "ZIP_CSV",
       "ZIP_XML", "ZIP_JSON"), concurrency_mode = c("Parallel", "Serial"),
    -  verbose = FALSE)
    + control, ..., verbose=FALSE)

    Note

    diff --git a/docs/reference/sf_create_job_bulk_v2.html b/docs/reference/sf_create_job_bulk_v2.html index 78c80330..c2cf64fb 100644 --- a/docs/reference/sf_create_job_bulk_v2.html +++ b/docs/reference/sf_create_job_bulk_v2.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -160,9 +163,8 @@

    Create Job using Bulk 2.0 API

    sf_create_job_bulk_v2(operation = c("insert", "delete", "upsert",
       "update"), object_name, external_id_fieldname = NULL,
    -  content_type = "CSV", line_ending = NULL,
    -  column_delimiter = c("COMMA", "TAB", "PIPE", "SEMICOLON", "CARET",
    -  "BACKQUOTE"), verbose = FALSE)
    + content_type="CSV", column_delimiter=c("COMMA", "TAB", "PIPE", + "SEMICOLON", "CARET", "BACKQUOTE"), control, ..., verbose=FALSE)

    Note

    diff --git a/docs/reference/sf_create_metadata.html b/docs/reference/sf_create_metadata.html index bc66a768..6779edaf 100644 --- a/docs/reference/sf_create_metadata.html +++ b/docs/reference/sf_create_metadata.html @@ -101,6 +101,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -160,7 +163,7 @@

    Create Object or Field Metadata in Salesforce

    -
    sf_create_metadata(metadata_type, metadata, all_or_none = FALSE,
    +    
    sf_create_metadata(metadata_type, metadata, control = list(...), ...,
       verbose = FALSE)

    Arguments

    @@ -176,9 +179,14 @@

    Arg XML before being sent via API

    - - + + + + + + @@ -225,8 +233,9 @@

    Examp custom_metadata$deploymentStatus <- 'Deployed' # make a description to identify this easily in the UI setup tab custom_metadata$description <- 'created by the Metadata API' -new_custom_object <- sf_create_metadata(metadata_type='CustomObject', - metadata=custom_metadata, verbose=TRUE) +new_custom_object <- sf_create_metadata(metadata_type = 'CustomObject', + metadata = custom_metadata, + verbose = TRUE) # adding custom fields to our object # input formatted as a list diff --git a/docs/reference/sf_create_rest.html b/docs/reference/sf_create_rest.html index 064558a1..62de446b 100644 --- a/docs/reference/sf_create_rest.html +++ b/docs/reference/sf_create_rest.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -158,8 +161,7 @@

    Create Records using REST API

    -
    sf_create_rest(input_data, object_name, all_or_none = FALSE,
    -  verbose = FALSE)
    +
    sf_create_rest(input_data, object_name, control, ..., verbose = FALSE)

    Note

    diff --git a/docs/reference/sf_create_soap.html b/docs/reference/sf_create_soap.html index 3da58c0b..a690268b 100644 --- a/docs/reference/sf_create_soap.html +++ b/docs/reference/sf_create_soap.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -158,8 +161,7 @@

    Create Records using SOAP API

    -
    sf_create_soap(input_data, object_name, all_or_none = FALSE,
    -  verbose = FALSE)
    +
    sf_create_soap(input_data, object_name, control, ..., verbose = FALSE)

    Note

    diff --git a/docs/reference/sf_delete.html b/docs/reference/sf_delete.html index 3784ca7b..29da758b 100644 --- a/docs/reference/sf_delete.html +++ b/docs/reference/sf_delete.html @@ -38,7 +38,7 @@ - + @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -154,12 +157,12 @@

    Delete Records

    -

    Deletes one or more records to your organization’s data.

    +

    Deletes one or more records from your organization’s data.

    -
    sf_delete(ids, object_name, all_or_none = FALSE, api_type = c("REST",
    -  "SOAP", "Bulk 1.0", "Bulk 2.0"), ..., verbose = FALSE)
    +
    sf_delete(ids, object_name, api_type = c("REST", "SOAP", "Bulk 1.0",
    +  "Bulk 2.0"), control = list(...), ..., verbose = FALSE)

    Arguments

    content_type

    character; being one of 'CSV', 'ZIP_CSV', 'ZIP_XML', or 'ZIP_JSON' to -indicate the type of data being passed to the Bulk API.

    concurrency_mode
    line_ending

    character; indicating the line ending used for CSV job data, -marking the end of a data row. The default is NULL meaing that the line ending -is determined by the operating system using "CRLF" for Windows machines and -"LF" for Unix machines. Note: this argument is only used in the Bulk 2.0 API -and will be ignored in calls using the Bulk 1.0 API.

    column_delimiter
    control

    list; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for sf_control

    ...

    arguments passed to sf_control

    verbose

    logical; do you want informative messages?

    all_or_none

    logical; allows a call to roll back all changes unless all -records are processed successfully

    control

    list; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for sf_control

    ...

    arguments passed to sf_control

    verbose
    @@ -174,20 +177,22 @@

    Arg

    - - - - + + + + - + @@ -199,20 +204,24 @@

    Value

    tbl_df of records with success indicator

    +

    Note

    + +

    Because the SOAP and REST calls chunk data into batches of 200 records +the AllOrNoneHeader will only apply to the success or failure of every batch +of records and not all records submitted to the function.

    +

    Examples

    # NOT RUN {
     n <- 3
     new_contacts <- tibble(FirstName = rep("Test", n),
                            LastName = paste0("Contact", 1:n))
    -new_contacts_result1 <- sf_create(new_contacts, object_name="Contact")
    -deleted_contacts_result1 <- sf_delete(new_contacts_result1$id,
    -                                      object_name="Contact")
    -
    -new_contacts_result2 <- sf_create(new_contacts, "Contact")
    -deleted_contacts_result2 <- sf_delete(new_contacts_result2$id,
    -                                      object_name="Contact",
    -                                      api_type="Bulk")
    +new_records <- sf_create(new_contacts, object_name="Contact")
    +deleted_first <- sf_delete(new_records$id[1], object_name = "Contact")
    +
    +# add the control to do an "All or None" deletion of the remaining records
    +deleted_rest <- sf_delete(new_records$id[2:3], object_name = "Contact",
    +                          AllOrNoneHeader = list(allOrNone = TRUE))
     # }
    -
    sf_delete_metadata(metadata_type, object_names, all_or_none = FALSE,
    +    
    sf_delete_metadata(metadata_type, object_names, control = list(...), ...,
       verbose = FALSE)

    Arguments

    @@ -173,9 +176,14 @@

    Arg

    - - + + + + + + diff --git a/docs/reference/sf_describe_metadata.html b/docs/reference/sf_describe_metadata.html index c94eda2f..ecd41049 100644 --- a/docs/reference/sf_describe_metadata.html +++ b/docs/reference/sf_describe_metadata.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_describe_object_fields.html b/docs/reference/sf_describe_object_fields.html index 929f8238..3fcbf1ec 100644 --- a/docs/reference/sf_describe_object_fields.html +++ b/docs/reference/sf_describe_object_fields.html @@ -101,6 +101,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_describe_objects.html b/docs/reference/sf_describe_objects.html index 199af9ac..1122ceb3 100644 --- a/docs/reference/sf_describe_objects.html +++ b/docs/reference/sf_describe_objects.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -158,8 +161,8 @@

    SObject Basic Information

    -
    sf_describe_objects(object_names, api_type = c("SOAP", "REST", "Bulk"),
    -  verbose = FALSE)
    +
    sf_describe_objects(object_names, api_type = c("SOAP", "REST"),
    +  control = list(...), ..., verbose = FALSE)

    Arguments

    object_name

    character; the name of one Salesforce objects that the function is operating against (e.g. "Account", "Contact", "CustomObject__c")

    all_or_none

    logical; allows a call to roll back all changes unless all -records are processed successfully

    api_type

    character; one of "REST", "SOAP", "Bulk 1.0", "Bulk 2.0", or "Chatter" indicating which API to use when making the request

    control

    list; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for sf_control

    ...

    other arguments passed on to sf_bulk_operation.

    arguments passed to sf_control or further downstream +to sf_bulk_operation

    verbose

    a character vector of names that we wish to read metadata for

    all_or_none

    logical; allows a call to roll back all changes unless all -records are processed successfully

    control

    list; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for sf_control

    ...

    arguments passed to sf_control

    verbose
    @@ -174,6 +177,16 @@

    Arg

    + + + + + + + + diff --git a/docs/reference/sf_download_attachment.html b/docs/reference/sf_download_attachment.html index 6a68c2e8..2f95f29f 100644 --- a/docs/reference/sf_download_attachment.html +++ b/docs/reference/sf_download_attachment.html @@ -101,6 +101,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_end_job_bulk.html b/docs/reference/sf_end_job_bulk.html index 2dca843a..c2aaaa54 100644 --- a/docs/reference/sf_end_job_bulk.html +++ b/docs/reference/sf_end_job_bulk.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_find_duplicates.html b/docs/reference/sf_find_duplicates.html index 848c800e..db856027 100644 --- a/docs/reference/sf_find_duplicates.html +++ b/docs/reference/sf_find_duplicates.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_find_duplicates_by_id.html b/docs/reference/sf_find_duplicates_by_id.html index 12ea4060..75f9ae3c 100644 --- a/docs/reference/sf_find_duplicates_by_id.html +++ b/docs/reference/sf_find_duplicates_by_id.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_get_all_jobs_bulk.html b/docs/reference/sf_get_all_jobs_bulk.html index 9b269a9f..6a26e217 100644 --- a/docs/reference/sf_get_all_jobs_bulk.html +++ b/docs/reference/sf_get_all_jobs_bulk.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_get_job_bulk.html b/docs/reference/sf_get_job_bulk.html index 5739fa7a..9ddc1eca 100644 --- a/docs/reference/sf_get_job_bulk.html +++ b/docs/reference/sf_get_job_bulk.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_get_job_records_bulk.html b/docs/reference/sf_get_job_records_bulk.html index 0ed75cda..ab18dd0b 100644 --- a/docs/reference/sf_get_job_records_bulk.html +++ b/docs/reference/sf_get_job_records_bulk.html @@ -101,6 +101,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_input_data_validation.html b/docs/reference/sf_input_data_validation.html index 242e60d4..5774925e 100644 --- a/docs/reference/sf_input_data_validation.html +++ b/docs/reference/sf_input_data_validation.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_job_batches_bulk.html b/docs/reference/sf_job_batches_bulk.html index 801f8afb..b6d7e3ea 100644 --- a/docs/reference/sf_job_batches_bulk.html +++ b/docs/reference/sf_job_batches_bulk.html @@ -101,6 +101,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_list_api_limits.html b/docs/reference/sf_list_api_limits.html index ac655134..e787af87 100644 --- a/docs/reference/sf_list_api_limits.html +++ b/docs/reference/sf_list_api_limits.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_list_metadata.html b/docs/reference/sf_list_metadata.html index 4de323e7..6dc3a8bf 100644 --- a/docs/reference/sf_list_metadata.html +++ b/docs/reference/sf_list_metadata.html @@ -101,6 +101,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_list_objects.html b/docs/reference/sf_list_objects.html index 36873567..87df037f 100644 --- a/docs/reference/sf_list_objects.html +++ b/docs/reference/sf_list_objects.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_list_resources.html b/docs/reference/sf_list_resources.html index 75d63324..2298167c 100644 --- a/docs/reference/sf_list_resources.html +++ b/docs/reference/sf_list_resources.html @@ -101,6 +101,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_list_rest_api_versions.html b/docs/reference/sf_list_rest_api_versions.html index 1bfebde7..d6f2edaa 100644 --- a/docs/reference/sf_list_rest_api_versions.html +++ b/docs/reference/sf_list_rest_api_versions.html @@ -101,6 +101,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_query.html b/docs/reference/sf_query.html index 644c7575..c5fdddc0 100644 --- a/docs/reference/sf_query.html +++ b/docs/reference/sf_query.html @@ -101,6 +101,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -160,9 +163,9 @@

    Perform SOQL Query

    -
    sf_query(soql, object_name, guess_types = TRUE, queryall = FALSE,
    -  page_size = 1000, api_type = c("REST", "SOAP", "Bulk 1.0"),
    -  next_records_url = NULL, ..., verbose = FALSE)
    +
    sf_query(soql, object_name, queryall = FALSE, guess_types = TRUE,
    +  api_type = c("REST", "SOAP", "Bulk 1.0"), control = list(...), ...,
    +  next_records_url = NULL, verbose = FALSE)

    Arguments

    character; one of "REST", "SOAP", "Bulk 1.0", "Bulk 2.0", or "Chatter" indicating which API to use when making the request

    control

    list; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for sf_control

    ...

    arguments passed to sf_control

    verbose

    logical; do you want informative messages?

    @@ -175,6 +178,13 @@

    Arg

    + + + + @@ -183,30 +193,26 @@

    Arg and FALSE returns all values as character strings.

    - - + + - - + + - - + + - - - - @@ -236,7 +242,7 @@

    R

    Examples

    # NOT RUN {
    -sf_query("SELECT Id, Account.Name, Email FROM Contact LIMIT 10", verbose = TRUE)
    +sf_query("SELECT Id, Account.Name, Email FROM Contact LIMIT 10")
     # }
    -
    sf_query_bulk(soql, object_name, guess_types = TRUE,
    -  api_type = c("Bulk 1.0"), interval_seconds = 5, max_attempts = 100,
    -  verbose = FALSE)
    +
    sf_query_bulk(soql, object_name = NULL, queryall = FALSE,
    +  guess_types = TRUE, api_type = c("Bulk 1.0"), interval_seconds = 5,
    +  max_attempts = 100, control = list(...), ..., verbose = FALSE)

    Arguments

    object_name

    character; the name of one Salesforce objects that the function is operating against (e.g. "Account", "Contact", "CustomObject__c")

    queryall

    logical; indicating if the query recordset should include records +that have been deleted because of a merge or delete. QueryAll will also return +information about archived Task and Event records. QueryAll is available in API +version 29.0 and later.

    guess_types
    queryall

    logical; indicating if the query recordset should include -deleted and archived records (available only when querying Task and Event records)

    api_type

    character; one of "REST", "SOAP", "Bulk 1.0", "Bulk 2.0", or +"Chatter" indicating which API to use when making the request

    page_size

    numeric; a number between 200 and 2000 indicating the number of -records per page that are returned. Speed benchmarks should be done to better -understand the speed implications of choosing high or low values of this argument.

    control

    list; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for sf_control

    api_type

    character; one of "REST", "SOAP", "Bulk 1.0", "Bulk 2.0", or -"Chatter" indicating which API to use when making the request

    ...

    arguments passed to sf_control or further downstream +to sf_query_bulk.

    next_records_url

    character (leave as NULL); a string used internally by the function to paginate through to more records until complete

    ...

    Other arguments passed on to sf_query_bulk.

    verbose

    logical; do you want informative messages?

    @@ -175,6 +178,13 @@

    Arg

    + + + + @@ -197,6 +207,16 @@

    Arg

    + + + + + + + + @@ -209,13 +229,14 @@

    Value

    References

    -

    https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/

    +

    https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/asynch_api_bulk_query_intro.htm

    Examples

    # NOT RUN {
    -# select all Ids from Account object
    -ids <- sf_query_bulk(soql='SELECT Id FROM Account', object_name='Account')
    +# select all Ids from Account object (up to 1000)
    +ids <- sf_query_bulk(soql = 'SELECT Id FROM Account LIMIT 1000',
    +                     object_name = 'Account')
     # }
    -
    sf_reset_password(user_id, verbose = FALSE)
    +
    sf_reset_password(user_id, control = list(...), ..., verbose = FALSE)

    Arguments

    object_name

    character; the name of one Salesforce objects that the function is operating against (e.g. "Account", "Contact", "CustomObject__c")

    queryall

    logical; indicating if the query recordset should include records +that have been deleted because of a merge or delete. QueryAll will also return +information about archived Task and Event records. QueryAll is available in API +version 29.0 and later.

    guess_types

    integer; defines then max number attempts to check for job completion before stopping

    control

    list; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for sf_control

    ...

    arguments passed to sf_control

    verbose

    logical; do you want informative messages?

    @@ -167,6 +170,16 @@

    Arg

    + + + + + + + + @@ -180,7 +193,11 @@

    Value

    Examples

    # NOT RUN {
    -sf_reset_password(user_id = "0056A000000ZZZaaBBB")
    +# reset a user's password and ensure that an email is triggered to them
    +sf_reset_password(user_id = "0056A000000ZZZaaBBB",
    +                  EmailHeader = list(triggerAutoResponseEmail = FALSE,
    +                                     triggerOtherEmail = FALSE,
    +                                     triggerUserEmail = TRUE))
     # }
    sf_retrieve(ids, fields, object_name, api_type = c("REST", "SOAP",
    -  "Bulk 1.0", "Bulk 2.0"), verbose = FALSE)
    + "Bulk 1.0", "Bulk 2.0"), control=list(...), ..., verbose=FALSE)

    Arguments

    user_id

    character; the unique Salesforce Id assigned to the User

    control

    list; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for sf_control

    ...

    arguments passed to sf_control

    verbose

    logical; do you want informative messages?

    @@ -185,6 +188,16 @@

    Arg

    + + + + + + + + diff --git a/docs/reference/sf_retrieve_metadata.html b/docs/reference/sf_retrieve_metadata.html index e3abc984..84019313 100644 --- a/docs/reference/sf_retrieve_metadata.html +++ b/docs/reference/sf_retrieve_metadata.html @@ -102,6 +102,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_retrieve_metadata_check_status.html b/docs/reference/sf_retrieve_metadata_check_status.html index 4a3eb546..5d94c836 100644 --- a/docs/reference/sf_retrieve_metadata_check_status.html +++ b/docs/reference/sf_retrieve_metadata_check_status.html @@ -101,6 +101,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_search.html b/docs/reference/sf_search.html index 7e6c7dc8..9e1d679a 100644 --- a/docs/reference/sf_search.html +++ b/docs/reference/sf_search.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -189,7 +192,7 @@

    Arg

    +controlling the search if not using SOSL. If using SOSL this argument is ignored.

    diff --git a/docs/reference/sf_server_timestamp.html b/docs/reference/sf_server_timestamp.html index 6d6873f8..cd3305f4 100644 --- a/docs/reference/sf_server_timestamp.html +++ b/docs/reference/sf_server_timestamp.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_session_id.html b/docs/reference/sf_session_id.html index 80d7a562..20291be0 100644 --- a/docs/reference/sf_session_id.html +++ b/docs/reference/sf_session_id.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_set_password.html b/docs/reference/sf_set_password.html index 27bb4ffa..f4e00235 100644 --- a/docs/reference/sf_set_password.html +++ b/docs/reference/sf_set_password.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_submit_query_bulk.html b/docs/reference/sf_submit_query_bulk.html index f1d934ad..1d464b37 100644 --- a/docs/reference/sf_submit_query_bulk.html +++ b/docs/reference/sf_submit_query_bulk.html @@ -101,6 +101,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -209,9 +212,9 @@

    R

    Examples

    # NOT RUN {
    -my_query <- "SELECT Id, Name FROM Account LIMIT 10"
    -job_info <- sf_create_job_bulk(operation='query', object='Account')
    -query_info <- sf_submit_query_bulk(job_id=job_info$id, soql=my_query)
    +my_query <- "SELECT Id, Name FROM Account LIMIT 1000"
    +job_info <- sf_create_job_bulk(operation = 'query', object = 'Account')
    +query_info <- sf_submit_query_bulk(job_id = job_info$id, soql = my_query)
     # }
    -
    sf_update(input_data, object_name, all_or_none = FALSE,
    -  api_type = c("SOAP", "REST", "Bulk 1.0", "Bulk 2.0"), ...,
    -  verbose = FALSE)
    +
    sf_update(input_data, object_name, api_type = c("SOAP", "REST",
    +  "Bulk 1.0", "Bulk 2.0"), control = list(...), ..., verbose = FALSE)

    Arguments

    character; one of "REST", "SOAP", "Bulk 1.0", "Bulk 2.0", or "Chatter" indicating which API to use when making the request

    control

    list; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for sf_control

    ...

    arguments passed to sf_control

    verbose

    logical; do you want informative messages?

    parameterized_search_options

    list; a list of parameters for -controlling the search if not using SOSL. If using SOSL this is ignored.

    verbose
    @@ -174,20 +176,22 @@

    Arg

    - - - - + + + + - + @@ -199,20 +203,34 @@

    Value

    tbl_df of records with success indicator

    +

    Note

    + +

    Because the SOAP and REST calls chunk data into batches of 200 records +the AllOrNoneHeader will only apply to the success or failure of every batch +of records and not all records submitted to the function.

    +

    Examples

    # NOT RUN {
    -n <- 3
    -new_contacts <- tibble(FirstName = rep("Test", n),
    -                       LastName = paste0("Contact", 1:n))
    -new_contacts_result <- sf_create(new_contacts, "Contact")
    -
    -update_contacts <- tibble(FirstName = rep("TestTest", n),
    -                          LastName = paste0("Contact", 1:n),
    -                          Id = new_contacts_result$id)
    -updated_contacts_result1 <- sf_update(update_contacts, "Contact")
    -updated_contacts_result2 <- sf_update(update_contacts, "Contact",
    -                                      api_type="Bulk")
    +n <- 2
    +new_accts <- tibble(FirstName = rep("Test", n),
    +                       LastName = paste0("Account", 1:n))
    +new_records <- sf_create(new_accts, "Account")
    +
    +updated_accts <- tibble(FirstName = rep("TestTest", n),
    +                        LastName = paste0("Account", 1:n),
    +                        Id = new_records$id)
    +
    +# update the accounts and ensure that all contacts and cases (open and closed) 
    +# owned by the previous account owner are transferred to the new owner                         
    +update <- sf_update(updated_accts, "Account",
    +                    OwnerChangeOptions=list(options=
    +                                              list(list(execute=TRUE,
    +                                                        type="TransferAllOwnedCases"),
    +                                                   list(execute=TRUE,
    +                                                        type="TransferOwnedOpenCases"),
    +                                                   list(execute=TRUE,
    +                                                        type="TransferContacts"))))
     # }
    -
    sf_update_bulk_v1(input_data, object_name, all_or_none = FALSE, ...,
    -  verbose = FALSE)
    +
    sf_update_bulk_v1(input_data, object_name, control, ..., verbose = FALSE)

    Note

    diff --git a/docs/reference/sf_update_bulk_v2.html b/docs/reference/sf_update_bulk_v2.html index 2b5262b8..843d127a 100644 --- a/docs/reference/sf_update_bulk_v2.html +++ b/docs/reference/sf_update_bulk_v2.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -158,8 +161,7 @@

    Update Records using Bulk 2.0 API

    -
    sf_update_bulk_v2(input_data, object_name, all_or_none = FALSE, ...,
    -  verbose = FALSE)
    +
    sf_update_bulk_v2(input_data, object_name, control, ..., verbose = FALSE)

    Note

    diff --git a/docs/reference/sf_update_metadata.html b/docs/reference/sf_update_metadata.html index 65d4abce..560b1f76 100644 --- a/docs/reference/sf_update_metadata.html +++ b/docs/reference/sf_update_metadata.html @@ -101,6 +101,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -160,7 +163,7 @@

    Update Object or Field Metadata in Salesforce

    -
    sf_update_metadata(metadata_type, metadata, all_or_none = FALSE,
    +    
    sf_update_metadata(metadata_type, metadata, control = list(...), ...,
       verbose = FALSE)

    Arguments

    @@ -176,9 +179,14 @@

    Arg XML before being sent via API

    - - + + + + + + diff --git a/docs/reference/sf_update_rest.html b/docs/reference/sf_update_rest.html index fd4a1c3d..1991b653 100644 --- a/docs/reference/sf_update_rest.html +++ b/docs/reference/sf_update_rest.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -158,8 +161,7 @@

    Update Records using REST API

    -
    sf_update_rest(input_data, object_name, all_or_none = FALSE,
    -  verbose = FALSE)
    +
    sf_update_rest(input_data, object_name, control, ..., verbose = FALSE)

    Note

    diff --git a/docs/reference/sf_update_soap.html b/docs/reference/sf_update_soap.html index e7bd500b..6f5093a1 100644 --- a/docs/reference/sf_update_soap.html +++ b/docs/reference/sf_update_soap.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -158,8 +161,7 @@

    Update Records using SOAP API

    -
    sf_update_soap(input_data, object_name, all_or_none = FALSE,
    -  verbose = FALSE)
    +
    sf_update_soap(input_data, object_name, control, ..., verbose = FALSE)

    Note

    diff --git a/docs/reference/sf_upload_complete_bulk.html b/docs/reference/sf_upload_complete_bulk.html index 08062c22..b6aafe1a 100644 --- a/docs/reference/sf_upload_complete_bulk.html +++ b/docs/reference/sf_upload_complete_bulk.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_upsert.html b/docs/reference/sf_upsert.html index dc91fdef..c949fa57 100644 --- a/docs/reference/sf_upsert.html +++ b/docs/reference/sf_upsert.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -159,8 +162,8 @@

    Upsert Records

    sf_upsert(input_data, object_name, external_id_fieldname,
    -  all_or_none = FALSE, api_type = c("SOAP", "REST", "Bulk 1.0",
    -  "Bulk 2.0"), ..., verbose = FALSE)
    + api_type=c("SOAP", "REST", "Bulk 1.0", "Bulk 2.0"), + control=list(...), ..., verbose=FALSE)

    Arguments

    object_name

    character; the name of one Salesforce objects that the function is operating against (e.g. "Account", "Contact", "CustomObject__c")

    all_or_none

    logical; allows a call to roll back all changes unless all -records are processed successfully

    api_type

    character; one of "REST", "SOAP", "Bulk 1.0", "Bulk 2.0", or "Chatter" indicating which API to use when making the request

    control

    list; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for sf_control

    ...

    other arguments passed on to sf_bulk_operation.

    arguments passed to sf_control or further downstream +to sf_bulk_operation

    verbose
    all_or_none

    logical; allows a call to roll back all changes unless all -records are processed successfully

    control

    list; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for sf_control

    ...

    arguments passed to sf_control

    verbose
    @@ -180,20 +183,22 @@

    Arg

    - - - - + + + + - + diff --git a/docs/reference/sf_upsert_bulk_v1.html b/docs/reference/sf_upsert_bulk_v1.html index 29472696..2e64494b 100644 --- a/docs/reference/sf_upsert_bulk_v1.html +++ b/docs/reference/sf_upsert_bulk_v1.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -158,8 +161,8 @@

    Upsert Records using Bulk 1.0 API

    -
    sf_upsert_bulk_v1(input_data, object_name, external_id_fieldname,
    -  all_or_none = FALSE, ..., verbose = FALSE)
    +
    sf_upsert_bulk_v1(input_data, object_name, external_id_fieldname, control,
    +  ..., verbose = FALSE)

    Note

    diff --git a/docs/reference/sf_upsert_bulk_v2.html b/docs/reference/sf_upsert_bulk_v2.html index 6e56025a..89e0046d 100644 --- a/docs/reference/sf_upsert_bulk_v2.html +++ b/docs/reference/sf_upsert_bulk_v2.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -158,8 +161,8 @@

    Upsert Records using Bulk 2.0 API

    -
    sf_upsert_bulk_v2(input_data, object_name, external_id_fieldname,
    -  all_or_none = FALSE, ..., verbose = FALSE)
    +
    sf_upsert_bulk_v2(input_data, object_name, external_id_fieldname, control,
    +  ..., verbose = FALSE)

    Note

    diff --git a/docs/reference/sf_upsert_metadata.html b/docs/reference/sf_upsert_metadata.html index fb152fcc..f3b7d562 100644 --- a/docs/reference/sf_upsert_metadata.html +++ b/docs/reference/sf_upsert_metadata.html @@ -101,6 +101,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -160,7 +163,7 @@

    Upsert Object or Field Metadata in Salesforce

    -
    sf_upsert_metadata(metadata_type, metadata, all_or_none = FALSE,
    +    
    sf_upsert_metadata(metadata_type, metadata, control = list(...), ...,
       verbose = FALSE)

    Arguments

    @@ -176,9 +179,14 @@

    Arg XML before being sent via API

    - - + + + + + + diff --git a/docs/reference/sf_upsert_rest.html b/docs/reference/sf_upsert_rest.html index 33e18255..a5802e5c 100644 --- a/docs/reference/sf_upsert_rest.html +++ b/docs/reference/sf_upsert_rest.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -158,8 +161,8 @@

    Upsert Records using REST API

    -
    sf_upsert_rest(input_data, object_name, external_id_fieldname,
    -  all_or_none = FALSE, verbose = FALSE)
    +
    sf_upsert_rest(input_data, object_name, external_id_fieldname, control,
    +  ..., verbose = FALSE)

    Note

    diff --git a/docs/reference/sf_upsert_soap.html b/docs/reference/sf_upsert_soap.html index 9746df75..c3f94b53 100644 --- a/docs/reference/sf_upsert_soap.html +++ b/docs/reference/sf_upsert_soap.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • @@ -158,8 +161,8 @@

    Upsert Records using SOAP API

    -
    sf_upsert_soap(input_data, object_name, external_id_fieldname,
    -  all_or_none = FALSE, verbose = FALSE)
    +
    sf_upsert_soap(input_data, object_name, external_id_fieldname, control,
    +  ..., verbose = FALSE)

    Note

    diff --git a/docs/reference/sf_user_info.html b/docs/reference/sf_user_info.html index d98d516b..5a4bc637 100644 --- a/docs/reference/sf_user_info.html +++ b/docs/reference/sf_user_info.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/sf_write_csv.html b/docs/reference/sf_write_csv.html index 91370621..b523bda7 100644 --- a/docs/reference/sf_write_csv.html +++ b/docs/reference/sf_write_csv.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/token_available.html b/docs/reference/token_available.html index 2bc28b34..1aef6521 100644 --- a/docs/reference/token_available.html +++ b/docs/reference/token_available.html @@ -101,6 +101,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/valid_metadata_list.html b/docs/reference/valid_metadata_list.html index a32fccd3..5894bcbd 100644 --- a/docs/reference/valid_metadata_list.html +++ b/docs/reference/valid_metadata_list.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/xmlToList2.html b/docs/reference/xmlToList2.html index 7f9b36f2..8b6bccdc 100644 --- a/docs/reference/xmlToList2.html +++ b/docs/reference/xmlToList2.html @@ -102,6 +102,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/reference/xml_nodeset_to_df.html b/docs/reference/xml_nodeset_to_df.html index 33248522..764ff19e 100644 --- a/docs/reference/xml_nodeset_to_df.html +++ b/docs/reference/xml_nodeset_to_df.html @@ -100,6 +100,9 @@
  • Working with Bulk API
  • +
  • + Passing Control Args +
  • Transitioning from RForcecom
  • diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 6478931d..269ed094 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -6,6 +6,12 @@ https://stevenmmortimer.github.io/salesforcer/reference/VERB_n.html + + https://stevenmmortimer.github.io/salesforcer/reference/accepted_controls_by_api.html + + + https://stevenmmortimer.github.io/salesforcer/reference/accepted_controls_by_operation.html + https://stevenmmortimer.github.io/salesforcer/reference/build_metadata_xml_from_list.html @@ -18,6 +24,9 @@ https://stevenmmortimer.github.io/salesforcer/reference/collapse_list_with_dupe_names.html + + https://stevenmmortimer.github.io/salesforcer/reference/filter_valid_controls.html + https://stevenmmortimer.github.io/salesforcer/reference/get_os.html @@ -117,6 +126,9 @@ https://stevenmmortimer.github.io/salesforcer/reference/remove_empty_linked_object_cols.html + + https://stevenmmortimer.github.io/salesforcer/reference/return_matching_controls.html + https://stevenmmortimer.github.io/salesforcer/reference/rforcecom.bulkAction.html @@ -189,6 +201,9 @@ https://stevenmmortimer.github.io/salesforcer/reference/sf_close_job_bulk.html + + https://stevenmmortimer.github.io/salesforcer/reference/sf_control.html + https://stevenmmortimer.github.io/salesforcer/reference/sf_create.html @@ -381,6 +396,9 @@ https://stevenmmortimer.github.io/salesforcer/articles/getting-started.html + + https://stevenmmortimer.github.io/salesforcer/articles/passing-control-args.html + https://stevenmmortimer.github.io/salesforcer/articles/transitioning-from-RForcecom.html diff --git a/index.Rmd b/index.Rmd index 6f2b6aaa..d9fe3e53 100644 --- a/index.Rmd +++ b/index.Rmd @@ -14,7 +14,8 @@ knitr::opts_chunk$set( [![Build Status](https://travis-ci.org/StevenMMortimer/salesforcer.svg?branch=master)](https://travis-ci.org/StevenMMortimer/salesforcer) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/StevenMMortimer/salesforcer?branch=master&svg=true)](https://ci.appveyor.com/project/StevenMMortimer/salesforcer) -[![CRAN_Status_Badge](http://www.r-pkg.org/badges/version/salesforcer)](http://cran.r-project.org/package=salesforcer) +[![CRAN_Status_Badge](https://www.r-pkg.org/badges/version/salesforcer)](https://cran.r-project.org/package=salesforcer) +[![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/master/graph/badge.svg)](https://codecov.io/gh/StevenMMortimer/salesforcer?branch=master)
    @@ -34,9 +35,12 @@ Package features include: * 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()`) + * 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) @@ -61,6 +65,16 @@ devtools::install_github("StevenMMortimer/salesforcer") If you encounter a clear bug, please file a minimal reproducible example on [GitHub](https://github.com/StevenMMortimer/salesforcer/issues). +## Vignettes + +The README below outlines the package functionality, but review the vignettes for +more detailed examples on usage. + + * [Getting Started](https://StevenMMortimer.github.io/salesforcer/articles/getting-started.html) + * [Working with Bulk API](https://StevenMMortimer.github.io/salesforcer/articles/working-with-bulk-api.html) + * [Transitioning from RForcecom](https://StevenMMortimer.github.io/salesforcer/articles/transitioning-from-RForcecom.html) + * [Passing Control Args](https://StevenMMortimer.github.io/salesforcer/articles/passing-control-args.html) + ## Usage ### Authenticate @@ -242,6 +256,11 @@ 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 ``` If you prefer to be more precise about collecting and formatting the field data you @@ -253,9 +272,10 @@ describe_obj_result <- sf_describe_objects(object_names=c('Account', 'Contact')) # confirm that the Account object is queryable describe_obj_result[[1]][c('label', 'queryable')] # show the different picklist values for the Account Type field -the_type_field <- describe_obj_result[[1]][[59]] -the_type_field$label -map_df(the_type_field[which(names(the_type_field) == "picklistValues")], as_tibble) +all_fields <- describe_obj_result[[1]][names(describe_obj_result[[1]]) == "fields"] +the_type_field_idx <- which(sapply(all_fields, FUN=function(x){x$label}) == "Account Type") +acct_type_field <- all_fields[[the_type_field_idx]] +map_df(acct_type_field[which(names(acct_type_field) == "picklistValues")], as_tibble) ``` It is recommended that you try out the various metadata functions `sf_read_metadata()`, @@ -296,6 +316,30 @@ deleted_custom_object_result <- sf_delete_metadata(metadata_type = 'CustomObject object_names = c('Custom_Account1__c')) ``` +Note that newly created custom fields are not editable by default, meaning that you +will not be able to insert records into them until updating the field level security +of your user profile. Run the following code to determine the user profiles in your +org and updating the field permissions on an object that you may have created with the +example code above. + +```{r metadata-crud-field-security, eval=FALSE} +# get list of user proviles in order to get the "fullName" parameter correct in the next call +my_queries <- list(list(type='Profile')) +profiles_list <- sf_list_metadata(queries=my_queries) + +# update the field level security to "editable" for your fields +prof_update <- sf_update_metadata(metadata_type='Profile', + metadata=list(fullName='Admin', + fieldPermissions=list(field=paste0(custom_object$fullName, '.CustomField3__c'), + editable='true'), + fieldPermissions=list(field=paste0(custom_object$fullName, '.CustomField4__c'), + editable='true'))) + +# now try inserting values into that custom object's fields +my_new_data = tibble(CustomField3__c = "Hello World", CustomField4__c = "Hello World") +added_record <- sf_create(my_new_data, object_name = custom_object$fullName) +``` + ```{r, include=FALSE, message = FALSE, eval=FALSE} # There are methods as part of the REST API that will return metatdata. #sf_describe_global() diff --git a/man-roxygen/all_or_none.R b/man-roxygen/all_or_none.R deleted file mode 100644 index 71579c83..00000000 --- a/man-roxygen/all_or_none.R +++ /dev/null @@ -1,2 +0,0 @@ -#' @param all_or_none logical; allows a call to roll back all changes unless all -#' records are processed successfully diff --git a/man-roxygen/control.R b/man-roxygen/control.R new file mode 100644 index 00000000..2cc6a366 --- /dev/null +++ b/man-roxygen/control.R @@ -0,0 +1,3 @@ +#' @param control \code{list}; a list of parameters for controlling the behavior of +#' the API call being used. For more information of what parameters are available +#' look at the documentation for \code{\link{sf_control}} diff --git a/man-roxygen/queryall.R b/man-roxygen/queryall.R new file mode 100644 index 00000000..41148e1a --- /dev/null +++ b/man-roxygen/queryall.R @@ -0,0 +1,4 @@ +#' @param queryall logical; indicating if the query recordset should include records +#' that have been deleted because of a merge or delete. QueryAll will also return +#' information about archived Task and Event records. QueryAll is available in API +#' version 29.0 and later. diff --git a/man/accepted_controls_by_api.Rd b/man/accepted_controls_by_api.Rd new file mode 100644 index 00000000..7185ce36 --- /dev/null +++ b/man/accepted_controls_by_api.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils-control.R +\name{accepted_controls_by_api} +\alias{accepted_controls_by_api} +\title{Return the Accepted Control Arguments by API Type} +\usage{ +accepted_controls_by_api(api_type = c("SOAP", "REST", "Bulk 1.0", + "Bulk 2.0", "Metadata")) +} +\description{ +Return the Accepted Control Arguments by API Type +} +\note{ +This function is meant to be used internally. Only use when debugging. +} +\keyword{internal} diff --git a/man/accepted_controls_by_operation.Rd b/man/accepted_controls_by_operation.Rd new file mode 100644 index 00000000..04aa6b72 --- /dev/null +++ b/man/accepted_controls_by_operation.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils-control.R +\name{accepted_controls_by_operation} +\alias{accepted_controls_by_operation} +\title{Return the Accepted Control Arguments by Operation} +\usage{ +accepted_controls_by_operation(operation = c("delete", "hardDelete", + "insert", "update", "upsert", "query", "queryall", "retrieve", + "resetPassword", "describeSObjects")) +} +\description{ +Return the Accepted Control Arguments by Operation +} +\note{ +This function is meant to be used internally. Only use when debugging. +} +\keyword{internal} diff --git a/man/filter_valid_controls.Rd b/man/filter_valid_controls.Rd new file mode 100644 index 00000000..bf0024a2 --- /dev/null +++ b/man/filter_valid_controls.Rd @@ -0,0 +1,15 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils-control.R +\name{filter_valid_controls} +\alias{filter_valid_controls} +\title{Filter Out Control Arguments by API or Operation} +\usage{ +filter_valid_controls(supplied, api_type = NULL, operation = NULL) +} +\description{ +Filter Out Control Arguments by API or Operation +} +\note{ +This function is meant to be used internally. Only use when debugging. +} +\keyword{internal} diff --git a/man/return_matching_controls.Rd b/man/return_matching_controls.Rd new file mode 100644 index 00000000..e93b68a1 --- /dev/null +++ b/man/return_matching_controls.Rd @@ -0,0 +1,15 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils-control.R +\name{return_matching_controls} +\alias{return_matching_controls} +\title{Of All Args Return Ones Matching Control Arguments} +\usage{ +return_matching_controls(args) +} +\description{ +Of All Args Return Ones Matching Control Arguments +} +\note{ +This function is meant to be used internally. Only use when debugging. +} +\keyword{internal} diff --git a/man/sf_bulk_operation.Rd b/man/sf_bulk_operation.Rd index 2f4ce6b7..50728830 100644 --- a/man/sf_bulk_operation.Rd +++ b/man/sf_bulk_operation.Rd @@ -7,8 +7,8 @@ sf_bulk_operation(input_data, object_name, operation = c("insert", "delete", "upsert", "update", "hardDelete"), external_id_fieldname = NULL, api_type = c("Bulk 1.0", "Bulk 2.0"), - ..., wait_for_results = TRUE, interval_seconds = 3, - max_attempts = 200, verbose = FALSE) + wait_for_results = TRUE, interval_seconds = 3, max_attempts = 200, + control = list(...), ..., verbose = FALSE) } \arguments{ \item{input_data}{\code{named vector}, \code{matrix}, \code{data.frame}, or @@ -26,9 +26,6 @@ objects during upserts to determine if the record already exists in Salesforce o \item{api_type}{character; one of "REST", "SOAP", "Bulk 1.0", "Bulk 2.0", or "Chatter" indicating which API to use when making the request} -\item{...}{other arguments passed on to \code{\link{sf_create_job_bulk}} such as -\code{content_type}, \code{concurrency_mode}, \code{line_ending} or \code{column_delimiter}.} - \item{wait_for_results}{logical; indicating whether to wait for the operation to complete so that the batch results of individual records can be obtained} @@ -38,6 +35,13 @@ for job completion} \item{max_attempts}{integer; defines then max number attempts to check for job completion before stopping} +\item{control}{\code{list}; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for \code{\link{sf_control}}} + +\item{...}{other arguments passed on to \code{\link{sf_control}} or \code{\link{sf_create_job_bulk}} +such as \code{content_type}, \code{concurrency_mode}, or \code{column_delimiter}} + \item{verbose}{logical; do you want informative messages?} } \value{ diff --git a/man/sf_control.Rd b/man/sf_control.Rd new file mode 100644 index 00000000..281d41cd --- /dev/null +++ b/man/sf_control.Rd @@ -0,0 +1,167 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils-control.R +\name{sf_control} +\alias{sf_control} +\title{Auxiliary for Controlling Calls to Salesforce APIs} +\usage{ +sf_control(AllOrNoneHeader = list(allOrNone = FALSE), + AllowFieldTruncationHeader = list(allowFieldTruncation = FALSE), + AssignmentRuleHeader = list(useDefaultRule = TRUE), + DisableFeedTrackingHeader = list(disableFeedTracking = FALSE), + DuplicateRuleHeader = list(allowSave = FALSE, includeRecordDetails = + FALSE, runAsCurrentUser = TRUE), + EmailHeader = list(triggerAutoResponseEmail = FALSE, triggerOtherEmail + = FALSE, triggerUserEmail = TRUE), LocaleOptions = list(language = + "en_US"), MruHeader = list(updateMru = FALSE), + OwnerChangeOptions = list(options = list(list(execute = TRUE, type = + "EnforceNewOwnerHasReadAccess"), list(execute = FALSE, type = + "KeepAccountTeam"), list(execute = FALSE, type = "KeepSalesTeam"), + list(execute = FALSE, type = + "KeepSalesTeamGrantCurrentOwnerReadWriteAccess"), list(execute = FALSE, + type = "SendEmail"), list(execute = FALSE, type = + "TransferAllOwnedCases"), list(execute = TRUE, type = + "TransferContacts"), list(execute = TRUE, type = "TransferContracts"), + list(execute = FALSE, type = "TransferNotesAndAttachments"), + list(execute = TRUE, type = "TransferOpenActivities"), list(execute = + TRUE, type = "TransferOrders"), list(execute = FALSE, type = + "TransferOtherOpenOpportunities"), list(execute = FALSE, type = + "TransferOwnedClosedOpportunities"), list(execute = FALSE, type = + "TransferOwnedOpenCases"), list(execute = FALSE, type = + "TransferOwnedOpenOpportunities"))), QueryOptions = list(batchSize = + 1000), UserTerritoryDeleteHeader = list(transferToUserId = NA), + BatchRetryHeader = list(`Sforce-Disable-Batch-Retry` = FALSE), + LineEndingHeader = list(`Sforce-Line-Ending` = NA), + PKChunkingHeader = list(`Sforce-Enable-PKChunking` = FALSE), + api_type = NULL, operation = NULL) +} +\arguments{ +\item{AllOrNoneHeader}{\code{list}; containing the \code{allOrNone} element with +a value of \code{TRUE} or \code{FALSE}. This control specifies whether a call rolls back all changes +unless all records are processed successfully. This control is available in +SOAP, REST, and Metadata APIs for the following functions: \code{\link{sf_create}}, +\code{\link{sf_delete}}, \code{\link{sf_update}}, \code{\link{sf_upsert}}, \code{\link{sf_create_metadata}}, +\code{\link{sf_delete_metadata}}, \code{\link{sf_update_metadata}}, \code{\link{sf_upsert_metadata}}. +For more information, read the Salesforce documentation +\href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_allornoneheader.htm}{here}.} + +\item{AllowFieldTruncationHeader}{\code{list}; containing the \code{allowFieldTruncation} +element with a value of \code{TRUE} or \code{FALSE}. This control specifies the truncation behavior +for some field types in SOAP API version 15.0 and later for the following functions: +\code{\link{sf_create}}, \code{\link{sf_update}}, \code{\link{sf_upsert}}. For +more information, read the Salesforce documentation +\href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_allowfieldtruncation.htm}{here}.} + +\item{AssignmentRuleHeader}{\code{list}; containing the \code{useDefaultRule} +element with a value of \code{TRUE} or \code{FALSE} or the \code{assignmentRuleId} element. +This control specifies the assignment rule to use when creating or updating an +Account, Case, or Lead for the following functions: \code{\link{sf_create}}, +\code{\link{sf_update}}, \code{\link{sf_upsert}}. For more information, read the Salesforce documentation +\href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_assignmentruleheader.htm}{here}.} + +\item{DisableFeedTrackingHeader}{\code{list}; containing the \code{disableFeedTracking} +element with a value of \code{TRUE} or \code{FALSE}. This control specifies whether +the changes made in the current call are tracked in feeds for SOAP API calls made +with the following functions: \code{\link{sf_create}}, \code{\link{sf_delete}}, +\code{\link{sf_update}}, \code{\link{sf_upsert}}. For more information, read the Salesforce documentation +\href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_disablefeedtracking.htm}{here}.} + +\item{DuplicateRuleHeader}{\code{list}; containing the \code{allowSave}, +\code{includeRecordDetails}, and \code{runAsCurrentUser} elements each with a +value of \code{TRUE} or \code{FALSE}. This control specifies how duplicate rules should be applied +when using the following functions: \code{\link{sf_create}}, \code{\link{sf_update}}, +\code{\link{sf_upsert}}. For more information, read the Salesforce documentation +\href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_duplicateruleheader.htm}{here}.} + +\item{EmailHeader}{\code{list}; containing the \code{triggerAutoResponseEmail}, +\code{triggerOtherEmail}, and \code{triggerUserEmail} elements each with a +value of \code{TRUE} or \code{FALSE}. This control determines if an email notification should be sent +when a request is processed by SOAP API calls made with the following functions: +\code{\link{sf_create}}, \code{\link{sf_delete}}, \code{\link{sf_update}}, \code{\link{sf_upsert}}, +\code{\link{sf_reset_password}}. For more information, read the Salesforce documentation +\href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_emailheader.htm}{here}.} + +\item{LocaleOptions}{\code{list}; containing the \code{language} element. This control +specifies the language of the labels returned by the \code{\link{sf_describe_objects}} +function using the SOAP API. The value must be a valid user locale (language and country), such as +\code{de_DE} or \code{en_GB}. For more information, read the Salesforce documentation +\href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_localeheader.htm}{here}. +The list of valid user locales is available +\href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_objects_categorynodelocalization.htm#languagelocalekey_desc}{here}.} + +\item{MruHeader}{\code{list}; containing the \code{updateMru} element with a value +of \code{TRUE} or \code{FALSE}. This control indicates whether to update the list +of most recently used items (\code{TRUE}) or not (\code{FALSE}) in the Recent Items +section of the sidebar in the Salesforce user interface. This works for SOAP API calls +made with the following functions: \code{\link{sf_create}}, \code{\link{sf_update}}, +\code{\link{sf_upsert}}, \code{\link{sf_retrieve}}, \code{\link{sf_query}}. For more +information, read the Salesforce documentation +\href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_mruheader.htm}{here}.} + +\item{OwnerChangeOptions}{\code{list}; containing the \code{options} element. +This control specifies the details of ownership of attachments and notes when a +record’s owner is changed. This works for SOAP API calls made with the following functions: +\code{\link{sf_update}}, \code{\link{sf_upsert}}. For more information, read the Salesforce documentation +\href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_ownerchangeoptions.htm}{here}.} + +\item{QueryOptions}{\code{list}; containing the \code{batchSize} element. +This control specifies the batch size for query results . This works for SOAP or +REST API calls made with the following functions: \code{\link{sf_query}}, +\code{\link{sf_retrieve}}. For more information, read the Salesforce documentation +\href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_queryoptions.htm}{here}.} + +\item{UserTerritoryDeleteHeader}{\code{list}; containing the \code{transferToUserId} element. +This control specifies a user to whom open opportunities are assigned when the current +owner is removed from a territory. This works for the \code{\link{sf_delete}} function +using the SOAP API. For more information, read the Salesforce documentation +\href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_userterritorydeleteheader.htm}{here}.} + +\item{BatchRetryHeader}{\code{list}; containing the \code{Sforce-Disable-Batch-Retry} element. +When you create a bulk job, the Batch Retry control lets you disable retries +for unfinished batches included in the job. This works for most operations run through +the Bulk 1.0 API (e.g. \code{sf_create(., api_type = "Bulk 1.0")}) or creating +a Bulk 1.0 job with \code{\link{sf_create_job_bulk}}. For more information, read the Salesforce documentation +\href{https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/async_api_headers_disable_batch_retry.htm}{here}.} + +\item{LineEndingHeader}{\code{list}; containing the \code{Sforce-Line-Ending} element. +When you’re creating a bulk upload job, the Line Ending control lets you +specify whether line endings are read as line feeds (LFs) or as carriage returns +and line feeds (CRLFs) for fields of type Text Area and Text Area (Long). This +works for most operations run through the Bulk APIs or creating a Bulk 1.0 +job with \code{\link{sf_create_job_bulk}}. For more information, read the +Salesforce documentation +\href{https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/async_api_headers_line_ending.htm}{here}.} + +\item{PKChunkingHeader}{\code{list}; containing the \code{Sforce-Enable-PKChunking} element. +Use the PK Chunking control to enable automatic primary key (PK) chunking +for a bulk query job. This works for queries run through the Bulk 1.0 API either via +\code{sf_query(., api_type = "Bulk 1.0")}) or \code{\link{sf_query_bulk}}. For +more information, read the Salesforce documentation +\href{https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/async_api_headers_enable_pk_chunking.htm}{here}.} + +\item{api_type}{character; one of "REST", "SOAP", "Bulk 1.0", "Bulk 2.0", or +"Chatter" indicating which API to use when making the request} + +\item{operation}{character; a string defining the type of operation being +performed (e.g. "insert", "update", "upsert", "delete")} +} +\description{ +Typically only used internally by functions when control parameters are passed +through via dots (...), but it can be called directly to control the behavior +of API calls. This function behaves exactly like \code{\link[stats:glm.control]{glm.control}} +for the \code{\link[stats:glm]{glm}} function. +} +\examples{ +\dontrun{ +this_control <- sf_control(DuplicateRuleHeader=list(allowSave=TRUE, + includeRecordDetails=FALSE, + runAsCurrentUser=TRUE)) +new_contact <- c(FirstName = "Test", LastName = "Contact-Create") +new_record <- sf_create(new_contact, "Contact", control = this_control) + +# specifying the controls directly and are picked up by dots +new_record <- sf_create(new_contact, "Contact", + DuplicateRuleHeader = list(allowSave=TRUE, + includeRecordDetails=FALSE, + runAsCurrentUser=TRUE)) +} +} diff --git a/man/sf_create.Rd b/man/sf_create.Rd index fd27d222..24211c3f 100644 --- a/man/sf_create.Rd +++ b/man/sf_create.Rd @@ -4,9 +4,8 @@ \alias{sf_create} \title{Create Records} \usage{ -sf_create(input_data, object_name, all_or_none = FALSE, - api_type = c("SOAP", "REST", "Bulk 1.0", "Bulk 2.0"), ..., - verbose = FALSE) +sf_create(input_data, object_name, api_type = c("SOAP", "REST", + "Bulk 1.0", "Bulk 2.0"), control = list(...), ..., verbose = FALSE) } \arguments{ \item{input_data}{\code{named vector}, \code{matrix}, \code{data.frame}, or @@ -15,13 +14,15 @@ sf_create(input_data, object_name, all_or_none = FALSE, \item{object_name}{character; the name of one Salesforce objects that the function is operating against (e.g. "Account", "Contact", "CustomObject__c")} -\item{all_or_none}{logical; allows a call to roll back all changes unless all -records are processed successfully} - \item{api_type}{character; one of "REST", "SOAP", "Bulk 1.0", "Bulk 2.0", or "Chatter" indicating which API to use when making the request} -\item{...}{other arguments passed on to \code{\link{sf_bulk_operation}}.} +\item{control}{\code{list}; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for \code{\link{sf_control}}} + +\item{...}{arguments passed to \code{\link{sf_control}} or further downstream +to \code{\link{sf_bulk_operation}}} \item{verbose}{logical; do you want informative messages?} } @@ -31,12 +32,25 @@ records are processed successfully} \description{ Adds one or more new records to your organization’s data. } +\note{ +Because the SOAP and REST calls chunk data into batches of 200 records +the AllOrNoneHeader will only apply to the success or failure of every batch +of records and not all records submitted to the function. +} \examples{ \dontrun{ -n <- 3 +n <- 2 new_contacts <- tibble(FirstName = rep("Test", n), LastName = paste0("Contact", 1:n)) new_contacts_result <- sf_create(new_contacts, object_name="Contact") -new_contacts_result <- sf_create(new_contacts, object_name="Contact", api_type="REST") + +# add the control to allow the creation of records that might violate a duplicate rule +new_contacts_result <- sf_create(new_contacts, object_name="Contact", + DuplicateRuleHeader = list(allowSave = TRUE, + includeRecordDetails = FALSE, + runAsCurrentUser = TRUE)) + +# an example of how to specify using the Bulk 1.0 API to insert the records +new_contacts_result <- sf_create(new_contacts, object_name="Contact", api_type="Bulk 1.0") } } diff --git a/man/sf_create_bulk_v1.Rd b/man/sf_create_bulk_v1.Rd index 05f942d5..be85804e 100644 --- a/man/sf_create_bulk_v1.Rd +++ b/man/sf_create_bulk_v1.Rd @@ -4,8 +4,7 @@ \alias{sf_create_bulk_v1} \title{Create Records using Bulk 1.0 API} \usage{ -sf_create_bulk_v1(input_data, object_name, all_or_none = FALSE, ..., - verbose = FALSE) +sf_create_bulk_v1(input_data, object_name, control, ..., verbose = FALSE) } \description{ Create Records using Bulk 1.0 API diff --git a/man/sf_create_bulk_v2.Rd b/man/sf_create_bulk_v2.Rd index f16169b2..67910b3b 100644 --- a/man/sf_create_bulk_v2.Rd +++ b/man/sf_create_bulk_v2.Rd @@ -4,8 +4,7 @@ \alias{sf_create_bulk_v2} \title{Create Records using Bulk 2.0 API} \usage{ -sf_create_bulk_v2(input_data, object_name, all_or_none = FALSE, ..., - verbose = FALSE) +sf_create_bulk_v2(input_data, object_name, control, ..., verbose = FALSE) } \description{ Create Records using Bulk 2.0 API diff --git a/man/sf_create_job_bulk.Rd b/man/sf_create_job_bulk.Rd index b8a4ce6f..165745c0 100644 --- a/man/sf_create_job_bulk.Rd +++ b/man/sf_create_job_bulk.Rd @@ -5,11 +5,12 @@ \title{Create Bulk API Job} \usage{ sf_create_job_bulk(operation = c("insert", "delete", "upsert", "update", - "hardDelete", "query"), object_name, external_id_fieldname = NULL, - api_type = c("Bulk 1.0", "Bulk 2.0"), content_type = c("CSV", - "ZIP_CSV", "ZIP_XML", "ZIP_JSON"), concurrency_mode = c("Parallel", - "Serial"), line_ending = NULL, column_delimiter = c("COMMA", "TAB", - "PIPE", "SEMICOLON", "CARET", "BACKQUOTE"), verbose = FALSE) + "hardDelete", "query", "queryall"), object_name, + external_id_fieldname = NULL, api_type = c("Bulk 1.0", "Bulk 2.0"), + content_type = c("CSV", "ZIP_CSV", "ZIP_XML", "ZIP_JSON"), + concurrency_mode = c("Parallel", "Serial"), + column_delimiter = c("COMMA", "TAB", "PIPE", "SEMICOLON", "CARET", + "BACKQUOTE"), control = list(...), ..., verbose = FALSE) } \arguments{ \item{operation}{character; a string defining the type of operation being @@ -26,25 +27,26 @@ objects during upserts to determine if the record already exists in Salesforce o "Chatter" indicating which API to use when making the request} \item{content_type}{character; being one of 'CSV', 'ZIP_CSV', 'ZIP_XML', or 'ZIP_JSON' to -indicate the type of data being passed to the Bulk API.} +indicate the type of data being passed to the Bulk APIs. For the Bulk 2.0 API the only +valid value (and the default) is 'CSV'.} \item{concurrency_mode}{character; either "Parallel" or "Serial" that specifies whether batches should be completed sequentially or in parallel. Use "Serial" only if lock contentions persist with in "Parallel" mode. Note: this argument is only used in the Bulk 1.0 API and will be ignored in calls using the Bulk 2.0 API.} -\item{line_ending}{character; indicating the line ending used for CSV job data, -marking the end of a data row. The default is NULL meaing that the line ending -is determined by the operating system using "CRLF" for Windows machines and -"LF" for Unix machines. Note: this argument is only used in the Bulk 2.0 API -and will be ignored in calls using the Bulk 1.0 API.} - \item{column_delimiter}{character; indicating the column delimiter used for CSV job data. The default value is COMMA. Valid values are: "BACKQUOTE", "CARET", "COMMA", "PIPE", "SEMICOLON", and "TAB", but this package only accepts and uses "COMMA". Also, note that this argument is only used in the Bulk 2.0 API and will be ignored in calls using the Bulk 1.0 API.} +\item{control}{\code{list}; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for \code{\link{sf_control}}} + +\item{...}{arguments passed to \code{\link{sf_control}}} + \item{verbose}{logical; do you want informative messages?} } \value{ diff --git a/man/sf_create_job_bulk_v1.Rd b/man/sf_create_job_bulk_v1.Rd index 267926b0..f2db95b5 100644 --- a/man/sf_create_job_bulk_v1.Rd +++ b/man/sf_create_job_bulk_v1.Rd @@ -5,10 +5,10 @@ \title{Create Job using Bulk 1.0 API} \usage{ sf_create_job_bulk_v1(operation = c("insert", "delete", "upsert", - "update", "hardDelete", "query"), object_name, + "update", "hardDelete", "query", "queryall"), object_name, external_id_fieldname = NULL, content_type = c("CSV", "ZIP_CSV", "ZIP_XML", "ZIP_JSON"), concurrency_mode = c("Parallel", "Serial"), - verbose = FALSE) + control, ..., verbose = FALSE) } \description{ Create Job using Bulk 1.0 API diff --git a/man/sf_create_job_bulk_v2.Rd b/man/sf_create_job_bulk_v2.Rd index c4599706..bd2cc067 100644 --- a/man/sf_create_job_bulk_v2.Rd +++ b/man/sf_create_job_bulk_v2.Rd @@ -6,9 +6,8 @@ \usage{ sf_create_job_bulk_v2(operation = c("insert", "delete", "upsert", "update"), object_name, external_id_fieldname = NULL, - content_type = "CSV", line_ending = NULL, - column_delimiter = c("COMMA", "TAB", "PIPE", "SEMICOLON", "CARET", - "BACKQUOTE"), verbose = FALSE) + content_type = "CSV", column_delimiter = c("COMMA", "TAB", "PIPE", + "SEMICOLON", "CARET", "BACKQUOTE"), control, ..., verbose = FALSE) } \description{ Create Job using Bulk 2.0 API diff --git a/man/sf_create_metadata.Rd b/man/sf_create_metadata.Rd index fe51a2fd..e76b54cc 100644 --- a/man/sf_create_metadata.Rd +++ b/man/sf_create_metadata.Rd @@ -4,7 +4,7 @@ \alias{sf_create_metadata} \title{Create Object or Field Metadata in Salesforce} \usage{ -sf_create_metadata(metadata_type, metadata, all_or_none = FALSE, +sf_create_metadata(metadata_type, metadata, control = list(...), ..., verbose = FALSE) } \arguments{ @@ -13,8 +13,11 @@ sf_create_metadata(metadata_type, metadata, all_or_none = FALSE, \item{metadata}{\code{list}; metadata components to be created formatted as XML before being sent via API} -\item{all_or_none}{logical; allows a call to roll back all changes unless all -records are processed successfully} +\item{control}{\code{list}; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for \code{\link{sf_control}}} + +\item{...}{arguments passed to \code{\link{sf_control}}} \item{verbose}{logical; do you want informative messages?} } @@ -55,8 +58,9 @@ custom_metadata$fields <- list(fullName="Phone__c", custom_metadata$deploymentStatus <- 'Deployed' # make a description to identify this easily in the UI setup tab custom_metadata$description <- 'created by the Metadata API' -new_custom_object <- sf_create_metadata(metadata_type='CustomObject', - metadata=custom_metadata, verbose=TRUE) +new_custom_object <- sf_create_metadata(metadata_type = 'CustomObject', + metadata = custom_metadata, + verbose = TRUE) # adding custom fields to our object # input formatted as a list diff --git a/man/sf_create_rest.Rd b/man/sf_create_rest.Rd index 4def4aca..86bfee68 100644 --- a/man/sf_create_rest.Rd +++ b/man/sf_create_rest.Rd @@ -4,8 +4,7 @@ \alias{sf_create_rest} \title{Create Records using REST API} \usage{ -sf_create_rest(input_data, object_name, all_or_none = FALSE, - verbose = FALSE) +sf_create_rest(input_data, object_name, control, ..., verbose = FALSE) } \description{ Create Records using REST API diff --git a/man/sf_create_soap.Rd b/man/sf_create_soap.Rd index 02fdcc49..69edc797 100644 --- a/man/sf_create_soap.Rd +++ b/man/sf_create_soap.Rd @@ -4,8 +4,7 @@ \alias{sf_create_soap} \title{Create Records using SOAP API} \usage{ -sf_create_soap(input_data, object_name, all_or_none = FALSE, - verbose = FALSE) +sf_create_soap(input_data, object_name, control, ..., verbose = FALSE) } \description{ Create Records using SOAP API diff --git a/man/sf_delete.Rd b/man/sf_delete.Rd index aac8e793..acaa4f87 100644 --- a/man/sf_delete.Rd +++ b/man/sf_delete.Rd @@ -4,8 +4,8 @@ \alias{sf_delete} \title{Delete Records} \usage{ -sf_delete(ids, object_name, all_or_none = FALSE, api_type = c("REST", - "SOAP", "Bulk 1.0", "Bulk 2.0"), ..., verbose = FALSE) +sf_delete(ids, object_name, api_type = c("REST", "SOAP", "Bulk 1.0", + "Bulk 2.0"), control = list(...), ..., verbose = FALSE) } \arguments{ \item{ids}{\code{vector}, \code{matrix}, \code{data.frame}, or @@ -15,13 +15,15 @@ that can be passed in the request} \item{object_name}{character; the name of one Salesforce objects that the function is operating against (e.g. "Account", "Contact", "CustomObject__c")} -\item{all_or_none}{logical; allows a call to roll back all changes unless all -records are processed successfully} - \item{api_type}{character; one of "REST", "SOAP", "Bulk 1.0", "Bulk 2.0", or "Chatter" indicating which API to use when making the request} -\item{...}{other arguments passed on to \code{\link{sf_bulk_operation}}.} +\item{control}{\code{list}; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for \code{\link{sf_control}}} + +\item{...}{arguments passed to \code{\link{sf_control}} or further downstream +to \code{\link{sf_bulk_operation}}} \item{verbose}{logical; do you want informative messages?} } @@ -29,20 +31,23 @@ records are processed successfully} \code{tbl_df} of records with success indicator } \description{ -Deletes one or more records to your organization’s data. +Deletes one or more records from your organization’s data. +} +\note{ +Because the SOAP and REST calls chunk data into batches of 200 records +the AllOrNoneHeader will only apply to the success or failure of every batch +of records and not all records submitted to the function. } \examples{ \dontrun{ n <- 3 new_contacts <- tibble(FirstName = rep("Test", n), LastName = paste0("Contact", 1:n)) -new_contacts_result1 <- sf_create(new_contacts, object_name="Contact") -deleted_contacts_result1 <- sf_delete(new_contacts_result1$id, - object_name="Contact") +new_records <- sf_create(new_contacts, object_name="Contact") +deleted_first <- sf_delete(new_records$id[1], object_name = "Contact") -new_contacts_result2 <- sf_create(new_contacts, "Contact") -deleted_contacts_result2 <- sf_delete(new_contacts_result2$id, - object_name="Contact", - api_type="Bulk") +# add the control to do an "All or None" deletion of the remaining records +deleted_rest <- sf_delete(new_records$id[2:3], object_name = "Contact", + AllOrNoneHeader = list(allOrNone = TRUE)) } } diff --git a/man/sf_delete_metadata.Rd b/man/sf_delete_metadata.Rd index ad9c820b..8c6ee333 100644 --- a/man/sf_delete_metadata.Rd +++ b/man/sf_delete_metadata.Rd @@ -4,7 +4,7 @@ \alias{sf_delete_metadata} \title{Delete Object or Field Metadata in Salesforce} \usage{ -sf_delete_metadata(metadata_type, object_names, all_or_none = FALSE, +sf_delete_metadata(metadata_type, object_names, control = list(...), ..., verbose = FALSE) } \arguments{ @@ -12,8 +12,11 @@ sf_delete_metadata(metadata_type, object_names, all_or_none = FALSE, \item{object_names}{a character vector of names that we wish to read metadata for} -\item{all_or_none}{logical; allows a call to roll back all changes unless all -records are processed successfully} +\item{control}{\code{list}; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for \code{\link{sf_control}}} + +\item{...}{arguments passed to \code{\link{sf_control}}} \item{verbose}{logical; do you want informative messages?} } diff --git a/man/sf_describe_objects.Rd b/man/sf_describe_objects.Rd index 669784b2..4a2de38e 100644 --- a/man/sf_describe_objects.Rd +++ b/man/sf_describe_objects.Rd @@ -4,8 +4,8 @@ \alias{sf_describe_objects} \title{SObject Basic Information} \usage{ -sf_describe_objects(object_names, api_type = c("SOAP", "REST", "Bulk"), - verbose = FALSE) +sf_describe_objects(object_names, api_type = c("SOAP", "REST"), + control = list(...), ..., verbose = FALSE) } \arguments{ \item{object_names}{character; the name of one or more Salesforce objects that the @@ -14,6 +14,12 @@ function is operating against (e.g. "Account", "Contact", "CustomObject__c")} \item{api_type}{character; one of "REST", "SOAP", "Bulk 1.0", "Bulk 2.0", or "Chatter" indicating which API to use when making the request} +\item{control}{\code{list}; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for \code{\link{sf_control}}} + +\item{...}{arguments passed to \code{\link{sf_control}}} + \item{verbose}{logical; do you want informative messages?} } \value{ diff --git a/man/sf_query.Rd b/man/sf_query.Rd index d45cace1..05f5c49a 100644 --- a/man/sf_query.Rd +++ b/man/sf_query.Rd @@ -4,9 +4,9 @@ \alias{sf_query} \title{Perform SOQL Query} \usage{ -sf_query(soql, object_name, guess_types = TRUE, queryall = FALSE, - page_size = 1000, api_type = c("REST", "SOAP", "Bulk 1.0"), - next_records_url = NULL, ..., verbose = FALSE) +sf_query(soql, object_name, queryall = FALSE, guess_types = TRUE, + api_type = c("REST", "SOAP", "Bulk 1.0"), control = list(...), ..., + next_records_url = NULL, verbose = FALSE) } \arguments{ \item{soql}{character; a string defining a SOQL query (e.g. "SELECT Id, Name FROM Account")} @@ -14,25 +14,28 @@ sf_query(soql, object_name, guess_types = TRUE, queryall = FALSE, \item{object_name}{character; the name of one Salesforce objects that the function is operating against (e.g. "Account", "Contact", "CustomObject__c")} +\item{queryall}{logical; indicating if the query recordset should include records +that have been deleted because of a merge or delete. QueryAll will also return +information about archived Task and Event records. QueryAll is available in API +version 29.0 and later.} + \item{guess_types}{logical; indicating whether or not to use \code{col_guess()} to try and cast the data returned in the query recordset. TRUE uses \code{col_guess()} and FALSE returns all values as character strings.} -\item{queryall}{logical; indicating if the query recordset should include -deleted and archived records (available only when querying Task and Event records)} - -\item{page_size}{numeric; a number between 200 and 2000 indicating the number of -records per page that are returned. Speed benchmarks should be done to better -understand the speed implications of choosing high or low values of this argument.} - \item{api_type}{character; one of "REST", "SOAP", "Bulk 1.0", "Bulk 2.0", or "Chatter" indicating which API to use when making the request} +\item{control}{\code{list}; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for \code{\link{sf_control}}} + +\item{...}{arguments passed to \code{\link{sf_control}} or further downstream +to \code{\link{sf_query_bulk}}.} + \item{next_records_url}{character (leave as NULL); a string used internally by the function to paginate through to more records until complete} -\item{...}{Other arguments passed on to \code{\link{sf_query_bulk}}.} - \item{verbose}{logical; do you want informative messages?} } \value{ @@ -57,7 +60,7 @@ Additionally, Bulk API can't access or query compound address or compound geoloc } \examples{ \dontrun{ -sf_query("SELECT Id, Account.Name, Email FROM Contact LIMIT 10", verbose = TRUE) +sf_query("SELECT Id, Account.Name, Email FROM Contact LIMIT 10") } } \references{ diff --git a/man/sf_query_bulk.Rd b/man/sf_query_bulk.Rd index 5a677a47..1766a6f2 100644 --- a/man/sf_query_bulk.Rd +++ b/man/sf_query_bulk.Rd @@ -4,9 +4,9 @@ \alias{sf_query_bulk} \title{Run Bulk Query} \usage{ -sf_query_bulk(soql, object_name, guess_types = TRUE, - api_type = c("Bulk 1.0"), interval_seconds = 5, max_attempts = 100, - verbose = FALSE) +sf_query_bulk(soql, object_name = NULL, queryall = FALSE, + guess_types = TRUE, api_type = c("Bulk 1.0"), interval_seconds = 5, + max_attempts = 100, control = list(...), ..., verbose = FALSE) } \arguments{ \item{soql}{character; a string defining a SOQL query (e.g. "SELECT Id, Name FROM Account")} @@ -14,6 +14,11 @@ sf_query_bulk(soql, object_name, guess_types = TRUE, \item{object_name}{character; the name of one Salesforce objects that the function is operating against (e.g. "Account", "Contact", "CustomObject__c")} +\item{queryall}{logical; indicating if the query recordset should include records +that have been deleted because of a merge or delete. QueryAll will also return +information about archived Task and Event records. QueryAll is available in API +version 29.0 and later.} + \item{guess_types}{logical; indicating whether or not to use \code{col_guess()} to try and cast the data returned in the query recordset. TRUE uses \code{col_guess()} and FALSE returns all values as character strings.} @@ -27,6 +32,12 @@ for job completion} \item{max_attempts}{integer; defines then max number attempts to check for job completion before stopping} +\item{control}{\code{list}; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for \code{\link{sf_control}}} + +\item{...}{arguments passed to \code{\link{sf_control}}} + \item{verbose}{logical; do you want informative messages?} } \value{ @@ -38,10 +49,11 @@ bulk query API jobs } \examples{ \dontrun{ -# select all Ids from Account object -ids <- sf_query_bulk(soql='SELECT Id FROM Account', object_name='Account') +# select all Ids from Account object (up to 1000) +ids <- sf_query_bulk(soql = 'SELECT Id FROM Account LIMIT 1000', + object_name = 'Account') } } \references{ -\url{https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/} +\url{https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/asynch_api_bulk_query_intro.htm} } diff --git a/man/sf_query_result_bulk.Rd b/man/sf_query_result_bulk.Rd index 2f8c518e..a9d394a4 100644 --- a/man/sf_query_result_bulk.Rd +++ b/man/sf_query_result_bulk.Rd @@ -34,9 +34,9 @@ which has already been submitted to Bulk API Job and has Completed state } \examples{ \dontrun{ -my_query <- "SELECT Id, Name FROM Account LIMIT 10" -job_info <- sf_create_job_bulk(operation='query', object='Account') -query_info <- sf_submit_query_bulk(job_id=job_info$id, soql=my_query) +my_query <- "SELECT Id, Name FROM Account LIMIT 1000" +job_info <- sf_create_job_bulk(operation = 'query', object = 'Account') +query_info <- sf_submit_query_bulk(job_id = job_info$id, soql = my_query) result <- sf_batch_details_bulk(job_id = query_info$jobId, batch_id = query_info$id) recordset <- sf_query_result_bulk(job_id = query_info$jobId, diff --git a/man/sf_reset_password.Rd b/man/sf_reset_password.Rd index 647081f7..71b2b991 100644 --- a/man/sf_reset_password.Rd +++ b/man/sf_reset_password.Rd @@ -4,11 +4,17 @@ \alias{sf_reset_password} \title{Reset User Password} \usage{ -sf_reset_password(user_id, verbose = FALSE) +sf_reset_password(user_id, control = list(...), ..., verbose = FALSE) } \arguments{ \item{user_id}{character; the unique Salesforce Id assigned to the User} +\item{control}{\code{list}; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for \code{\link{sf_control}}} + +\item{...}{arguments passed to \code{\link{sf_control}}} + \item{verbose}{logical; do you want informative messages?} } \value{ @@ -19,6 +25,10 @@ Changes a user’s password to a temporary, system-generated value. } \examples{ \dontrun{ -sf_reset_password(user_id = "0056A000000ZZZaaBBB") +# reset a user's password and ensure that an email is triggered to them +sf_reset_password(user_id = "0056A000000ZZZaaBBB", + EmailHeader = list(triggerAutoResponseEmail = FALSE, + triggerOtherEmail = FALSE, + triggerUserEmail = TRUE)) } } diff --git a/man/sf_retrieve.Rd b/man/sf_retrieve.Rd index fded45e8..86d8eb44 100644 --- a/man/sf_retrieve.Rd +++ b/man/sf_retrieve.Rd @@ -5,7 +5,7 @@ \title{Retrieve Records By Id} \usage{ sf_retrieve(ids, fields, object_name, api_type = c("REST", "SOAP", - "Bulk 1.0", "Bulk 2.0"), verbose = FALSE) + "Bulk 1.0", "Bulk 2.0"), control = list(...), ..., verbose = FALSE) } \arguments{ \item{ids}{\code{vector}, \code{matrix}, \code{data.frame}, or @@ -21,6 +21,12 @@ function is operating against (e.g. "Account", "Contact", "CustomObject__c")} \item{api_type}{character; one of "REST", "SOAP", "Bulk 1.0", "Bulk 2.0", or "Chatter" indicating which API to use when making the request} +\item{control}{\code{list}; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for \code{\link{sf_control}}} + +\item{...}{arguments passed to \code{\link{sf_control}}} + \item{verbose}{logical; do you want informative messages?} } \value{ diff --git a/man/sf_search.Rd b/man/sf_search.Rd index fa58fe43..05ee2651 100644 --- a/man/sf_search.Rd +++ b/man/sf_search.Rd @@ -23,7 +23,7 @@ and FALSE returns all values as character strings.} "Chatter" indicating which API to use when making the request} \item{parameterized_search_options}{\code{list}; a list of parameters for -controlling the search if not using SOSL. If using SOSL this is ignored.} +controlling the search if not using SOSL. If using SOSL this argument is ignored.} \item{verbose}{logical; do you want informative messages?} diff --git a/man/sf_submit_query_bulk.Rd b/man/sf_submit_query_bulk.Rd index a659ade6..a94a904c 100644 --- a/man/sf_submit_query_bulk.Rd +++ b/man/sf_submit_query_bulk.Rd @@ -40,9 +40,9 @@ Additionally, Bulk API can't access or query compound address or compound geoloc } \examples{ \dontrun{ -my_query <- "SELECT Id, Name FROM Account LIMIT 10" -job_info <- sf_create_job_bulk(operation='query', object='Account') -query_info <- sf_submit_query_bulk(job_id=job_info$id, soql=my_query) +my_query <- "SELECT Id, Name FROM Account LIMIT 1000" +job_info <- sf_create_job_bulk(operation = 'query', object = 'Account') +query_info <- sf_submit_query_bulk(job_id = job_info$id, soql = my_query) } } \references{ diff --git a/man/sf_update.Rd b/man/sf_update.Rd index 72f8c6ce..ff6417d9 100644 --- a/man/sf_update.Rd +++ b/man/sf_update.Rd @@ -4,9 +4,8 @@ \alias{sf_update} \title{Update Records} \usage{ -sf_update(input_data, object_name, all_or_none = FALSE, - api_type = c("SOAP", "REST", "Bulk 1.0", "Bulk 2.0"), ..., - verbose = FALSE) +sf_update(input_data, object_name, api_type = c("SOAP", "REST", + "Bulk 1.0", "Bulk 2.0"), control = list(...), ..., verbose = FALSE) } \arguments{ \item{input_data}{\code{named vector}, \code{matrix}, \code{data.frame}, or @@ -15,13 +14,15 @@ sf_update(input_data, object_name, all_or_none = FALSE, \item{object_name}{character; the name of one Salesforce objects that the function is operating against (e.g. "Account", "Contact", "CustomObject__c")} -\item{all_or_none}{logical; allows a call to roll back all changes unless all -records are processed successfully} - \item{api_type}{character; one of "REST", "SOAP", "Bulk 1.0", "Bulk 2.0", or "Chatter" indicating which API to use when making the request} -\item{...}{other arguments passed on to \code{\link{sf_bulk_operation}}.} +\item{control}{\code{list}; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for \code{\link{sf_control}}} + +\item{...}{arguments passed to \code{\link{sf_control}} or further downstream +to \code{\link{sf_bulk_operation}}} \item{verbose}{logical; do you want informative messages?} } @@ -31,18 +32,31 @@ records are processed successfully} \description{ Updates one or more records to your organization’s data. } +\note{ +Because the SOAP and REST calls chunk data into batches of 200 records +the AllOrNoneHeader will only apply to the success or failure of every batch +of records and not all records submitted to the function. +} \examples{ \dontrun{ -n <- 3 -new_contacts <- tibble(FirstName = rep("Test", n), - LastName = paste0("Contact", 1:n)) -new_contacts_result <- sf_create(new_contacts, "Contact") +n <- 2 +new_accts <- tibble(FirstName = rep("Test", n), + LastName = paste0("Account", 1:n)) +new_records <- sf_create(new_accts, "Account") -update_contacts <- tibble(FirstName = rep("TestTest", n), - LastName = paste0("Contact", 1:n), - Id = new_contacts_result$id) -updated_contacts_result1 <- sf_update(update_contacts, "Contact") -updated_contacts_result2 <- sf_update(update_contacts, "Contact", - api_type="Bulk") +updated_accts <- tibble(FirstName = rep("TestTest", n), + LastName = paste0("Account", 1:n), + Id = new_records$id) + +# update the accounts and ensure that all contacts and cases (open and closed) +# owned by the previous account owner are transferred to the new owner +update <- sf_update(updated_accts, "Account", + OwnerChangeOptions=list(options= + list(list(execute=TRUE, + type="TransferAllOwnedCases"), + list(execute=TRUE, + type="TransferOwnedOpenCases"), + list(execute=TRUE, + type="TransferContacts")))) } } diff --git a/man/sf_update_bulk_v1.Rd b/man/sf_update_bulk_v1.Rd index 04eb321b..7676e3c9 100644 --- a/man/sf_update_bulk_v1.Rd +++ b/man/sf_update_bulk_v1.Rd @@ -4,8 +4,7 @@ \alias{sf_update_bulk_v1} \title{Update Records using Bulk 1.0 API} \usage{ -sf_update_bulk_v1(input_data, object_name, all_or_none = FALSE, ..., - verbose = FALSE) +sf_update_bulk_v1(input_data, object_name, control, ..., verbose = FALSE) } \description{ Update Records using Bulk 1.0 API diff --git a/man/sf_update_bulk_v2.Rd b/man/sf_update_bulk_v2.Rd index 19d82a57..28fc848a 100644 --- a/man/sf_update_bulk_v2.Rd +++ b/man/sf_update_bulk_v2.Rd @@ -4,8 +4,7 @@ \alias{sf_update_bulk_v2} \title{Update Records using Bulk 2.0 API} \usage{ -sf_update_bulk_v2(input_data, object_name, all_or_none = FALSE, ..., - verbose = FALSE) +sf_update_bulk_v2(input_data, object_name, control, ..., verbose = FALSE) } \description{ Update Records using Bulk 2.0 API diff --git a/man/sf_update_metadata.Rd b/man/sf_update_metadata.Rd index 54af55cb..824526a5 100644 --- a/man/sf_update_metadata.Rd +++ b/man/sf_update_metadata.Rd @@ -4,7 +4,7 @@ \alias{sf_update_metadata} \title{Update Object or Field Metadata in Salesforce} \usage{ -sf_update_metadata(metadata_type, metadata, all_or_none = FALSE, +sf_update_metadata(metadata_type, metadata, control = list(...), ..., verbose = FALSE) } \arguments{ @@ -13,8 +13,11 @@ sf_update_metadata(metadata_type, metadata, all_or_none = FALSE, \item{metadata}{\code{list}; metadata components to be created formatted as XML before being sent via API} -\item{all_or_none}{logical; allows a call to roll back all changes unless all -records are processed successfully} +\item{control}{\code{list}; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for \code{\link{sf_control}}} + +\item{...}{arguments passed to \code{\link{sf_control}}} \item{verbose}{logical; do you want informative messages?} } diff --git a/man/sf_update_rest.Rd b/man/sf_update_rest.Rd index 09928882..719abe7b 100644 --- a/man/sf_update_rest.Rd +++ b/man/sf_update_rest.Rd @@ -4,8 +4,7 @@ \alias{sf_update_rest} \title{Update Records using REST API} \usage{ -sf_update_rest(input_data, object_name, all_or_none = FALSE, - verbose = FALSE) +sf_update_rest(input_data, object_name, control, ..., verbose = FALSE) } \description{ Update Records using REST API diff --git a/man/sf_update_soap.Rd b/man/sf_update_soap.Rd index 02abad3c..4ca311ec 100644 --- a/man/sf_update_soap.Rd +++ b/man/sf_update_soap.Rd @@ -4,8 +4,7 @@ \alias{sf_update_soap} \title{Update Records using SOAP API} \usage{ -sf_update_soap(input_data, object_name, all_or_none = FALSE, - verbose = FALSE) +sf_update_soap(input_data, object_name, control, ..., verbose = FALSE) } \description{ Update Records using SOAP API diff --git a/man/sf_upsert.Rd b/man/sf_upsert.Rd index 5ba3fae8..1c322f80 100644 --- a/man/sf_upsert.Rd +++ b/man/sf_upsert.Rd @@ -5,8 +5,8 @@ \title{Upsert Records} \usage{ sf_upsert(input_data, object_name, external_id_fieldname, - all_or_none = FALSE, api_type = c("SOAP", "REST", "Bulk 1.0", - "Bulk 2.0"), ..., verbose = FALSE) + api_type = c("SOAP", "REST", "Bulk 1.0", "Bulk 2.0"), + control = list(...), ..., verbose = FALSE) } \arguments{ \item{input_data}{\code{named vector}, \code{matrix}, \code{data.frame}, or @@ -19,13 +19,15 @@ function is operating against (e.g. "Account", "Contact", "CustomObject__c")} object that has been set as an "External ID" field. This field is used to reference objects during upserts to determine if the record already exists in Salesforce or not.} -\item{all_or_none}{logical; allows a call to roll back all changes unless all -records are processed successfully} - \item{api_type}{character; one of "REST", "SOAP", "Bulk 1.0", "Bulk 2.0", or "Chatter" indicating which API to use when making the request} -\item{...}{other arguments passed on to \code{\link{sf_bulk_operation}}.} +\item{control}{\code{list}; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for \code{\link{sf_control}}} + +\item{...}{arguments passed to \code{\link{sf_control}} or further downstream +to \code{\link{sf_bulk_operation}}} \item{verbose}{logical; do you want informative messages?} } diff --git a/man/sf_upsert_bulk_v1.Rd b/man/sf_upsert_bulk_v1.Rd index 588844d1..36b1122e 100644 --- a/man/sf_upsert_bulk_v1.Rd +++ b/man/sf_upsert_bulk_v1.Rd @@ -4,8 +4,8 @@ \alias{sf_upsert_bulk_v1} \title{Upsert Records using Bulk 1.0 API} \usage{ -sf_upsert_bulk_v1(input_data, object_name, external_id_fieldname, - all_or_none = FALSE, ..., verbose = FALSE) +sf_upsert_bulk_v1(input_data, object_name, external_id_fieldname, control, + ..., verbose = FALSE) } \description{ Upsert Records using Bulk 1.0 API diff --git a/man/sf_upsert_bulk_v2.Rd b/man/sf_upsert_bulk_v2.Rd index ddb1515d..50de5340 100644 --- a/man/sf_upsert_bulk_v2.Rd +++ b/man/sf_upsert_bulk_v2.Rd @@ -4,8 +4,8 @@ \alias{sf_upsert_bulk_v2} \title{Upsert Records using Bulk 2.0 API} \usage{ -sf_upsert_bulk_v2(input_data, object_name, external_id_fieldname, - all_or_none = FALSE, ..., verbose = FALSE) +sf_upsert_bulk_v2(input_data, object_name, external_id_fieldname, control, + ..., verbose = FALSE) } \description{ Upsert Records using Bulk 2.0 API diff --git a/man/sf_upsert_metadata.Rd b/man/sf_upsert_metadata.Rd index 7dc6f985..cafc6654 100644 --- a/man/sf_upsert_metadata.Rd +++ b/man/sf_upsert_metadata.Rd @@ -4,7 +4,7 @@ \alias{sf_upsert_metadata} \title{Upsert Object or Field Metadata in Salesforce} \usage{ -sf_upsert_metadata(metadata_type, metadata, all_or_none = FALSE, +sf_upsert_metadata(metadata_type, metadata, control = list(...), ..., verbose = FALSE) } \arguments{ @@ -13,8 +13,11 @@ sf_upsert_metadata(metadata_type, metadata, all_or_none = FALSE, \item{metadata}{\code{list}; metadata components to be created formatted as XML before being sent via API} -\item{all_or_none}{logical; allows a call to roll back all changes unless all -records are processed successfully} +\item{control}{\code{list}; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for \code{\link{sf_control}}} + +\item{...}{arguments passed to \code{\link{sf_control}}} \item{verbose}{logical; do you want informative messages?} } diff --git a/man/sf_upsert_rest.Rd b/man/sf_upsert_rest.Rd index 760a3c04..b844e213 100644 --- a/man/sf_upsert_rest.Rd +++ b/man/sf_upsert_rest.Rd @@ -4,8 +4,8 @@ \alias{sf_upsert_rest} \title{Upsert Records using REST API} \usage{ -sf_upsert_rest(input_data, object_name, external_id_fieldname, - all_or_none = FALSE, verbose = FALSE) +sf_upsert_rest(input_data, object_name, external_id_fieldname, control, + ..., verbose = FALSE) } \description{ Upsert Records using REST API diff --git a/man/sf_upsert_soap.Rd b/man/sf_upsert_soap.Rd index 59be6df5..aa4f528b 100644 --- a/man/sf_upsert_soap.Rd +++ b/man/sf_upsert_soap.Rd @@ -4,8 +4,8 @@ \alias{sf_upsert_soap} \title{Upsert Records using SOAP API} \usage{ -sf_upsert_soap(input_data, object_name, external_id_fieldname, - all_or_none = FALSE, verbose = FALSE) +sf_upsert_soap(input_data, object_name, external_id_fieldname, control, + ..., verbose = FALSE) } \description{ Upsert Records using SOAP API diff --git a/vignettes/getting-started.Rmd b/vignettes/getting-started.Rmd index 3826de6a..e8029c04 100644 --- a/vignettes/getting-started.Rmd +++ b/vignettes/getting-started.Rmd @@ -70,6 +70,7 @@ sprintf("User Active?: %s", user_info$isActive) ``` ### 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. @@ -82,6 +83,7 @@ created_records ``` ### Retrieve + Retrieve pulls down a specific set of records and fields. It's very similar to running a query, but doesn't use SOQL. Here is an example where we retrieve the data we just created. @@ -93,7 +95,6 @@ retrieved_records <- sf_retrieve(ids=created_records$id, retrieved_records ``` - ### Query Salesforce has proprietary form of SQL called SOQL (Salesforce Object Query diff --git a/vignettes/getting-started.md b/vignettes/getting-started.md index f35f3eea..9e4c7096 100644 --- a/vignettes/getting-started.md +++ b/vignettes/getting-started.md @@ -55,12 +55,13 @@ the information returned about the current user. It should be information about # and confirm a connection to the APIs user_info <- sf_user_info() sprintf("User Id: %s", user_info$id) -#> [1] "User Id: 0056A000000MPRjQAO" +#> character(0) sprintf("User Active?: %s", user_info$isActive) -#> [1] "User Active?: TRUE" +#> character(0) ``` ### 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. @@ -73,12 +74,13 @@ created_records <- sf_create(new_contacts, "Contact") created_records #> # A tibble: 2 x 2 #> id success -#> -#> 1 0036A00000SncEJQAZ true -#> 2 0036A00000SncEKQAZ true +#> +#> 1 0036A00000wzh4VQAQ TRUE +#> 2 0036A00000wzh4WQAQ TRUE ``` ### Retrieve + Retrieve pulls down a specific set of records and fields. It's very similar to running a query, but doesn't use SOQL. Here is an example where we retrieve the data we just created. @@ -92,11 +94,10 @@ retrieved_records #> # A tibble: 2 x 3 #> Id FirstName LastName #> -#> 1 0036A00000SncEJQAZ Test Contact-Create-1 -#> 2 0036A00000SncEKQAZ Test Contact-Create-2 +#> 1 0036A00000wzh4VQAQ Test Contact-Create-1 +#> 2 0036A00000wzh4WQAQ Test Contact-Create-2 ``` - ### Query Salesforce has proprietary form of SQL called SOQL (Salesforce Object Query @@ -121,9 +122,9 @@ queried_records <- sf_query(my_soql) queried_records #> # A tibble: 2 x 4 #> Id Account FirstName LastName -#> * -#> 1 0036A00000SncEJQAZ NA Test Contact-Create-1 -#> 2 0036A00000SncEKQAZ NA Test Contact-Create-2 +#> +#> 1 0036A00000wzh4VQAQ NA Test Contact-Create-1 +#> 2 0036A00000wzh4WQAQ NA Test Contact-Create-2 ``` ### Update @@ -147,9 +148,9 @@ updated_records <- sf_update(queried_records, object_name="Contact") updated_records #> # A tibble: 2 x 2 #> id success -#> -#> 1 0036A00000SncEJQAZ true -#> 2 0036A00000SncEKQAZ true +#> +#> 1 0036A00000wzh4VQAQ TRUE +#> 2 0036A00000wzh4WQAQ TRUE ``` ### Delete @@ -164,8 +165,8 @@ deleted_records #> # A tibble: 2 x 3 #> id success errors #> -#> 1 0036A00000SncEJQAZ TRUE -#> 2 0036A00000SncEKQAZ TRUE +#> 1 0036A00000wzh4VQAQ TRUE +#> 2 0036A00000wzh4WQAQ TRUE ``` ### Upsert @@ -199,10 +200,10 @@ upserted_records <- sf_upsert(input_data=upserted_contacts, upserted_records #> # A tibble: 3 x 3 #> created id success -#> -#> 1 false 0036A00000SncEOQAZ true -#> 2 false 0036A00000SncEPQAZ true -#> 3 true 0036A00000SncETQAZ true +#> +#> 1 FALSE 0036A00000wzh4XQAQ TRUE +#> 2 FALSE 0036A00000wzh4YQAQ TRUE +#> 3 TRUE 0036A00000wzh4aQAA TRUE ``` diff --git a/vignettes/passing-control-args.R b/vignettes/passing-control-args.R new file mode 100644 index 00000000..83eacec1 --- /dev/null +++ b/vignettes/passing-control-args.R @@ -0,0 +1,53 @@ +## ---- echo = FALSE------------------------------------------------------- +NOT_CRAN <- identical(tolower(Sys.getenv("NOT_CRAN")), "true") +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + purl = NOT_CRAN, + eval = NOT_CRAN +) + +## ----auth, include = FALSE----------------------------------------------- +suppressWarnings(suppressMessages(library(dplyr))) +library(salesforcer) +token_path <- here::here("tests", "testthat", "salesforcer_token.rds") +suppressMessages(sf_auth(token = token_path, verbose = FALSE)) + +## ----sample-create------------------------------------------------------- +new_contact <- c(FirstName = "Test", LastName = "Contact-Create") +record <- sf_create(new_contact, + object_name = "Contact", + DisableFeedTrackingHeader = list(disableFeedTracking = TRUE), + api_type = "SOAP") + +## ---- include = FALSE---------------------------------------------------- +deleted_records <- sf_delete(record$id) + +## ----sample-create-w-duplicate------------------------------------------- +new_contact <- c(FirstName = "Test", LastName = "Contact-Create") +record <- sf_create(new_contact, + object_name = "Contact", + DuplicateRuleHeader = list(allowSave = TRUE, + includeRecordDetails = FALSE, + runAsCurrentUser = TRUE)) + +## ---- include = FALSE---------------------------------------------------- +deleted_records <- sf_delete(record$id) + +## ----sample-create-w-warning--------------------------------------------- +new_contact <- c(FirstName = "Test", LastName = "Contact-Create") +record <- sf_create(new_contact, + object_name = "Contact", + BatchRetryHeader = list(`Sforce-Disable-Batch-Retry` = FALSE), + api_type = "SOAP") + +## ---- include = FALSE---------------------------------------------------- +deleted_records <- sf_delete(record$id) + +## ----sample-query-------------------------------------------------------- +new_contact <- c(FirstName = "Test", LastName = "Contact-Create") +records <- sf_query("SELECT Id, Name FROM Account LIMIT 1000", + object_name = "Account", + control = sf_control(QueryOptions = list(batchSize = 100)), + api_type = "SOAP") + diff --git a/vignettes/passing-control-args.Rmd b/vignettes/passing-control-args.Rmd new file mode 100644 index 00000000..05a194e1 --- /dev/null +++ b/vignettes/passing-control-args.Rmd @@ -0,0 +1,166 @@ +--- +title: "Passing Control Args" +author: "Steven M. Mortimer" +date: "2019-06-08" +output: + rmarkdown::html_vignette: + toc: true + toc_depth: 4 + keep_md: true +vignette: > + %\VignetteIndexEntry{Passing Control Args} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, echo = FALSE} +NOT_CRAN <- identical(tolower(Sys.getenv("NOT_CRAN")), "true") +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + purl = NOT_CRAN, + eval = NOT_CRAN +) +``` + +If you're inserting records from R you may want to turn off the assignment rules +or even bypass duplicate rules and alerts to save records. Beginning in Version 0.1.3 of +the **salesforcer** package many functions have a `control` argument that will allow +you to fine tune the behavior of calls to the Salesforce APIs. This vignette will +introduce the different options you can control and how to pass them into the **salesforcer** +functions you're already familiar with. + +### The new control argument + +This new feature can be seen in the `sf_create` (and many other functions) as +`control=list(...)`. The dots mean that you can pass any number of controls directly +into the function. For example, the following code will create a record, but prevent +its creation from showing up in the Chatter feeds by setting the `DisableFeedTrackingHeader`. + +```{r auth, include = FALSE} +suppressWarnings(suppressMessages(library(dplyr))) +library(salesforcer) +token_path <- here::here("tests", "testthat", "salesforcer_token.rds") +suppressMessages(sf_auth(token = token_path, verbose = FALSE)) +``` + +```{r sample-create} +new_contact <- c(FirstName = "Test", LastName = "Contact-Create") +record <- sf_create(new_contact, + object_name = "Contact", + DisableFeedTrackingHeader = list(disableFeedTracking = TRUE), + api_type = "SOAP") +``` + +```{r, include = FALSE} +deleted_records <- sf_delete(record$id) +``` + +You will notice that the argument `DisableFeedTrackingHeader` can be included right into +the function without any documentation existing for it in the `sf_create` function. +This is because the dots (`...`) allow you to pass over a dozen different control +parameters and that documentation would be tedious to create and maintain over multiple +functions in the package. However, you will notice in the documentation entry for +the `control` argument there is a link to a function called `sf_control` which you +can use to directly to pass into `control` or simply to review its documentation of all the +possible control parameters and their defaults. This is where you can review the various +control options in more detail before trying to set them. + +You may have also noticed that the argument DisableFeedTrackingHeader was formatted +as a list with an element inside called `disableFeedTracking` set to `TRUE`. This may +seem redundant but there are two reasons for this. First, this is exactly how the +Salesforce APIs documents these options, which are typically referred to as "headers" +because they are passed as a named header of the HTTP request and then the header fields +and values are provided for that header. Second, some headers have multiple fields and values +so a list is the only way to provide multiple named fields and values under a single header entity. +For example, the DuplicateRuleHeader, which controls whether +the duplicate rules can be overridden when inserting records from the API, has three +fields: `allowSave`, `includeRecordDetails`, and `runAsCurrentUser`. Supplying all +three requires a list-like structure, which may seem redundant in other cases, but is +necessary to follow. + +```{r sample-create-w-duplicate} +new_contact <- c(FirstName = "Test", LastName = "Contact-Create") +record <- sf_create(new_contact, + object_name = "Contact", + DuplicateRuleHeader = list(allowSave = TRUE, + includeRecordDetails = FALSE, + runAsCurrentUser = TRUE)) +``` + +```{r, include = FALSE} +deleted_records <- sf_delete(record$id) +``` + +Finally, you will notice in the example call that the `api_type` argument is set to "SOAP". +This is because the `DisableFeedTrackingHeader` is a control that is only available when +making calls via the SOAP API. You will receive a warning when trying to set control +parameters for APIs or operations that do not recognize that control. For example, +the following code tries to set the `BatchRetryHeader` for a call to the SOAP +API which does not acknowledge that control. That control is only used with the Bulk +1.0 API since its records as submitted in batches and automatic retry can be controlled. + +```{r sample-create-w-warning} +new_contact <- c(FirstName = "Test", LastName = "Contact-Create") +record <- sf_create(new_contact, + object_name = "Contact", + BatchRetryHeader = list(`Sforce-Disable-Batch-Retry` = FALSE), + api_type = "SOAP") +``` + +```{r, include = FALSE} +deleted_records <- sf_delete(record$id) +``` + +### Creating the control argument with sf_control + +If this type of control structure is new to you, take a look at the documentation for +the `glm` and `glm.control` functions. The way these two functions behave is exactly how +functions like `sf_create` and `sf_control` work with each other. As demonstrated above +you can pass any number of arbitrary controls into the function and they are all +gathered up into the control by `control = list(...)`. However, you can specify the +control directly like this: + +```{r sample-query} +new_contact <- c(FirstName = "Test", LastName = "Contact-Create") +records <- sf_query("SELECT Id, Name FROM Account LIMIT 1000", + object_name = "Account", + control = sf_control(QueryOptions = list(batchSize = 100)), + api_type = "SOAP") +``` + +### Backwards compatibility for all_or_none and other named arguments + +You may already be taking advantage of the `all_or_none` or `line_ending` arguments +which are control arguments that were explicity included in functions. These argument +essentially hard coded values to pass the `AllOrNoneHeader` and `LineEndingHeader` +control parameters. Starting with the 0.1.3 release it is no longer necessary and +preferable not to have an argument like `all_or_none` listed explicity as an argument +since it can be provided in the `control` argument. Note: the `all_or_none` argument +and other explicit control arguments will still be available in **salesforcer 0.1.3** +but will provide a deprecated warning. They will be removed in the next CRAN release +of the package so it will be important to update your code now if you are explicitly +passing these arguments and see a deprecation warning. + +### Reference Links + +Below is a list of links that go directly to the control arguments (a.k.a headers) +for the different APIs. I highly recommend reading this documentation before setting +a control parameter in R so you know exactly what the behavior will be and how to +specify it in R. You may notice that some controls are not included in the R package. +Some may be added in the future if requested and some will not be added given the +scope of the package. One final note is that some arguments in the REST API, like the +"All or None" behavior is not a header, but a parameter in the API call. For this reason +you will not see it listed in the REST API Headers section, but it is set in this R package +using the `AllOrNoneHeader` argument in `sf_control` just to provide consistency between +the SOAP and REST APIs. It would be confusing to have two arguments named differently, +one for each API, but to do the exact same thing from R. For this reason, many of the +control arguments match exactly as they are listed in the SOAP API, but can be used +across other APIs even if not exactly written that way in the Salesforce documentation +referenced below. + + * **SOAP API Headers**: https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/soap_headers.htm + * **REST API Headers**: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/headers.htm + * **Bulk 1.0 API Headers**: https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/async_api_headers.htm + * **Bulk 2.0 API Headers**: None + * **Metadata API Headers**: https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_headers.htm diff --git a/vignettes/passing-control-args.md b/vignettes/passing-control-args.md new file mode 100644 index 00000000..0e3541ec --- /dev/null +++ b/vignettes/passing-control-args.md @@ -0,0 +1,154 @@ +--- +title: "Passing Control Args" +author: "Steven M. Mortimer" +date: "2019-06-08" +output: + rmarkdown::html_vignette: + toc: true + toc_depth: 4 + keep_md: true +vignette: > + %\VignetteIndexEntry{Passing Control Args} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + + + +If you're inserting records from R you may want to turn off the assignment rules +or even bypass duplicate rules and alerts to save records. Beginning in Version 0.1.3 of +the **salesforcer** package many functions have a `control` argument that will allow +you to fine tune the behavior of calls to the Salesforce APIs. This vignette will +introduce the different options you can control and how to pass them into the **salesforcer** +functions you're already familiar with. + +### The new control argument + +This new feature can be seen in the `sf_create` (and many other functions) as +`control=list(...)`. The dots mean that you can pass any number of controls directly +into the function. For example, the following code will create a record, but prevent +its creation from showing up in the Chatter feeds by setting the `DisableFeedTrackingHeader`. + + + + +```r +new_contact <- c(FirstName = "Test", LastName = "Contact-Create") +record <- sf_create(new_contact, + object_name = "Contact", + DisableFeedTrackingHeader = list(disableFeedTracking = TRUE), + api_type = "SOAP") +``` + + + +You will notice that the argument `DisableFeedTrackingHeader` can be included right into +the function without any documentation existing for it in the `sf_create` function. +This is because the dots (`...`) allow you to pass over a dozen different control +parameters and that documentation would be tedious to create and maintain over multiple +functions in the package. However, you will notice in the documentation entry for +the `control` argument there is a link to a function called `sf_control` which you +can use to directly to pass into `control` or simply to review its documentation of all the +possible control parameters and their defaults. This is where you can review the various +control options in more detail before trying to set them. + +You may have also noticed that the argument `DisableFeedTrackingHeader` was formatted +as a list with an element inside called `disableFeedTracking` set to `TRUE`. This may +seem redundant but there are two reasons for this. First, this is exactly how the +Salesforce APIs documents these options, which are typically referred to as "headers" +because they are passed as a named header of the HTTP request and then the header fields +and values are provided for that header. Second, some headers have multiple fields and values +so a list is the only way to provide multiple named fields and values under a single header entity. +For example, the `DuplicateRuleHeader`, which controls whether +the duplicate rules can be overridden when inserting records from the API, has three +fields: `allowSave`, `includeRecordDetails`, and `runAsCurrentUser`. Supplying all +three requires a list-like structure, which may seem redundant in other cases, but is +necessary to follow. + + +```r +new_contact <- c(FirstName = "Test", LastName = "Contact-Create") +record <- sf_create(new_contact, + object_name = "Contact", + DuplicateRuleHeader = list(allowSave = TRUE, + includeRecordDetails = FALSE, + runAsCurrentUser = TRUE)) +``` + + + +Finally, you will notice in the example call that the `api_type` argument is set to "SOAP". +This is because the `DisableFeedTrackingHeader` is a control that is only available when +making calls via the SOAP API. You will receive a warning when trying to set control +parameters for APIs or operations that do not recognize that control. For example, +the following code tries to set the `BatchRetryHeader` for a call to the SOAP +API which does not acknowledge that control. That control is only used with the Bulk +1.0 API since its records as submitted in batches and automatic retry can be controlled. + + +```r +new_contact <- c(FirstName = "Test", LastName = "Contact-Create") +record <- sf_create(new_contact, + object_name = "Contact", + BatchRetryHeader = list(`Sforce-Disable-Batch-Retry` = FALSE), + api_type = "SOAP") +#> Warning in filter_valid_controls(supplied_arguments, api_type = api_type): +#> Ignoring the following controls which are not used in the SOAP API: +#> BatchRetryHeader +``` + + + +### Creating the control argument with sf_control + +If this type of control structure is new to you, take a look at the documentation for +the `glm` and `glm.control` functions. The way these two functions behave is exactly how +functions like `sf_create` and `sf_control` work with each other. As demonstrated above +you can pass any number of arbitrary controls into the function and they are all +gathered up into the control by `control = list(...)`. However, you can specify the +control directly like this: + + +```r +new_contact <- c(FirstName = "Test", LastName = "Contact-Create") +records <- sf_query("SELECT Id, Name FROM Account LIMIT 1000", + object_name = "Account", + control = sf_control(QueryOptions = list(batchSize = 100)), + api_type = "SOAP") +``` + +### Backwards compatibility for all_or_none and other named arguments + +You may already be taking advantage of the `all_or_none` or `line_ending` arguments +which are control arguments that were explicity included in functions. These argument +essentially hard coded values to pass the `AllOrNoneHeader` and `LineEndingHeader` +control parameters. Starting with the 0.1.3 release it is no longer necessary and +preferable not to have an argument like `all_or_none` listed explicity as an argument +since it can be provided in the `control` argument. Note: the `all_or_none` argument +and other explicit control arguments will still be available in **salesforcer 0.1.3** +but will provide a deprecated warning. They will be removed in the next CRAN release +of the package so it will be important to update your code now if you are explicitly +passing these arguments and see a deprecation warning. + +### Reference Links + +Below is a list of links that go directly to the control arguments (a.k.a headers) +for the different APIs. I highly recommend reading this documentation before setting +a control parameter in R so you know exactly what the behavior will be and how to +specify it in R. You may notice that some controls are not included in the R package. +Some may be added in the future if requested and some will not be added given the +scope of the package. One final note is that some arguments in the REST API, like the +"All or None" behavior is not a header, but a parameter in the API call. For this reason +you will not see it listed in the REST API Headers section, but it is set in this R package +using the `AllOrNoneHeader` argument in `sf_control` just to provide consistency between +the SOAP and REST APIs. It would be confusing to have two arguments named differently, +one for each API, but to do the exact same thing from R. For this reason, many of the +control arguments match exactly as they are listed in the SOAP API, but can be used +across other APIs even if not exactly written that way in the Salesforce documentation +referenced below. + + * **SOAP API Headers**: https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/soap_headers.htm + * **REST API Headers**: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/headers.htm + * **Bulk 1.0 API Headers**: https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/async_api_headers.htm + * **Bulk 2.0 API Headers**: None + * **Metadata API Headers**: https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_headers.htm

    character; string identifying a custom field on the object that has been set as an "External ID" field. This field is used to reference objects during upserts to determine if the record already exists in Salesforce or not.

    all_or_none

    logical; allows a call to roll back all changes unless all -records are processed successfully

    api_type

    character; one of "REST", "SOAP", "Bulk 1.0", "Bulk 2.0", or "Chatter" indicating which API to use when making the request

    control

    list; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for sf_control

    ...

    other arguments passed on to sf_bulk_operation.

    arguments passed to sf_control or further downstream +to sf_bulk_operation

    verbose
    all_or_none

    logical; allows a call to roll back all changes unless all -records are processed successfully

    control

    list; a list of parameters for controlling the behavior of +the API call being used. For more information of what parameters are available +look at the documentation for sf_control

    ...

    arguments passed to sf_control

    verbose