From b140020b1f2bb2c531ba683de291281e3e2d9e55 Mon Sep 17 00:00:00 2001 From: Shawn Garbett Date: Fri, 13 Mar 2026 09:55:18 -0500 Subject: [PATCH 1/4] Adding documentation on csv_delimiter, ability to set as argument on connection and tests #507 --- R/redcapConnection.R | 19 ++++++-- man/exportFileRepositoryListing.Rd | 14 +++--- man/redcapConnection.Rd | 6 +++ ...-020-redcapConnection-ArgumentValidation.R | 46 +++++++++++++++++++ 4 files changed, 75 insertions(+), 10 deletions(-) diff --git a/R/redcapConnection.R b/R/redcapConnection.R index ed141a95..5884f342 100644 --- a/R/redcapConnection.R +++ b/R/redcapConnection.R @@ -20,6 +20,7 @@ #' @param retry_quietly `logical(1)`. When `FALSE`, messages will #' be shown giving the status of the API calls. Defaults to `TRUE`. #' @param x `redcapConnection` object to be printed +#' @param csv_delimiter `character(1)`. The delimiter to use when reading a csv. #' @param ... arguments to pass to other methods #' #' @details @@ -65,6 +66,9 @@ #' with the external validation types (such as `sql` fields or text fields #' with BioPortal Ontology modules enabled). #' +#' One can set the csv delimiter after creating a connection using +#' a call like `rcon$set_csv_delimiter(";")`. +#' #' ## Specific to API Connections #' #' The `redcapApiConnection` object also stores the user preferences for @@ -83,6 +87,7 @@ #' Additional Curl option can be set in the `config` argument. See the documentation #' for [curl::handle_setopt] for more curl options. #' +#' #' ## Specific to Offline Connections #' #' "Offline connections" are a tool designed to provide the users without @@ -174,6 +179,7 @@ redcapConnection <- function(url = getOption('redcap_api_url'), retries = 5, retry_interval = 2^(seq_len(retries)), retry_quietly = TRUE, + csv_delimiter = ",", ...) { coll <- checkmate::makeAssertCollection() @@ -207,6 +213,12 @@ redcapConnection <- function(url = getOption('redcap_api_url'), any.missing = FALSE, add = coll) + checkmate::assert_character(x = csv_delimiter, + len = 1, + null.ok = FALSE, + any.missing = FALSE, + add = coll) + checkmate::reportAssertions(coll) config <- if(is.null(config)) .curlConfig(url, token) else @@ -405,15 +417,16 @@ redcapConnection <- function(url = getOption('redcap_api_url'), set_csv_delimiter = function(d) { checkmate::assert_choice( x = d, - choices = c(",", ";", "\t", " ", "|", "^") + choices = c(",", ";", "\t", " ", "|", "^"), + .var.name='csv_delimiter' ) csv_delim <<- d }, csv_delimiter = function() csv_delim ) class(rc) <- c("redcapApiConnection", "redcapConnection") - # Initialize csv_delimiter with default - rc$set_csv_delimiter(",") + + rc$set_csv_delimiter(csv_delimiter) rc } diff --git a/man/exportFileRepositoryListing.Rd b/man/exportFileRepositoryListing.Rd index cf8f7e51..5816ace5 100644 --- a/man/exportFileRepositoryListing.Rd +++ b/man/exportFileRepositoryListing.Rd @@ -49,20 +49,20 @@ include contents of subfolders. } \examples{ \dontrun{ -unlockREDCap(connections = c(rcon = "project_alias"), - url = "your_redcap_url", - keyring = "API_KEYs", +unlockREDCap(connections = c(rcon = "project_alias"), + url = "your_redcap_url", + keyring = "API_KEYs", envir = globalenv()) - + # Export the top-level listing of the File Repository exportFileRepositoryListing(rcon) # Export the complete listing of the File Repository -exportFileRepositoryListing(rcon, +exportFileRepositoryListing(rcon, recursive = TRUE) - + # Export the listing of a subfolder in the File Repository -exportFileRepositoryListing(rcon, +exportFileRepositoryListing(rcon, folder_id = 12345) } diff --git a/man/redcapConnection.Rd b/man/redcapConnection.Rd index 6fcfb046..02fcd392 100644 --- a/man/redcapConnection.Rd +++ b/man/redcapConnection.Rd @@ -14,6 +14,7 @@ redcapConnection( retries = 5, retry_interval = 2^(seq_len(retries)), retry_quietly = TRUE, + csv_delimiter = ",", ... ) @@ -64,6 +65,8 @@ the number of retries.} \item{retry_quietly}{\code{logical(1)}. When \code{FALSE}, messages will be shown giving the status of the API calls. Defaults to \code{TRUE}.} +\item{csv_delimiter}{\code{character(1)}. The delimiter to use when reading a csv.} + \item{...}{arguments to pass to other methods} \item{x}{\code{redcapConnection} object to be printed} @@ -175,6 +178,9 @@ the entire cache and refresh the entire cache, respectively. The \code{externalCoding} elements relate to the code-label mappings of text fields with the external validation types (such as \code{sql} fields or text fields with BioPortal Ontology modules enabled). + +One can set the csv delimiter after creating a connection using +a call like \code{rcon$set_csv_delimiter(";")}. \subsection{Specific to API Connections}{ The \code{redcapApiConnection} object also stores the user preferences for diff --git a/tests/testthat/test-020-redcapConnection-ArgumentValidation.R b/tests/testthat/test-020-redcapConnection-ArgumentValidation.R index a953f5d8..7501edac 100644 --- a/tests/testthat/test-020-redcapConnection-ArgumentValidation.R +++ b/tests/testthat/test-020-redcapConnection-ArgumentValidation.R @@ -96,3 +96,49 @@ test_that( expect_no_error(connectAndCheck(rcon$token, substr(rcon$url, 1, nchar(rcon$url)-1))) } ) + +test_that( + "csv_delimiter cannot be NULL", + { + expect_error(redcapConnection(url = url, token=API_KEY, csv_delimiter=NULL), + "'csv_delimiter': Must be of type 'character'") + } +) + +test_that( + "csv_delimiter cannot be NA", + { + expect_error(redcapConnection(url = url, token=API_KEY, csv_delimiter=NA), + "'csv_delimiter': Contains missing values") + } +) + +test_that( + "csv_delimiter defaults to ','", + { + expect_no_error(testcon <- redcapConnection(url = url, token=API_KEY)) + expect_equal(testcon$csv_delimiter(), ",") + } +) + +test_that( + "csv_delimiter can be set afterward to ';'", + { + expect_no_error(testcon <- redcapConnection(url = url, token=API_KEY)) + expect_no_error(testcon$set_csv_delimiter(";")) + expect_equal(testcon$csv_delimiter(), ";") + } +) + +test_that( + "csv_delimiter cannot be set crazy value", + { + expect_error(redcapConnection(url = url, token=API_KEY, csv_delimiter="jimmyjohn"), + "csv_delimiter") + + expect_no_error(testcon <- redcapConnection(url = url, token=API_KEY)) + + expect_error(testcon$set_csv_delimiter("jimmyjohn"), "csv_delimiter") + + } +) From 6d78e300c1d5769d308cc717ba9680d7d94f758a Mon Sep 17 00:00:00 2001 From: Shawn Garbett Date: Fri, 13 Mar 2026 10:01:13 -0500 Subject: [PATCH 2/4] Removing hard coded csv delimiter from exportExternalCoding #507 --- R/exportExternalCoding.R | 168 +++++++++++++++++++-------------------- 1 file changed, 84 insertions(+), 84 deletions(-) diff --git a/R/exportExternalCoding.R b/R/exportExternalCoding.R index 20ec1a2b..20d6c510 100644 --- a/R/exportExternalCoding.R +++ b/R/exportExternalCoding.R @@ -1,48 +1,48 @@ #' @name exportExternalCoding #' @title Export Codebook Mappings for Fields with External Dependencies -#' -#' @description These methods enable `redcapAPI` to obtain a mapping of -#' codes and associated labels for fields that have external dependencies. -#' The fields include SQL fields (dependent on another project) or +#' +#' @description These methods enable `redcapAPI` to obtain a mapping of +#' codes and associated labels for fields that have external dependencies. +#' The fields include SQL fields (dependent on another project) or #' fields that utilize the BioPortal Ontology modules. -#' +#' #' @inheritParams common-rcon-arg #' @inheritParams common-dot-args #' @inheritParams common-api-args #' @inheritParams recordsTypedMethods -#' -#' @details These methods operate by executing two API calls to export first the -#' coded values and then the labeled values of fields with external -#' dependencies. The two exports are then used to generate the code-label +#' +#' @details These methods operate by executing two API calls to export first the +#' coded values and then the labeled values of fields with external +#' dependencies. The two exports are then used to generate the code-label #' mappings for use in casting data. -#' -#' Fields of type `sql` are dropdown fields that are populated by a SQL -#' query to another project. -#' -#' Fields of type `bioportal` are text fields that have the BioPortal +#' +#' Fields of type `sql` are dropdown fields that are populated by a SQL +#' query to another project. +#' +#' Fields of type `bioportal` are text fields that have the BioPortal #' Ontology module enabled as the validation method. -#' +#' #' @return -#' Returns a named list of named character vectors. -#' -#' Each element is in the list is named for the field it maps. -#' -#' The character vectors are name-value pairs where the name is the labeled +#' Returns a named list of named character vectors. +#' +#' Each element is in the list is named for the field it maps. +#' +#' The character vectors are name-value pairs where the name is the labeled #' data and the value is the coded data. -#' +#' #' @examples #' \dontrun{ -#' unlockREDCap(connections = c(rcon = "project_alias"), -#' url = "your_redcap_url", -#' keyring = "API_KEYs", +#' unlockREDCap(connections = c(rcon = "project_alias"), +#' url = "your_redcap_url", +#' keyring = "API_KEYs", #' envir = globalenv()) -#' +#' #' exportExternalCoding(rcon) #' } -#' +#' -exportExternalCoding <- function(rcon, - fields, +exportExternalCoding <- function(rcon, + fields, ...){ UseMethod("exportExternalCoding") } @@ -50,116 +50,116 @@ exportExternalCoding <- function(rcon, #' @rdname exportExternalCoding #' @export -exportExternalCoding.redcapApiConnection <- function(rcon, - fields = NULL, - ..., +exportExternalCoding.redcapApiConnection <- function(rcon, + fields = NULL, + ..., batch_size = 1000) { ################################################################### # Argument Validation #### - + coll <- checkmate::makeAssertCollection() - - checkmate::assert_class(x = rcon, - class = "redcapConnection", + + checkmate::assert_class(x = rcon, + class = "redcapConnection", add = coll) - - checkmate::assert_character(x = fields, - null.ok = TRUE, - any.missing = FALSE, + + checkmate::assert_character(x = fields, + null.ok = TRUE, + any.missing = FALSE, add = coll) - - checkmate::assert_integerish(x = batch_size, + + checkmate::assert_integerish(x = batch_size, len = 1, - lower = 1, - null.ok = TRUE, - any.missing = FALSE, + lower = 1, + null.ok = TRUE, + any.missing = FALSE, add = coll) checkmate::reportAssertions(coll) - - checkmate::assert_subset(x = fields, - choices = rcon$metadata()$field_name, + + checkmate::assert_subset(x = fields, + choices = rcon$metadata()$field_name, add = coll) - + checkmate::reportAssertions(coll) - + ################################################################### # Functionality #### MetaData <- rcon$metadata() - external_fields <- - MetaData$field_name[grepl("BIOPORTAL", - MetaData$select_choices_or_calculations, - ignore.case = TRUE) | + external_fields <- + MetaData$field_name[grepl("BIOPORTAL", + MetaData$select_choices_or_calculations, + ignore.case = TRUE) | (!is.na(MetaData$field_type) & MetaData$field_type == "sql")] - + if (is.null(fields)){ fields <- external_fields } else { fields <- fields[fields %in% external_fields] } - + if (length(fields) == 0){ return(list()) } - - body <- c(list(content = "record", - format = "csv", - returnFormat = "csv", - type = "flat", - rawOrLabel = "raw"), + + body <- c(list(content = "record", + format = "csv", + returnFormat = "csv", + type = "flat", + rawOrLabel = "raw"), vectorToApiBodyList(fields, "fields")) - Code <- + Code <- if (!is.null(batch_size)){ - .exportRecordsTyped_Batched(rcon = rcon, - body = body, + .exportRecordsTyped_Batched(rcon = rcon, + body = body, records = NULL, - csv_delimiter = ",", + csv_delimiter = rcon$csv_delimiter(), batch_size = batch_size, ...) } else { - .exportRecordsTyped_Unbatched(rcon = rcon, - body = body, + .exportRecordsTyped_Unbatched(rcon = rcon, + body = body, records = NULL, - csv_delimiter = ",", + csv_delimiter = rcon$csv_delimiter(), ...) } - + body$rawOrLabel <- "label" - - Label <- + + Label <- if (!is.null(batch_size)){ - .exportRecordsTyped_Batched(rcon = rcon, - body = body, + .exportRecordsTyped_Batched(rcon = rcon, + body = body, records = NULL, - csv_delimiter = ",", + csv_delimiter = rcon$csv_delimiter(), batch_size = batch_size, ...) } else { - .exportRecordsTyped_Unbatched(rcon = rcon, - body = body, - records = NULL, - csv_delimiter = ",", + .exportRecordsTyped_Unbatched(rcon = rcon, + body = body, + records = NULL, + csv_delimiter = rcon$csv_delimiter(), ...) } - + External <- vector("list", length(fields)) names(External) <- fields - + for (f in fields){ - ThisCode <- data.frame(code = Code[[f]], - label = Label[[f]], + ThisCode <- data.frame(code = Code[[f]], + label = Label[[f]], stringsAsFactors = FALSE) ThisCode <- ThisCode[!duplicated(ThisCode), ] ThisCode <- ThisCode[!is.na(ThisCode$code), ] mapping <- ThisCode$code names(mapping) <- ThisCode$label - + External[[f]] <- mapping } - + External } From ce27db537e2cf03872f6265337ce0c9c55fc4fc6 Mon Sep 17 00:00:00 2001 From: Shawn Garbett Date: Fri, 13 Mar 2026 11:44:05 -0500 Subject: [PATCH 3/4] Added csv_delimiter to every call I could find #507 --- R/createFileRepositoryFolder.R | 2 +- R/exportArms.R | 22 ++--- R/exportEvents.R | 40 ++++---- R/exportFieldNames.R | 89 +++++++++--------- R/exportInstruments.R | 28 +++--- R/exportLogging.R | 136 +++++++++++++-------------- R/exportMappings.R | 38 ++++---- R/exportMetaData.R | 40 ++++---- R/exportRepeatingInstrumentsEvents.R | 26 ++--- R/exportSurveyParticipants.R | 58 ++++++------ R/exportUserDagAssignments.R | 24 ++--- R/exportUserRoleAssignments.R | 2 +- R/exportUserRoles.R | 94 +++++++++--------- R/exportUsers.R | 2 +- R/importRecords.R | 2 +- 15 files changed, 300 insertions(+), 303 deletions(-) diff --git a/R/createFileRepositoryFolder.R b/R/createFileRepositoryFolder.R index 31b94bba..18b97012 100644 --- a/R/createFileRepositoryFolder.R +++ b/R/createFileRepositoryFolder.R @@ -153,7 +153,7 @@ createFileRepositoryFolder.redcapApiConnection <- function(rcon, rcon$flush_fileRepository() # Prepare Output -------------------------------------------------- - NewFolder <- as.data.frame(response) + NewFolder <- as.data.frame(response, sep = rcon$csv_delimiter()) NewFolder$name <- rep(name, nrow(NewFolder)) diff --git a/R/exportArms.R b/R/exportArms.R index 0fb1c1ee..964afd55 100644 --- a/R/exportArms.R +++ b/R/exportArms.R @@ -10,35 +10,35 @@ exportArms <- function(rcon, ...){ #' @order 4 #' @export -exportArms.redcapApiConnection <- function(rcon, - arms = character(0), +exportArms.redcapApiConnection <- function(rcon, + arms = character(0), ...) { if (is.numeric(arms)) arms <- as.character(arms) - + # Argument Validation --------------------------------------------- coll <- checkmate::makeAssertCollection() checkmate::assert_class(x = rcon, classes = "redcapApiConnection", add = coll) - + checkmate::assert_character(x = arms, add = coll) checkmate::reportAssertions(coll) - + if(rcon$projectInformation()$is_longitudinal == 0){ return(REDCAP_ARMS_STRUCTURE) # defined in redcapDataStructure.R } - + # Build the body list --------------------------------------------- - body <- c(list(content = 'arm', - format = 'csv', - returnFormat = 'csv'), - vectorToApiBodyList(arms, + body <- c(list(content = 'arm', + format = 'csv', + returnFormat = 'csv'), + vectorToApiBodyList(arms, parameter_name = "arms")) # API Call -------------------------------------------------------- - as.data.frame(makeApiCall(rcon, body, ...)) + as.data.frame(makeApiCall(rcon, body, ...), sep = rcon$csv_delimiter()) } diff --git a/R/exportEvents.R b/R/exportEvents.R index 3b30d248..c0d1736f 100644 --- a/R/exportEvents.R +++ b/R/exportEvents.R @@ -2,7 +2,7 @@ #' @order 1 #' @export -exportEvents <- function(rcon, +exportEvents <- function(rcon, ...){ UseMethod("exportEvents") } @@ -11,53 +11,53 @@ exportEvents <- function(rcon, #' @order 4 #' @export -exportEvents.redcapApiConnection <- function(rcon, - arms = NULL, +exportEvents.redcapApiConnection <- function(rcon, + arms = NULL, ...) { if (is.character(arms)) arms <- as.numeric(arms) - + ################################################################## - # Argument Validation + # Argument Validation coll <- checkmate::makeAssertCollection() - + checkmate::assert_class(x = rcon, classes = "redcapApiConnection", add = coll) - + checkmate::assert_integerish(x = arms, null.ok = TRUE, add = coll) checkmate::reportAssertions(coll) - + Arms <- rcon$arms() - - checkmate::assert_subset(x = arms, - choices = Arms$arm_num, + + checkmate::assert_subset(x = arms, + choices = Arms$arm_num, add = coll) - + checkmate::reportAssertions(coll) - + ################################################################## # Return for Classical projects - + if (rcon$projectInformation()$is_longitudinal == 0){ return(REDCAP_EVENT_STRUCTURE) # Defined in redcapDataStructure.R } - + ################################################################## # Make the Body List - - body <- list(content = 'event', - format = 'csv', + + body <- list(content = 'event', + format = 'csv', returnFormat = 'csv') - body <- c(body, + body <- c(body, vectorToApiBodyList(arms, "arms")) ################################################################## # Call the API - response <- as.data.frame(makeApiCall(rcon, body, ...)) + response <- as.data.frame(makeApiCall(rcon, body, ...), sep = rcon$csv_delimiter()) if(nrow(response) == 0) REDCAP_EVENT_STRUCTURE else response } diff --git a/R/exportFieldNames.R b/R/exportFieldNames.R index 02403bbc..4c9926eb 100644 --- a/R/exportFieldNames.R +++ b/R/exportFieldNames.R @@ -1,75 +1,75 @@ #' @name exportFieldNames #' @title Export the Complete Field Names for a REDCap Project -#' -#' @description This method enables the user to access the complete field +#' +#' @description This method enables the user to access the complete field #' names utilized during export and import methods. These are expecially #' relevant when working with checkbox fields. -#' +#' #' @inheritParams common-rcon-arg #' @inheritParams common-dot-args #' @inheritParams common-api-args -#' @param fields `NULL` or `character`. Field name to be returned. By +#' @param fields `NULL` or `character`. Field name to be returned. By #' default, all fields are returned. -#' +#' #' @details #' `exportFieldNames` returns a data frame of the field names the user -#' may use when performing export and import functions. This is most useful -#' when working with checkbox fields, which have a different field name than -#' the one used in the Meta Data. The exported/imported field names for +#' may use when performing export and import functions. This is most useful +#' when working with checkbox fields, which have a different field name than +#' the one used in the Meta Data. The exported/imported field names for #' checkbox fields have the pattern `[field_name]___[coded_checkbox_value]` #' (there are exactly three underscores separating the field name and the #' coded value). -#' +#' #' Fields of types "calc", "file", and "descriptive" are not included in the #' export. (Signature fields also have the "file" type and are not included) -#' -#' @return -#' `exportFieldNames` returns a data frame with the columns: -#' +#' +#' @return +#' `exportFieldNames` returns a data frame with the columns: +#' #' | | | #' |-----------------------|--------------------------------------------------| #' | `original_field_name` | The field name as recorded in the data dictionary| #' | `choice_value` | represents the raw coded value for a checkbox choice. For non-checkbox fields, this will always be `NA`.| #' | `export_field_name` | The field name specific to the field. For non-checkbox fields, this is the same as `original_field_name`. For checkbox fields, it is the field name appended with `___[choice_value]`. | -#' -#' @seealso +#' +#' @seealso #' [exportMetaData()],\cr #' [importMetaData()], \cr #' [exportInstruments()],\cr #' [exportMappings()],\cr #' [importMappings()], \cr #' [exportPdf()] -#' +#' #' @examples #' \dontrun{ -#' unlockREDCap(connections = c(rcon = "project_alias"), -#' url = "your_redcap_url", -#' keyring = "API_KEYs", +#' unlockREDCap(connections = c(rcon = "project_alias"), +#' url = "your_redcap_url", +#' keyring = "API_KEYs", #' envir = globalenv()) #' #' # Export all of the field names #' exportFieldNames(rcon) -#' +#' #' # Export MetaData for a specific field -#' exportFieldNames(rcon, +#' exportFieldNames(rcon, #' fields = "checkbox_test") #' } #' @usage NULL #' @order 0 # dummy function to control the order of arguments in the help file. -exportFieldNamesArgs <- function(rcon, - fields, +exportFieldNamesArgs <- function(rcon, + fields, ...) { NULL } # Complete documentation in documentation.R -#' @rdname exportFieldNames +#' @rdname exportFieldNames #' @export -exportFieldNames <- function(rcon, +exportFieldNames <- function(rcon, ...){ UseMethod("exportFieldNames") } @@ -77,37 +77,37 @@ exportFieldNames <- function(rcon, #' @rdname exportFieldNames #' @export -exportFieldNames.redcapApiConnection <- function(rcon, - fields = character(0), +exportFieldNames.redcapApiConnection <- function(rcon, + fields = character(0), ...) { # Argument validation --------------------------------------------- coll <- checkmate::makeAssertCollection() - - checkmate::assert_class(x = rcon, - classes = "redcapApiConnection", + + checkmate::assert_class(x = rcon, + classes = "redcapApiConnection", add = coll) - + checkmate::assert_character(x = fields, add = coll) checkmate::reportAssertions(coll) - + if (length(fields) > 0){ - .exportFieldNames_validateFieldName(fields = fields, - rcon = rcon, + .exportFieldNames_validateFieldName(fields = fields, + rcon = rcon, coll = coll) } if (length(fields) > 0){ - result <- - lapply(fields, + result <- + lapply(fields, FUN = function(f, r, ...) .exportFieldNamesApiCall(r, f, ...), r = rcon, ...) do.call("rbind", result) } else { - .exportFieldNamesApiCall(rcon, fields = fields, ...) + .exportFieldNamesApiCall(rcon, fields = fields, ...) } } @@ -116,10 +116,10 @@ exportFieldNames.redcapApiConnection <- function(rcon, .exportFieldNames_validateFieldName <- function(fields, rcon, coll){ # Get project metadata MetaData <- rcon$metadata() - + no_match <- fields[!fields %in% MetaData$field_name] if (length(no_match) > 0){ - coll$push(sprintf("Field does not exist in the database: %s", + coll$push(sprintf("Field does not exist in the database: %s", no_match)) checkmate::reportAssertions(coll) } @@ -127,14 +127,11 @@ exportFieldNames.redcapApiConnection <- function(rcon, .exportFieldNamesApiCall <- function(rcon, fields, ...){ # Build the Body List --------------------------------------------- - body <- list(content = 'exportFieldNames', + body <- list(content = 'exportFieldNames', format = 'csv', - returnFormat = 'csv', + returnFormat = 'csv', field = fields) - + # Make the API Call ----------------------------------------------- - read.csv(text = as.character(makeApiCall(rcon, body, ...)), - na.strings = "", - stringsAsFactors = FALSE, - sep = rcon$csv_delimiter()) + as.data.frame(makeApiCall(rcon, body, ...), sep = rcon$csv_delimiter()) } diff --git a/R/exportInstruments.R b/R/exportInstruments.R index c5a2376b..431116d1 100644 --- a/R/exportInstruments.R +++ b/R/exportInstruments.R @@ -7,10 +7,10 @@ #' @inheritParams common-rcon-arg #' @inheritParams common-dot-args #' @inheritParams common-api-args -#' -#' @return +#' +#' @return #' Returns a data frame with the columns: -#' +#' #' | | | #' |-------------------|---------------------------------------| #' | `instrument_name` | The REDCap generated instrument name. | @@ -24,14 +24,14 @@ #' [exportMappings()],\cr #' [importMappings()], \cr #' [exportPdf()] -#' +#' #' @examples #' \dontrun{ -#' unlockREDCap(connections = c(rcon = "project_alias"), -#' url = "your_redcap_url", -#' keyring = "API_KEYs", +#' unlockREDCap(connections = c(rcon = "project_alias"), +#' url = "your_redcap_url", +#' keyring = "API_KEYs", #' envir = globalenv()) -#' +#' #' exportInstruments(rcon) #' } #' @@ -44,26 +44,26 @@ exportInstruments <- function(rcon, ...){ #' @rdname exportInstruments #' @export -exportInstruments.redcapApiConnection <- function(rcon, +exportInstruments.redcapApiConnection <- function(rcon, ...) { ################################################################## - # Argument Validation + # Argument Validation coll <- checkmate::makeAssertCollection() - + checkmate::assert_class(x = rcon, classes = "redcapApiConnection", add = coll) checkmate::reportAssertions(coll) - + ################################################################## # Make Body List - + body <- list(content = 'instrument', format = 'csv') ################################################################## # Call the API - as.data.frame(makeApiCall(rcon, body, ...)) + as.data.frame(makeApiCall(rcon, body, ...), sep = rcon$csv_delimiter()) } diff --git a/R/exportLogging.R b/R/exportLogging.R index f0b88183..983f3d03 100644 --- a/R/exportLogging.R +++ b/R/exportLogging.R @@ -1,14 +1,14 @@ #' @name exportLogging #' @title Export Logging Records -#' -#' @description These methods enable to user to export the logging -#' (audit trail) of all changes made to a project, including data exports, +#' +#' @description These methods enable to user to export the logging +#' (audit trail) of all changes made to a project, including data exports, #' data changes, project metadata changes, modification of user rights, etc. -#' +#' #' @inheritParams common-rcon-arg #' @inheritParams common-dot-args #' @inheritParams common-api-args -#' @param logtype `character(0/1)`. The log event types to export. +#' @param logtype `character(0/1)`. The log event types to export. #' When the length is zero, all event types are exported. Otherwise, it #' must be one of #' `c("export", "manage", "user", "record", "record_add", "record_edit", "record_delete", "lock_record", "page_view")` @@ -16,14 +16,14 @@ #' logs for all users are returned. #' @param record `character(0/1)`. Record ID for which logs are to be returned. #' By default, logs are returned for all records. -#' @param dag `character(0/1)`. Data access group ID for which to return logs. +#' @param dag `character(0/1)`. Data access group ID for which to return logs. #' By default, logs are returned for all data access groups. -#' @param beginTime `POSIXct(0/1)`. When given, only +#' @param beginTime `POSIXct(0/1)`. When given, only #' logs recorded on or after this time will be returned. The time specified #' is rounded to minutes and ignores the timezone. This can cause issues #' if the caller and server computers are configured in different timezones. #' Least surprising behavior is making sure the date specified is encoded -#' in the timezone of the REDCap server. +#' in the timezone of the REDCap server. #' @param endTime `POSIXct(0/1)`. When given, only logs #' recorded on or before this time will be returned. If using batchInterval #' it will only be before this time. See `beginTime` for details on time @@ -31,10 +31,10 @@ #' @param batchInterval `integerish(1)`. When provided will #' batch log pulls to intervals of this many days. Requires #' that beginTime is specified. -#' -#' @return +#' +#' @return #' Returns a data frame with columns -#' +#' #' | | | #' |------------|--------------------------------------| #' | `timestamp` | The date/time of the logging record. | @@ -43,34 +43,34 @@ #' | `details` | Details of the action being logged. | #' | `record` | The record ID associated with the action being logged. When not related to a record, this will be `NA` | #' -#' +#' #' @examples #' \dontrun{ -#' unlockREDCap(connections = c(rcon = "project_alias"), -#' url = "your_redcap_url", -#' keyring = "API_KEYs", +#' unlockREDCap(connections = c(rcon = "project_alias"), +#' url = "your_redcap_url", +#' keyring = "API_KEYs", #' envir = globalenv()) -#' -#' # Export all of the logging events +#' +#' # Export all of the logging events #' exportLogging(rcon) -#' +#' #' # Export all of the events for record '2' -#' exportLogging(rcon, +#' exportLogging(rcon, #' record = "2") -#' +#' #' # Export all of the events where a record was deleted -#' exportLoging(rcon, +#' exportLoging(rcon, #' logtype = "record_delete") #' } #' @export -#' +#' exportLogging <- function( - rcon, - logtype = character(0), - user = character(0), - record = character(0), - dag = character(0), - beginTime = as.POSIXct(character(0)), + rcon, + logtype = character(0), + user = character(0), + record = character(0), + dag = character(0), + beginTime = as.POSIXct(character(0)), endTime = as.POSIXct(character(0)), batchInterval = NULL, ...) @@ -81,61 +81,61 @@ exportLogging <- function( #' @rdname exportLogging #' @export exportLogging.redcapApiConnection <- function( - rcon, - logtype = character(0), - user = character(0), - record = character(0), - dag = character(0), - beginTime = as.POSIXct(character(0)), + rcon, + logtype = character(0), + user = character(0), + record = character(0), + dag = character(0), + beginTime = as.POSIXct(character(0)), endTime = as.POSIXct(character(0)), batchInterval = NULL, ...) { # Argument checks ------------------------------------------------- coll <- checkmate::makeAssertCollection() - + checkmate::assert_class(x = rcon, classes = "redcapApiConnection", add = coll) - + if (length(logtype) == 1) { - logtype <- checkmate::matchArg(x = logtype, - choices = c("export", "manage", "user", "record", - "record_add", "record_edit", "record_delete", + logtype <- checkmate::matchArg(x = logtype, + choices = c("export", "manage", "user", "record", + "record_add", "record_edit", "record_delete", "lock_record", "page_view"), - add = coll, + add = coll, .var.name = "logtype") } - + checkmate::assert_character(x = user, max.len = 1, add = coll) - - checkmate::assert_character(x = record, + + checkmate::assert_character(x = record, max.len = 1, add = coll) - - checkmate::assert_character(x = dag, + + checkmate::assert_character(x = dag, max.len = 1, add = coll) - + checkmate::assert_posixct(x = beginTime, min.len = if(is.null(batchInterval)) NULL else 1, - max.len = 1, + max.len = 1, add = coll) - - checkmate::assert_posixct(x = endTime, - max.len = 1, + + checkmate::assert_posixct(x = endTime, + max.len = 1, add = coll) - + checkmate::assert_integerish(x = batchInterval, max.len = 1, null.ok = TRUE, add = coll) checkmate::reportAssertions(coll) - + # Batch call if(!is.null(batchInterval)) { @@ -148,12 +148,12 @@ exportLogging.redcapApiConnection <- function( { cutTime <- min(x+24*60*60*batchInterval, endTime) data <- exportLogging( - rcon, + rcon, logtype, user, record, dag, - beginTime = x-60, + beginTime = x-60, endTime = cutTime+60 ) # Compensation for poor interface design @@ -162,32 +162,32 @@ exportLogging.redcapApiConnection <- function( ) )) } - + body <- list( - content = 'log', - format = 'csv', - returnFormat = 'csv', - logtype = logtype, - user = user, - record = record, - dag = dag, - beginTime = format(beginTime, format = "%Y-%m-%d %H:%M"), + content = 'log', + format = 'csv', + returnFormat = 'csv', + logtype = logtype, + user = user, + record = record, + dag = dag, + beginTime = format(beginTime, format = "%Y-%m-%d %H:%M"), endTime = format(endTime, format = "%Y-%m-%d %H:%M") ) - Log <- as.data.frame(makeApiCall(rcon, body, ...)) + Log <- as.data.frame(makeApiCall(rcon, body, ...), sep = rcon$csv_delimiter()) timezone <- if(length(beginTime) > 0 && !is.null(attr(beginTime, "tzone"))) { attr(beginTime, "tzone") - } else if(length(beginTime) == 0 && + } else if(length(beginTime) == 0 && length(endTime > 0) && !is.null(attr(endTime, "tzone"))) { attr(endTime, "tzone") - } else + } else { Sys.timezone() } @@ -195,6 +195,6 @@ exportLogging.redcapApiConnection <- function( Log$timestamp <- as.POSIXct(Log$timestamp, tz = timezone, format = "%Y-%m-%d %H:%M") - + Log -} \ No newline at end of file +} diff --git a/R/exportMappings.R b/R/exportMappings.R index 4fbbfb85..d09cb199 100644 --- a/R/exportMappings.R +++ b/R/exportMappings.R @@ -1,9 +1,9 @@ -#' @describeIn mappingMethods Export instrument-event mappings. +#' @describeIn mappingMethods Export instrument-event mappings. #' @order 1 -#' @export +#' @export -exportMappings <- function(rcon, - arms, +exportMappings <- function(rcon, + arms, ...){ UseMethod("exportMappings") } @@ -12,46 +12,46 @@ exportMappings <- function(rcon, #' @order 3 #' @export -exportMappings.redcapApiConnection <- function(rcon, - arms = NULL, +exportMappings.redcapApiConnection <- function(rcon, + arms = NULL, ...) { if (is.character(arms)) arms <- as.numeric(arms) - + ################################################################## # Argument Validation - + coll <- checkmate::makeAssertCollection() - + checkmate::assert_class(x = rcon, classes = "redcapApiConnection", add = coll) - + checkmate::assert_character(x = arms, null.ok = TRUE, any.missing = FALSE, add = coll) checkmate::reportAssertions(coll) - + ################################################################## # Return empty data frame for classical projects - + if (rcon$projectInformation()$is_longitudinal == 0){ - return(data.frame(arm_num = numeric(0), - unique_event_name = character(0), + return(data.frame(arm_num = numeric(0), + unique_event_name = character(0), form = character(0))) } - + ################################################################## # Make Body List - body <- list(content = 'formEventMapping', + body <- list(content = 'formEventMapping', format = 'csv') - - body <- c(body, + + body <- c(body, vectorToApiBodyList(arms, "arms")) ################################################################## # Call the API - as.data.frame(makeApiCall(rcon, body, ...)) + as.data.frame(makeApiCall(rcon, body, ...), sep = rcon$csv_delimiter()) } diff --git a/R/exportMetaData.R b/R/exportMetaData.R index 9b276149..87ad0038 100644 --- a/R/exportMetaData.R +++ b/R/exportMetaData.R @@ -10,49 +10,49 @@ exportMetaData <- function(rcon, ...){ #' @rdname metaDataMethods #' @export -exportMetaData.redcapApiConnection <- function(rcon, - fields = character(0), +exportMetaData.redcapApiConnection <- function(rcon, + fields = character(0), forms = character(0), ...) { # Argument validation --------------------------------------------- coll <- checkmate::makeAssertCollection() - + checkmate::assert_class(x = rcon, classes = "redcapApiConnection", add = coll) - - checkmate::assert_character(x = fields, + + checkmate::assert_character(x = fields, add = coll) - - checkmate::assert_character(x = forms, + + checkmate::assert_character(x = forms, add = coll) checkmate::reportAssertions(coll) - + if (!is.null(fields)){ - checkmate::assert_subset(x = fields, - choices = rcon$metadata()$field_name, + checkmate::assert_subset(x = fields, + choices = rcon$metadata()$field_name, add = coll) } - + if (!is.null(forms)){ - checkmate::assert_subset(x = forms, - choices = rcon$instruments()$instrument_name, + checkmate::assert_subset(x = forms, + choices = rcon$instruments()$instrument_name, add = coll) } - + checkmate::reportAssertions(coll) - + # Build the Body List --------------------------------------------- body <- c(list(content = "metadata", format = "csv", - returnFormat = "csv"), - vectorToApiBodyList(fields, - parameter_name = "fields"), - vectorToApiBodyList(forms, + returnFormat = "csv"), + vectorToApiBodyList(fields, + parameter_name = "fields"), + vectorToApiBodyList(forms, parameter_name = "forms")) # API Call -------------------------------------------------------- - as.data.frame(makeApiCall(rcon, body, ...)) + as.data.frame(makeApiCall(rcon, body, ...), sep = rcon$csv_delimiter()) } diff --git a/R/exportRepeatingInstrumentsEvents.R b/R/exportRepeatingInstrumentsEvents.R index db879e77..2f8bc80c 100644 --- a/R/exportRepeatingInstrumentsEvents.R +++ b/R/exportRepeatingInstrumentsEvents.R @@ -2,7 +2,7 @@ #' @order 1 #' @export -exportRepeatingInstrumentsEvents <- function(rcon, +exportRepeatingInstrumentsEvents <- function(rcon, ...){ UseMethod("exportRepeatingInstrumentsEvents") } @@ -11,36 +11,36 @@ exportRepeatingInstrumentsEvents <- function(rcon, #' @order 3 #' @export -exportRepeatingInstrumentsEvents.redcapApiConnection <- function(rcon, +exportRepeatingInstrumentsEvents.redcapApiConnection <- function(rcon, ...) { ################################################################### # Argument Validation #### - + coll <- checkmate::makeAssertCollection() - - checkmate::assert_class(x = rcon, - classes = c("redcapApiConnection"), + + checkmate::assert_class(x = rcon, + classes = c("redcapApiConnection"), add = coll) checkmate::reportAssertions(coll) - + checkmate::reportAssertions(coll) - + ################################################################### # Handle Project w/o repeating instruments #### - + if (rcon$projectInformation()$has_repeating_instruments_or_events == 0){ return(REDCAP_REPEAT_INSTRUMENT_STRUCTURE) } - + ################################################################### # Make the Body List #### - - body <- list(content = "repeatingFormsEvents", + + body <- list(content = "repeatingFormsEvents", format = "csv") ################################################################### # Call the API #### - as.data.frame(makeApiCall(rcon, body, ...)) + as.data.frame(makeApiCall(rcon, body, ...), sep = rcon$csv_delimiter()) } diff --git a/R/exportSurveyParticipants.R b/R/exportSurveyParticipants.R index 50284ffb..bb3d8418 100644 --- a/R/exportSurveyParticipants.R +++ b/R/exportSurveyParticipants.R @@ -2,8 +2,8 @@ #' @order 1 #' @export -exportSurveyParticipants <- function(rcon, - instrument, +exportSurveyParticipants <- function(rcon, + instrument, event, ...){ UseMethod("exportSurveyParticipants") } @@ -12,56 +12,56 @@ exportSurveyParticipants <- function(rcon, #' @order 5 #' @export -exportSurveyParticipants.redcapApiConnection <- function(rcon, - instrument = NULL, +exportSurveyParticipants.redcapApiConnection <- function(rcon, + instrument = NULL, event = NULL, ...) { ################################################################## # Argument Validation - + coll <- checkmate::makeAssertCollection() - - checkmate::assert_class(x = rcon, - classes = "redcapApiConnection", + + checkmate::assert_class(x = rcon, + classes = "redcapApiConnection", add = coll) - - checkmate::assert_character(x = instrument, - len = 1, - any.miss = FALSE, - null.ok = TRUE, + + checkmate::assert_character(x = instrument, + len = 1, + any.miss = FALSE, + null.ok = TRUE, add = coll) - - checkmate::assert_character(x = event, - len = 1, - any.miss = FALSE, - null.ok = TRUE, + + checkmate::assert_character(x = event, + len = 1, + any.miss = FALSE, + null.ok = TRUE, add = coll) checkmate::reportAssertions(coll) - - checkmate::assert_subset(x = instrument, - choices = rcon$instruments()$instrument_name, + + checkmate::assert_subset(x = instrument, + choices = rcon$instruments()$instrument_name, add = coll) - + if (!is.null(event)){ - checkmate::assert_subset(x = event, - choices = rcon$events()$unique_event_name, + checkmate::assert_subset(x = event, + choices = rcon$events()$unique_event_name, add = coll) } - + checkmate::reportAssertions(coll) - + ################################################################## # Make API Body List - + body <- list(instrument = instrument, event = event, content = 'participantList', - format = 'csv', + format = 'csv', returnFormat = 'csv') ################################################################## # Call the API - as.data.frame(makeApiCall(rcon, body, ...)) + as.data.frame(makeApiCall(rcon, body, ...), sep = rcon$csv_delimiter()) } diff --git a/R/exportUserDagAssignments.R b/R/exportUserDagAssignments.R index 4d17153f..26523542 100644 --- a/R/exportUserDagAssignments.R +++ b/R/exportUserDagAssignments.R @@ -3,7 +3,7 @@ #' @order 1 #' @export -exportUserDagAssignments <- function(rcon, +exportUserDagAssignments <- function(rcon, ...){ UseMethod("exportUserDagAssignments") } @@ -11,31 +11,31 @@ exportUserDagAssignments <- function(rcon, #' @rdname dagAssignmentMethods #' @export -exportUserDagAssignments.redcapApiConnection <- function(rcon, +exportUserDagAssignments.redcapApiConnection <- function(rcon, ...) { ################################################################### # Argument Validation #### - + coll <- checkmate::makeAssertCollection() - - checkmate::assert_class(x = rcon, - classes = "redcapApiConnection", + + checkmate::assert_class(x = rcon, + classes = "redcapApiConnection", add = coll) checkmate::reportAssertions(coll) - + ################################################################### # Build the body list #### - - body <- list(content = "userDagMapping", - format = "csv", + + body <- list(content = "userDagMapping", + format = "csv", returnFormat = "csv") ################################################################### # Make the API Call #### - - response <- as.data.frame(makeApiCall(rcon, body, ...)) + + response <- as.data.frame(makeApiCall(rcon, body, ...), sep = rcon$csv_delimiter()) if(nrow(response) == 0) REDCAP_DAG_ASSIGNMENT_STRUCTURE else response } diff --git a/R/exportUserRoleAssignments.R b/R/exportUserRoleAssignments.R index 034754de..ceaa8309 100644 --- a/R/exportUserRoleAssignments.R +++ b/R/exportUserRoleAssignments.R @@ -34,7 +34,7 @@ exportUserRoleAssignments.redcapApiConnection <- function(rcon, ################################################################### # Call the API #### - response <- as.data.frame(makeApiCall(rcon, body, ...)) + response <- as.data.frame(makeApiCall(rcon, body, ...), sep = rcon$csv_delimiter()) if(nrow(response) == 0) redcapUserRoleAssignmentStructure(rcon$version()) else response } diff --git a/R/exportUserRoles.R b/R/exportUserRoles.R index 6b9dc314..8cb88512 100644 --- a/R/exportUserRoles.R +++ b/R/exportUserRoles.R @@ -1,4 +1,4 @@ -#' @describeIn userRoleMethods Export user roles from a project. +#' @describeIn userRoleMethods Export user roles from a project. #' @order 1 #' @export @@ -10,45 +10,45 @@ exportUserRoles <- function(rcon, ...){ #' @order 4 #' @export -exportUserRoles.redcapApiConnection <- function(rcon, - labels = TRUE, - form_rights = TRUE, +exportUserRoles.redcapApiConnection <- function(rcon, + labels = TRUE, + form_rights = TRUE, ...) { ################################################################### # Argument Validation #### - + coll <- checkmate::makeAssertCollection() - - checkmate::assert_class(x = rcon, - classes = "redcapApiConnection", + + checkmate::assert_class(x = rcon, + classes = "redcapApiConnection", add = coll) - - checkmate::assert_logical(x = labels, - len = 1, + + checkmate::assert_logical(x = labels, + len = 1, null.ok = FALSE, add = coll) - - checkmate::assert_logical(x = form_rights, - len = 1, - null.ok = FALSE, + + checkmate::assert_logical(x = form_rights, + len = 1, + null.ok = FALSE, add = coll) checkmate::reportAssertions(coll) - + ################################################################### # API body list #### - - body <- list(content = "userRole", - format = "csv", + + body <- list(content = "userRole", + format = "csv", returnFormat = "csv") - + ################################################################### # Make API Call #### - UserRole <- as.data.frame(makeApiCall(rcon, body, ...)) + UserRole <- as.data.frame(makeApiCall(rcon, body, ...), sep = rcon$csv_delimiter()) if (nrow(UserRole) == 0) return(redcapUserRoleStructure(rcon$version())) - + # The API returns the forms_export string twice. We reduce it to once here temp <- UserRole$forms_export temp <- strsplit(temp, ",") @@ -56,46 +56,46 @@ exportUserRoles.redcapApiConnection <- function(rcon, temp <- temp[!duplicated(temp)] temp <- paste0(temp, collapse = ",") UserRole$forms_export <- temp - + ################################################################### # Format UserRole properties #### - + if (labels){ existing_vars <- intersect(REDCAP_USER_ROLE_TABLE_ACCESS_VARIABLES, names(UserRole)) - UserRole[existing_vars] <- - lapply(UserRole[existing_vars], - factor, - levels = 0:1, + UserRole[existing_vars] <- + lapply(UserRole[existing_vars], + factor, + levels = 0:1, labels = c("No Access", "Access")) } - + if (form_rights){ - FormAccess <- .exportUsers_separateFormAccess(rcon = rcon, - UserRole$forms, + FormAccess <- .exportUsers_separateFormAccess(rcon = rcon, + UserRole$forms, nrow = nrow(UserRole), export = FALSE) - ExportAccess <- .exportUsers_separateFormAccess(rcon = rcon, - form_access = UserRole$forms_export, - nrow = nrow(UserRole), + ExportAccess <- .exportUsers_separateFormAccess(rcon = rcon, + form_access = UserRole$forms_export, + nrow = nrow(UserRole), export = TRUE) - UserRole <- - cbind(UserRole, - FormAccess, + UserRole <- + cbind(UserRole, + FormAccess, ExportAccess) - + if (labels){ - UserRole[names(FormAccess)] <- - lapply(UserRole[names(FormAccess)], - .exportUsers_labels, + UserRole[names(FormAccess)] <- + lapply(UserRole[names(FormAccess)], + .exportUsers_labels, type = "form") - - UserRole[names(ExportAccess)] <- - lapply(UserRole[names(ExportAccess)], - .exportUsers_labels, + + UserRole[names(ExportAccess)] <- + lapply(UserRole[names(ExportAccess)], + .exportUsers_labels, type = "form_export") } - + } - + UserRole } diff --git a/R/exportUsers.R b/R/exportUsers.R index 0a4c2c49..4833ddd8 100644 --- a/R/exportUsers.R +++ b/R/exportUsers.R @@ -49,7 +49,7 @@ exportUsers.redcapApiConnection <- function(rcon, ################################################################## # API Call - Users <- as.data.frame(makeApiCall(rcon, body, ...)) + Users <- as.data.frame(makeApiCall(rcon, body, ...), sep = rcon$csv_delimiter()) Users$forms_export <- sub(",registration[:]\\d{1}.+$", "", Users$forms_export) diff --git a/R/importRecords.R b/R/importRecords.R index ddab1f31..870c4614 100644 --- a/R/importRecords.R +++ b/R/importRecords.R @@ -412,7 +412,7 @@ import_records_unbatched <- function(rcon, response <- makeApiCall(rcon, body, ...) if (returnContent %in% c("ids", "auto_ids")) - as.data.frame(response) else + as.data.frame(response, sep = rcon$csv_delimiter()) else as.character(response) } From c5261ee435360a2ba446f746d63ee1f640d000f7 Mon Sep 17 00:00:00 2001 From: Shawn Garbett Date: Fri, 13 Mar 2026 16:23:05 -0500 Subject: [PATCH 4/4] exportFileRepositoryListing csv_delimiter #507 --- R/exportFileRepositoryListing.R | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/R/exportFileRepositoryListing.R b/R/exportFileRepositoryListing.R index 0935c5bb..18f35a47 100644 --- a/R/exportFileRepositoryListing.R +++ b/R/exportFileRepositoryListing.R @@ -105,7 +105,7 @@ exportFileRepositoryListing.redcapApiConnection <- function(rcon, # Convert result to a data frame ---------------------------------- FileRepository <- .fileRepositoryFrame(response, - folder_id) + folder_id, rcon) # Recursive Call -------------------------------------------------- @@ -121,12 +121,14 @@ exportFileRepositoryListing.redcapApiConnection <- function(rcon, # Unexported -------------------------------------------------------- .fileRepositoryFrame <- function(response, - folder_id){ + folder_id, + rcon) +{ # If folder_id has length 0, set the parent to top-level parent <- if (length(folder_id) == 0) 0 else folder_id if (length(response$content) > 0){ - response <- as.data.frame(response) + response <- as.data.frame(response, sep=rcon$csv_delimiter()) response$parent_folder <- rep(parent, nrow(response)) } else {