From 05b2a9d2a327fe31c4c43a9c0de6b5e171a7d029 Mon Sep 17 00:00:00 2001 From: Will Beasley Date: Fri, 10 Aug 2018 17:30:00 -0500 Subject: [PATCH 1/6] update to roxygen 6.1.0 --- DESCRIPTION | 2 +- man/metadata_utilities.Rd | 1 - man/redcap_metadata_read.Rd | 6 +++--- man/redcap_variables.Rd | 3 ++- man/redcap_version.Rd | 3 ++- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index c1103c1d..b5cb6049 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -57,5 +57,5 @@ Remotes: License: GPL-2 LazyData: TRUE VignetteBuilder: knitr -RoxygenNote: 6.0.1 +RoxygenNote: 6.1.0 Roxygen: list(markdown = TRUE) diff --git a/man/metadata_utilities.Rd b/man/metadata_utilities.Rd index 1fc83f17..b95e6368 100644 --- a/man/metadata_utilities.Rd +++ b/man/metadata_utilities.Rd @@ -4,7 +4,6 @@ \alias{metadata_utilities} \alias{regex_named_captures} \alias{checkbox_choices} -\alias{checkbox_choices} \title{Manipulate and interpret the metadata of a REDCap project.} \usage{ regex_named_captures(pattern, text, perl = TRUE) diff --git a/man/redcap_metadata_read.Rd b/man/redcap_metadata_read.Rd index 0fa6b440..db83661a 100644 --- a/man/redcap_metadata_read.Rd +++ b/man/redcap_metadata_read.Rd @@ -4,9 +4,9 @@ \alias{redcap_metadata_read} \title{Export the metadata of a REDCap project.} \usage{ -redcap_metadata_read(redcap_uri, token, forms = NULL, forms_collapsed = "", - fields = NULL, fields_collapsed = "", verbose = TRUE, - config_options = NULL) +redcap_metadata_read(redcap_uri, token, forms = NULL, + forms_collapsed = "", fields = NULL, fields_collapsed = "", + verbose = TRUE, config_options = NULL) } \arguments{ \item{redcap_uri}{The URI (uniform resource identifier) of the REDCap project. Required.} diff --git a/man/redcap_variables.Rd b/man/redcap_variables.Rd index 5abae0a4..25143b83 100644 --- a/man/redcap_variables.Rd +++ b/man/redcap_variables.Rd @@ -4,7 +4,8 @@ \alias{redcap_variables} \title{Enumerate the exported variables.} \usage{ -redcap_variables(redcap_uri, token, verbose = TRUE, config_options = NULL) +redcap_variables(redcap_uri, token, verbose = TRUE, + config_options = NULL) } \arguments{ \item{redcap_uri}{The URI (uniform resource identifier) of the REDCap project. Required.} diff --git a/man/redcap_version.Rd b/man/redcap_version.Rd index e1509e06..273ea98f 100644 --- a/man/redcap_version.Rd +++ b/man/redcap_version.Rd @@ -4,7 +4,8 @@ \alias{redcap_version} \title{Determine version of REDCap instance} \usage{ -redcap_version(redcap_uri, token, verbose = TRUE, config_options = NULL) +redcap_version(redcap_uri, token, verbose = TRUE, + config_options = NULL) } \arguments{ \item{redcap_uri}{The URI (uniform resource identifier) of the REDCap project. Required.} From 16a586ee279566829450a61de021d565e95931e5 Mon Sep 17 00:00:00 2001 From: Will Beasley Date: Fri, 10 Aug 2018 17:45:41 -0500 Subject: [PATCH 2/6] update vignettes & webpage --- docs/CONDUCT.html | 6 +- docs/articles/BasicREDCapROperations.html | 1475 +++----------- docs/articles/SecurityDatabase.html | 512 ++--- docs/articles/TroubleshootingApiCalls.html | 291 +-- .../articles/advanced-redcapr-operations.html | 1147 ++--------- docs/articles/index.html | 6 +- docs/authors.html | 6 +- docs/favicon.ico | Bin 1906 -> 1915 bytes docs/index.html | 44 +- docs/pkgdown.yml | 4 +- docs/reference/REDCapR-package.html | 6 +- docs/reference/collapse_vector.html | 6 +- docs/reference/constant.html | 6 +- docs/reference/create_batch_glossary.html | 6 +- docs/reference/index.html | 6 +- docs/reference/kernel_api.html | 8 +- docs/reference/metadata_utilities.html | 8 +- docs/reference/redcap_column_sanitize.html | 14 +- .../redcap_download_file_oneshot.html | 6 +- docs/reference/redcap_metadata_read.html | 12 +- docs/reference/redcap_project.html | 6 +- docs/reference/redcap_read.html | 6 +- docs/reference/redcap_read_oneshot.html | 6 +- docs/reference/redcap_read_oneshot_eav.html | 6 +- .../reference/redcap_upload_file_oneshot.html | 6 +- docs/reference/redcap_variables.html | 9 +- docs/reference/redcap_version.html | 11 +- docs/reference/redcap_write.html | 6 +- docs/reference/redcap_write_oneshot.html | 6 +- docs/reference/replace_nas_with_explicit.html | 6 +- docs/reference/retrieve_credential.html | 6 +- docs/reference/sanitize_token.html | 6 +- docs/reference/validate.html | 14 +- docs/sitemap.xml | 12 +- inst/doc/BasicREDCapROperations.html | 1767 +++++------------ inst/doc/SecurityDatabase.html | 790 +++++--- inst/doc/TroubleshootingApiCalls.html | 558 ++++-- inst/doc/advanced-redcapr-operations.html | 1435 ++++--------- 38 files changed, 2674 insertions(+), 5551 deletions(-) diff --git a/docs/CONDUCT.html b/docs/CONDUCT.html index 25e8a5a3..8074687d 100644 --- a/docs/CONDUCT.html +++ b/docs/CONDUCT.html @@ -89,6 +89,9 @@ diff --git a/docs/articles/BasicREDCapROperations.html b/docs/articles/BasicREDCapROperations.html index 623a3d83..d23f0884 100644 --- a/docs/articles/BasicREDCapROperations.html +++ b/docs/articles/BasicREDCapROperations.html @@ -55,6 +55,9 @@ @@ -96,7 +96,7 @@

Read all records and fields.

If no information is passed about the desired records or fields, then the entire data set is returned. Only two parameters are required, redcap_uri and token. Unless the verbose parameter is set to FALSE, a message will be printed on the R console with the number of records and fields returned.

-
#Return all records and all variables.
-ds_all_rows_all_fields <- redcap_read(redcap_uri=uri, token=token)$data
+
The data dictionary describing 16 fields was read from REDCap in 0.4 seconds.  The http status code was 200.
-
5 records and 1 columns were read from REDCap in 0.7 seconds.  The http status code was 200.
-
Starting to read 5 records  at 2018-07-12 00:22:13.
+
5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
+
Starting to read 5 records  at 2018-08-10 17:44:07.
Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
-
5 records and 24 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
-
ds_all_rows_all_fields #Inspect the returned dataset
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-record id - -name first - -name last - -address - -telephone - -email - -dob - -age - -sex - -demographics complete - -height - -weight - -bmi - -comments - -mugshot - -health complete - -race 1 - -race 2 - -race 3 - -race 4 - -race 5 - -race 6 - -ethnicity - -race and ethnicity complete -
-1 - -Nutmeg - -Nutmouse - -14 Rose Cottage St. Kenning UK, 323232 - -
  1. 321-1111 -
-
-nutty@mouse.com - -2003-08-30 - -11 - -0 - -2 - -7.00 - -1 - -204.1 - -Character in a book, with some guessing - -[document] - -1 - -0 - -0 - -0 - -0 - -1 - -0 - -1 - -2 -
-2 - -Tumtum - -Nutmouse - -14 Rose Cottage Blvd. Kenning UK 34243 - -
  1. 321-2222 -
-
-tummy@mouse.comm - -2003-03-10 - -11 - -1 - -2 - -6.00 - -1 - -277.8 - -A mouse character from a good book - -[document] - -0 - -0 - -0 - -1 - -0 - -1 - -0 - -1 - -0 -
-3 - -Marcus - -Wood - -243 Hill St. Guthrie OK 73402 - -
  1. 321-3333 -
-
-mw@mwood.net - -1934-04-09 - -80 - -1 - -2 - -180.00 - -80 - -24.7 - -completely made up - -[document] - -2 - -0 - -0 - -0 - -1 - -1 - -0 - -0 - -2 -
-4 - -Trudy - -DAG - -342 Elm Duncanville TX, 75116 - -
  1. 321-4444 -
-
-peroxide@blonde.com - -1952-11-02 - -61 - -0 - -2 - -165.00 - -54 - -19.8 - -This record doesn’t have a DAG assigned - - - - -So call up Trudy on the telephone Send her a letter in the mail - -[document] - -2 - -0 - -1 - -0 - -0 - -1 - -0 - -1 - -2 -
-5 - -John Lee - -Walker - -Hotel Suite New Orleans LA, 70115 - -
  1. 321-5555 -
-
-left@hippocket.com - -1955-04-15 - -59 - -1 - -2 - -193.04 - -104 - -27.9 - -Had a hand for trouble and a eye for cash - -He had a gold watch chain and a black mustache - -[document] - -0 - -1 - -0 - -0 - -0 - -0 - -1 - -2 - -2 -
+
5 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
+ +
  record_id name_first name_last                                 address
+1         1     Nutmeg  Nutmouse 14 Rose Cottage St.\nKenning UK, 323232
+2         2     Tumtum  Nutmouse 14 Rose Cottage Blvd.\nKenning UK 34243
+3         3     Marcus      Wood          243 Hill St.\nGuthrie OK 73402
+4         4      Trudy       DAG          342 Elm\nDuncanville TX, 75116
+5         5   John Lee    Walker      Hotel Suite\nNew Orleans LA, 70115
+       telephone               email        dob age sex
+1 (405) 321-1111     nutty@mouse.com 2003-08-30  11   0
+2 (405) 321-2222    tummy@mouse.comm 2003-03-10  11   1
+3 (405) 321-3333        mw@mwood.net 1934-04-09  80   1
+4 (405) 321-4444 peroxide@blonde.com 1952-11-02  61   0
+5 (405) 321-5555  left@hippocket.com 1955-04-15  59   1
+  demographics_complete height weight   bmi
+1                     2   7.00      1 204.1
+2                     2   6.00      1 277.8
+3                     2 180.00     80  24.7
+4                     2 165.00     54  19.8
+5                     2 193.04    104  27.9
+                                                                                                     comments
+1                                                                     Character in a book, with some guessing
+2                                                                          A mouse character from a good book
+3                                                                                          completely made up
+4 This record doesn't have a DAG assigned\n\nSo call up Trudy on the telephone\nSend her a letter in the mail
+5                 Had a hand for trouble and a eye for cash\n\nHe had a gold watch chain and a black mustache
+     mugshot health_complete race___1 race___2 race___3 race___4 race___5
+1 [document]               1        0        0        0        0        1
+2 [document]               0        0        0        1        0        1
+3 [document]               2        0        0        0        1        1
+4 [document]               2        0        1        0        0        1
+5 [document]               0        1        0        0        0        0
+  race___6 ethnicity race_and_ethnicity_complete
+1        0         1                           2
+2        0         1                           0
+3        0         0                           2
+4        0         1                           2
+5        1         2                           2

Read a subset of the records.

If only a subset of the records is desired, the two approaches are shown below. The first is to pass an array (where each element is an ID) to the records parameter. The second is to pass a single string (where the elements are separated by commas) to the records_collapsed parameter.

The first format is more natural for more R users. The second format is what is expected by the REDCap API. If a value for records is specified, but records_collapsed is not specified, then redcap_read_oneshot automatically converts the array into the format needed by the API.

-
#Return only records with IDs of 1 and 3
-desired_records_v1 <- c(1, 3)
-ds_some_rows_v1 <- redcap_read(
-   redcap_uri = uri, 
-   token      = token, 
-   records    = desired_records_v1
-)$data
-
The data dictionary describing 16 fields was read from REDCap in 0.3 seconds.  The http status code was 200.
-
2 records and 1 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
-
Starting to read 2 records  at 2018-07-12 00:22:15.
+ +
The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
+
2 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
+
Starting to read 2 records  at 2018-08-10 17:44:09.
Reading batch 1 of 1, with subjects 1 through 3 (ie, 2 unique subject records).
2 records and 24 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
-
#Return only records with IDs of 1 and 3 (alternate way)
-desired_records_v2 <- "1, 3"
-ds_some_rows_v2 <- redcap_read(
-   redcap_uri        = uri, 
-   token             = token, 
-   records_collapsed = desired_records_v2
-)$data
-
The data dictionary describing 16 fields was read from REDCap in 0.3 seconds.  The http status code was 200.
-
2 records and 1 columns were read from REDCap in 0.4 seconds.  The http status code was 200.
-
Starting to read 2 records  at 2018-07-12 00:22:16.
+ +
The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
+
2 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
+
Starting to read 2 records  at 2018-08-10 17:44:10.
Reading batch 1 of 1, with subjects 1 through 3 (ie, 2 unique subject records).
-
2 records and 24 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
-
ds_some_rows_v2 #Inspect the returned dataset
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-record id - -name first - -name last - -address - -telephone - -email - -dob - -age - -sex - -demographics complete - -height - -weight - -bmi - -comments - -mugshot - -health complete - -race 1 - -race 2 - -race 3 - -race 4 - -race 5 - -race 6 - -ethnicity - -race and ethnicity complete -
-1 - -Nutmeg - -Nutmouse - -14 Rose Cottage St. Kenning UK, 323232 - -
  1. 321-1111 -
-
-nutty@mouse.com - -2003-08-30 - -11 - -0 - -2 - -7 - -1 - -204.1 - -Character in a book, with some guessing - -[document] - -1 - -0 - -0 - -0 - -0 - -1 - -0 - -1 - -2 -
-3 - -Marcus - -Wood - -243 Hill St. Guthrie OK 73402 - -
  1. 321-3333 -
-
-mw@mwood.net - -1934-04-09 - -80 - -1 - -2 - -180 - -80 - -24.7 - -completely made up - -[document] - -2 - -0 - -0 - -0 - -1 - -1 - -0 - -0 - -2 -
+
2 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
+ +
  record_id name_first name_last                                 address
+1         1     Nutmeg  Nutmouse 14 Rose Cottage St.\nKenning UK, 323232
+2         3     Marcus      Wood          243 Hill St.\nGuthrie OK 73402
+       telephone           email        dob age sex demographics_complete
+1 (405) 321-1111 nutty@mouse.com 2003-08-30  11   0                     2
+2 (405) 321-3333    mw@mwood.net 1934-04-09  80   1                     2
+  height weight   bmi                                comments    mugshot
+1      7      1 204.1 Character in a book, with some guessing [document]
+2    180     80  24.7                      completely made up [document]
+  health_complete race___1 race___2 race___3 race___4 race___5 race___6
+1               1        0        0        0        0        1        0
+2               2        0        0        0        1        1        0
+  ethnicity race_and_ethnicity_complete
+1         1                           2
+2         0                           2

Read a subset of the fields.

If only a subset of the fields is desired, then two approaches exist. The first is to pass an array (where each element is an field) to the fields parameter. The second is to pass a single string (where the elements are separated by commas) to the fields_collapsed parameter. Like with records and records_collapsed described above, this function converts the more natural format (ie, fields) to the format required by the API (ie, fields_collapsed) if fields is specified and fields_collapsed is not.

-
#Return only the fields record_id, name_first, and age
-desired_fields_v1 <- c("record_id", "name_first", "age")
-ds_some_fields_v1 <- redcap_read(
-   redcap_uri = uri, 
-   token      = token, 
-   fields     = desired_fields_v1
-)$data
-
The data dictionary describing 16 fields was read from REDCap in 0.3 seconds.  The http status code was 200.
+ +
The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
-
Starting to read 5 records  at 2018-07-12 00:22:18.
+
Starting to read 5 records  at 2018-08-10 17:44:11.
Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
-
5 records and 3 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
-
#Return only the fields record_id, name_first, and age (alternate way)
-desired_fields_v2 <- "record_id, name_first, age"
-ds_some_fields_v2 <- redcap_read(
-   redcap_uri       = uri, 
-   token            = token, 
-   fields_collapsed = desired_fields_v2
-)$data
-
The data dictionary describing 16 fields was read from REDCap in 0.3 seconds.  The http status code was 200.
-
5 records and 1 columns were read from REDCap in 0.4 seconds.  The http status code was 200.
-
Starting to read 5 records  at 2018-07-12 00:22:20.
+
5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
+ +
The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
+
5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
+
Starting to read 5 records  at 2018-08-10 17:44:12.
Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
-
5 records and 3 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
-
ds_some_fields_v2 #Inspect the returned dataset
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-record id - -name first - -age -
-1 - -Nutmeg - -11 -
-2 - -Tumtum - -11 -
-3 - -Marcus - -80 -
-4 - -Trudy - -61 -
-5 - -John Lee - -59 -
+
5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
+ +
  record_id name_first age
+1         1     Nutmeg  11
+2         2     Tumtum  11
+3         3     Marcus  80
+4         4      Trudy  61
+5         5   John Lee  59

Read a subset of records, conditioned on the values in some variables.

The two techniques above can be combined when your datasets are large and you don’t want to pull records with certain values. Suppose you want to select subjects from the previous dataset if the were born before 1960 and their weight was over 70kg. Two calls to the server are required. The first call to REDCap pulls all the records, but for only three columns: record_id, dob, and weight. From this subset, identify the records that you want to pull all the data for; in this case, the desired record_id values are 3 & 5. The second call to REDCap pulls all the columns, but only for the identified records.

-
######
-## Step 1: First call to REDCap
-desired_fields_v3 <- c("record_id", "dob", "weight")
-ds_some_fields_v3 <- redcap_read(
-   redcap_uri = uri, 
-   token      = token, 
-   fields     = desired_fields_v3
-)$data
-
The data dictionary describing 16 fields was read from REDCap in 0.4 seconds.  The http status code was 200.
-
5 records and 1 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
-
Starting to read 5 records  at 2018-07-12 00:22:21.
+ +
The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
+
5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
+
Starting to read 5 records  at 2018-08-10 17:44:13.
Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
-
5 records and 3 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
-
ds_some_fields_v3 #Examine the these three variables.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-record id - -dob - -weight -
-1 - -2003-08-30 - -1 -
-2 - -2003-03-10 - -1 -
-3 - -1934-04-09 - -80 -
-4 - -1952-11-02 - -54 -
-5 - -1955-04-15 - -104 -
-
######
-## Step 2: identify desired records, based on age & weight
-before_1960 <- (ds_some_fields_v3$dob <= as.Date("1960-01-01"))
-heavier_than_70_kg <- (ds_some_fields_v3$weight > 70)
-desired_records_v3 <- ds_some_fields_v3[before_1960 & heavier_than_70_kg, ]$record_id
-
-desired_records_v3 #Peek at IDs of the identified records
+
5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
+ +
  record_id        dob weight
+1         1 2003-08-30      1
+2         2 2003-03-10      1
+3         3 1934-04-09     80
+4         4 1952-11-02     54
+5         5 1955-04-15    104
+
[1] 3 5
-
######
-## Step 3: second call to REDCap
-#Return only records that met the age & weight criteria.
-ds_some_rows_v3 <- redcap_read(
-   redcap_uri = uri, 
-   token      = token, 
-   records    = desired_records_v3
-)$data
-
The data dictionary describing 16 fields was read from REDCap in 0.3 seconds.  The http status code was 200.
-
2 records and 1 columns were read from REDCap in 0.4 seconds.  The http status code was 200.
-
Starting to read 2 records  at 2018-07-12 00:22:23.
+ +
The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
+
2 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
+
Starting to read 2 records  at 2018-08-10 17:44:14.
Reading batch 1 of 1, with subjects 3 through 5 (ie, 2 unique subject records).
-
2 records and 24 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
-
ds_some_rows_v3 #Examine the results.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-record id - -name first - -name last - -address - -telephone - -email - -dob - -age - -sex - -demographics complete - -height - -weight - -bmi - -comments - -mugshot - -health complete - -race 1 - -race 2 - -race 3 - -race 4 - -race 5 - -race 6 - -ethnicity - -race and ethnicity complete -
-3 - -Marcus - -Wood - -243 Hill St. Guthrie OK 73402 - -
  1. 321-3333 -
-
-mw@mwood.net - -1934-04-09 - -80 - -1 - -2 - -180.00 - -80 - -24.7 - -completely made up - -[document] - -2 - -0 - -0 - -0 - -1 - -1 - -0 - -0 - -2 -
-5 - -John Lee - -Walker - -Hotel Suite New Orleans LA, 70115 - -
  1. 321-5555 -
-
-left@hippocket.com - -1955-04-15 - -59 - -1 - -2 - -193.04 - -104 - -27.9 - -Had a hand for trouble and a eye for cash - - -He had a gold watch chain and a black mustache - -[document] - -0 - -1 - -0 - -0 - -0 - -0 - -1 - -2 - -2 -
+
2 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
+ +
  record_id name_first name_last                            address
+1         3     Marcus      Wood     243 Hill St.\nGuthrie OK 73402
+2         5   John Lee    Walker Hotel Suite\nNew Orleans LA, 70115
+       telephone              email        dob age sex
+1 (405) 321-3333       mw@mwood.net 1934-04-09  80   1
+2 (405) 321-5555 left@hippocket.com 1955-04-15  59   1
+  demographics_complete height weight  bmi
+1                     2 180.00     80 24.7
+2                     2 193.04    104 27.9
+                                                                                     comments
+1                                                                          completely made up
+2 Had a hand for trouble and a eye for cash\n\nHe had a gold watch chain and a black mustache
+     mugshot health_complete race___1 race___2 race___3 race___4 race___5
+1 [document]               2        0        0        0        1        1
+2 [document]               0        1        0        0        0        0
+  race___6 ethnicity race_and_ethnicity_complete
+1        0         0                           2
+2        1         2                           2

@@ -1306,18 +328,18 @@

  • The fields_collapsed fields passed to the API. This shows which field subsets, if any, were requested.
  • The elapsed_seconds measures the duration of the call.
  • -
    #Return only the fields record_id, name_first, and age
    -all_information <- redcap_read(
    -   redcap_uri = uri, 
    -   token      = token, 
    -   fields     = desired_fields_v1
    -)
    -
    The data dictionary describing 16 fields was read from REDCap in 0.3 seconds.  The http status code was 200.
    -
    5 records and 1 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-07-12 00:22:24.
    + +
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    +
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    +
    Starting to read 5 records  at 2018-08-10 17:44:16.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    -
    5 records and 3 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    -
    all_information #Inspect the additional information
    +
    5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    +
    $data
       record_id name_first age
     1         1     Nutmeg  11
    @@ -1333,7 +355,7 @@ 

    [1] "200" $outcome_messages -[1] "5 records and 3 columns were read from REDCap in 0.3 seconds. The http status code was 200." +[1] "5 records and 3 columns were read from REDCap in 0.2 seconds. The http status code was 200." $records_collapsed [1] "" @@ -1351,81 +373,78 @@

    [1] "" $elapsed_seconds -[1] 1.503323

    +[1] 1.156543

    Session Information

    For the sake of documentation and reproducibility, the current report was rendered in the following environment. Click the line below to expand.

    -

    Environment

    -
    ─ Session info ──────────────────────────────────────────────────────────
    - setting  value                       
    - version  R version 3.4.4 (2018-03-15)
    - os       Ubuntu 18.04 LTS            
    - system   x86_64, linux-gnu           
    - ui       X11                         
    - language (EN)                        
    - collate  en_US.UTF-8                 
    - tz       America/Chicago             
    - date     2018-07-12                  
    +

    Environment

    +
    - Session info ----------------------------------------------------------
    + setting  value                                      
    + version  R version 3.5.1 Patched (2018-08-06 r75070)
    + os       Windows >= 8 x64                           
    + system   x86_64, mingw32                            
    + ui       RTerm                                      
    + language (EN)                                       
    + collate  English_United States.1252                 
    + tz       America/Chicago                            
    + date     2018-08-10                                 
     
    -─ Packages ──────────────────────────────────────────────────────────────
    +- Packages --------------------------------------------------------------
      package     * version     date       source                          
    - assertthat    0.2.0       2017-04-11 cran (@0.2.0)                   
    - backports     1.1.2       2017-12-13 cran (@1.1.2)                   
    - bindr         0.1.1       2018-03-13 CRAN (R 3.4.3)                  
    - bindrcpp      0.2.2       2018-03-29 CRAN (R 3.4.3)                  
    - checkmate     1.8.6       2018-04-10 Github (mllg/checkmate@489319a) 
    - clisymbols    1.2.0       2017-05-21 CRAN (R 3.4.3)                  
    - colorspace    1.3-2       2016-12-14 CRAN (R 3.4.3)                  
    - commonmark    1.5         2018-04-28 CRAN (R 3.4.4)                  
    - crayon        1.3.4       2017-09-16 CRAN (R 3.4.3)                  
    - curl          3.2         2018-03-28 CRAN (R 3.4.4)                  
    - desc          1.2.0       2018-05-01 CRAN (R 3.4.4)                  
    - devtools      1.13.6      2018-06-27 CRAN (R 3.4.4)                  
    - digest        0.6.15      2018-01-28 CRAN (R 3.4.3)                  
    - dplyr         0.7.6       2018-06-29 CRAN (R 3.4.4)                  
    - evaluate      0.10.1      2017-06-24 CRAN (R 3.4.3)                  
    - fs            1.2.3       2018-06-08 CRAN (R 3.4.4)                  
    - glue          1.2.0       2017-10-29 cran (@1.2.0)                   
    - highr         0.7         2018-06-09 CRAN (R 3.4.4)                  
    - hms           0.4.2.9000  2018-05-26 Github (tidyverse/hms@14e74ab)  
    - htmltools     0.3.6       2017-04-28 CRAN (R 3.4.3)                  
    - httr          1.3.1       2017-08-20 CRAN (R 3.4.3)                  
    - kableExtra    0.9.0       2018-05-21 CRAN (R 3.4.4)                  
    - knitr       * 1.20        2018-02-20 CRAN (R 3.4.3)                  
    - magrittr    * 1.5         2014-11-22 cran (@1.5)                     
    - MASS          7.3-50      2018-04-30 CRAN (R 3.4.4)                  
    - memoise       1.1.0       2017-04-21 CRAN (R 3.4.3)                  
    - munsell       0.5.0       2018-06-12 CRAN (R 3.4.4)                  
    - pillar        1.2.3       2018-05-25 CRAN (R 3.4.4)                  
    - pkgconfig     2.0.1       2017-03-21 cran (@2.0.1)                   
    - pkgdown       1.1.0       2018-06-02 CRAN (R 3.4.4)                  
    - plyr          1.8.4       2016-06-08 CRAN (R 3.4.3)                  
    - purrr         0.2.5       2018-05-29 CRAN (R 3.4.4)                  
    - R6            2.2.2       2017-06-17 CRAN (R 3.4.3)                  
    - Rcpp          0.12.17     2018-05-18 CRAN (R 3.4.4)                  
    - readr         1.2.0       2018-05-26 Github (tidyverse/readr@d6d622b)
    - REDCapR     * 0.9.10.9001 2018-07-11 local                           
    - rlang         0.2.1       2018-05-30 CRAN (R 3.4.4)                  
    - rmarkdown     1.10        2018-06-11 CRAN (R 3.4.4)                  
    - roxygen2      6.0.1       2017-02-06 CRAN (R 3.4.4)                  
    - rprojroot     1.3-2       2018-01-03 CRAN (R 3.4.3)                  
    - rstudioapi    0.7         2017-09-07 CRAN (R 3.4.3)                  
    - rvest         0.3.2       2016-06-17 CRAN (R 3.4.3)                  
    - scales        0.5.0.9000  2018-03-29 Github (hadley/scales@d767915)  
    - sessioninfo   1.0.0       2017-06-21 CRAN (R 3.4.3)                  
    - stringi       1.2.3       2018-06-12 CRAN (R 3.4.4)                  
    - stringr       1.3.1       2018-05-10 CRAN (R 3.4.4)                  
    - tibble        1.4.2       2018-01-22 CRAN (R 3.4.3)                  
    - tidyselect    0.2.4       2018-02-26 CRAN (R 3.4.3)                  
    - viridisLite   0.3.0       2018-02-01 CRAN (R 3.4.3)                  
    - withr         2.1.2       2018-03-29 Github (jimhester/withr@79d7b0d)
    - xml2          1.2.0       2018-01-24 CRAN (R 3.4.3)                  
    - yaml          2.1.19      2018-05-01 CRAN (R 3.4.4)                  
    -

    -

    Report rendered by wibeasley at 2018-07-12, 00:22 -0500 in 15 seconds.

    + assertthat 0.2.0 2017-04-11 CRAN (R 3.5.0) + backports 1.1.2 2017-12-13 CRAN (R 3.5.0) + bindr 0.1.1 2018-03-13 CRAN (R 3.5.0) + bindrcpp 0.2.2 2018-03-29 CRAN (R 3.5.0) + checkmate 1.8.9-9000 2018-08-09 Github (mllg/checkmate@29a1fb9) + clisymbols 1.2.0 2017-05-21 CRAN (R 3.5.0) + colorspace 1.3-2 2016-12-14 CRAN (R 3.5.0) + commonmark 1.5 2018-04-28 CRAN (R 3.5.0) + crayon 1.3.4 2017-09-16 CRAN (R 3.5.0) + curl 3.2 2018-03-28 CRAN (R 3.5.0) + desc 1.2.0 2018-05-01 CRAN (R 3.5.0) + devtools 1.13.6 2018-06-27 CRAN (R 3.5.0) + digest 0.6.15 2018-01-28 CRAN (R 3.5.0) + dplyr 0.7.6 2018-06-29 CRAN (R 3.5.1) + evaluate 0.11 2018-07-17 CRAN (R 3.5.1) + fs 1.2.5 2018-07-30 CRAN (R 3.5.1) + glue 1.3.0 2018-07-17 CRAN (R 3.5.1) + hms 0.4.2.9001 2018-08-09 Github (tidyverse/hms@979286f) + htmltools 0.3.6 2017-04-28 CRAN (R 3.5.0) + httr 1.3.1 2017-08-20 CRAN (R 3.5.0) + kableExtra 0.9.0 2018-05-21 CRAN (R 3.5.0) + knitr * 1.20 2018-02-20 CRAN (R 3.5.0) + magrittr * 1.5 2014-11-22 CRAN (R 3.5.0) + MASS 7.3-50 2018-04-30 CRAN (R 3.5.1) + memoise 1.1.0 2017-04-21 CRAN (R 3.5.0) + munsell 0.5.0 2018-06-12 CRAN (R 3.5.0) + pillar 1.3.0 2018-07-14 CRAN (R 3.5.1) + pkgconfig 2.0.1 2017-03-21 CRAN (R 3.5.0) + pkgdown 1.1.0 2018-06-02 CRAN (R 3.5.1) + purrr 0.2.5 2018-05-29 CRAN (R 3.5.0) + R6 2.2.2 2017-06-17 CRAN (R 3.5.0) + Rcpp 0.12.18 2018-07-23 CRAN (R 3.5.1) + readr 1.2.0 2018-08-09 Github (tidyverse/readr@4b2e93a) + REDCapR * 0.9.10.9001 2018-08-10 local + rlang 0.2.1 2018-05-30 CRAN (R 3.5.0) + rmarkdown 1.10 2018-06-11 CRAN (R 3.5.0) + roxygen2 6.1.0 2018-07-27 CRAN (R 3.5.1) + rprojroot 1.3-2 2018-01-03 CRAN (R 3.5.0) + rstudioapi 0.7 2017-09-07 CRAN (R 3.5.0) + rvest 0.3.2 2016-06-17 CRAN (R 3.5.0) + scales 1.0.0 2018-08-09 CRAN (R 3.5.1) + sessioninfo 1.0.0 2017-06-21 CRAN (R 3.5.0) + stringi 1.2.4 2018-07-20 CRAN (R 3.5.1) + stringr 1.3.1 2018-05-10 CRAN (R 3.5.0) + tibble 1.4.2 2018-01-22 CRAN (R 3.5.0) + tidyselect 0.2.4 2018-02-26 CRAN (R 3.5.0) + viridisLite 0.3.0 2018-02-01 CRAN (R 3.5.0) + withr 2.1.2 2018-03-15 CRAN (R 3.5.0) + xml2 1.2.0 2018-01-24 CRAN (R 3.5.0) + yaml 2.2.0 2018-07-25 CRAN (R 3.5.1)
    +

    Report rendered by Will at 2018-08-10, 17:44 -0500 in 11 seconds.

    diff --git a/docs/articles/SecurityDatabase.html b/docs/articles/SecurityDatabase.html index 76e2c077..1fea59ed 100644 --- a/docs/articles/SecurityDatabase.html +++ b/docs/articles/SecurityDatabase.html @@ -55,6 +55,9 @@ @@ -96,7 +96,7 @@

    @@ -191,80 +191,80 @@

    Notice that this only gives the permissions to retrieve the token. You must still: 1. grant them API privileges to each appropriate REDCap project, and 2. copy the API from the REDCap database into the SecurityAuxiliary database.

    In the future REDCapR may expose a function that allows the user to perform the second step (through a stored procedure).

    Also, do not give typical users authorization for the ‘redcap_private’ schema. The current system allows the to view only their own tokens.

    -
    -----------------------------------------------------------------------
    --- Add a OUHSC's user account to the auxiliary_security database so that they can query the tables to retrieve their API.
    --- Notice that this only gives the permissions to retrieve the token.  You must still:
    ---   1) grant them API privileges to each appropriate REDCap project, and
    ---   2) copy the API from the REDCap database into the  auxiliary_security database.
    --- Also, do not give typical users authorization for the 'redcap_private' schema.  The current system allows the to view only their own tokens.
    ------------------------------------------------------------------------
    -
    --- STEP #1: Declare the user name.  If everything runs correctly, this should be the only piece of code that you need to modify.
    -print 'Step #1 executing....'
    -USE [master]
    -GO
    -DECLARE @qualified_user_name varchar(255); SET @qualified_user_name = '[OUHSC\lsuarez3]'
    -print 'Resulting login name: ' + @qualified_user_name; print ''
    -
    ---EXEC sp_helplogins @LoginNamePattern=@qualified_user_name
    ---SELECT * FROM master..syslogins WHERE name = @qualified_user_name
    ---SELECT * FROM auxiliary_security.sys.sysusers
    ---SELECT * FROM sys.database_permissions
    ---SELECT * FROM sys.server_principals
    -
    ------------------------------------------------------------------------
    --- STEP #2: Create a login for the *server*.
    -print 'Step #2 executing....'
    -DECLARE @sql_create_login nvarchar(max)
    -SET @sql_create_login = 'CREATE LOGIN ' + @qualified_user_name + ' FROM WINDOWS WITH DEFAULT_DATABASE=[auxiliary_security]'
    -EXECUTE sp_executesql @sql_create_login
    -DECLARE @login_count AS INT; SET @login_count = (SELECT COUNT(*) AS login_count FROM master..syslogins WHERE '[' + loginname + ']' = @qualified_user_name)
    -print 'Logins matching desired name should equal 1.  It equals: ' + CONVERT(varchar, @login_count); print ''
    -
    ------------------------------------------------------------------------
    --- STEP #3: Create a user account for the *data base*, after switching the database under focus to auxiliary_security.
    -print 'Step #3 executing....'
    -USE [auxiliary_security]
    -DECLARE @sql_create_user nvarchar(max)
    -SET @sql_create_user = 'CREATE USER ' + @qualified_user_name + ' FOR LOGIN ' + @qualified_user_name
    -EXECUTE sp_executesql @sql_create_user
    -DECLARE @user_count AS INT; SET @user_count = (SELECT COUNT(*) AS user_count FROM auxiliary_security.sys.sysusers WHERE '[' + name + ']' = @qualified_user_name)
    -print 'User accounts matching desired name should equal 1.  It equals: ' + CONVERT(varchar, @user_count); print ''
    -
    ------------------------------------------------------------------------
    --- STEP #4: Grant appropriate privileges for the 'redcap' schema.
    -print 'Step #4 executing....'
    -DECLARE @sql_grant_schema_redcap nvarchar(max)
    -SET @sql_grant_schema_redcap = 'GRANT EXECUTE ON SCHEMA::[redcap] TO ' + @qualified_user_name
    -EXECUTE sp_executesql @sql_grant_schema_redcap
    -print 'Step #4 executed'; print ''
    -
    ------------------------------------------------------------------------
    --- STEP #5: Grant appropriate privileges for the 'Security' schema.
    -print 'Step #5 executing....'
    -DECLARE @sql_grant_schema_security nvarchar(max)
    -SET @sql_grant_schema_security = 'GRANT EXECUTE ON SCHEMA::[security] TO ' + @qualified_user_name
    -EXECUTE sp_executesql @sql_grant_schema_security
    -print 'Step #5 executed'; print ''
    -
    ------------------------------------------------------------------------
    --- OPTIONAL STEP: Delete the user from the database (the first line) and then the server (the second line).
    --- The person's other database user accounts (besides with the auxiliary_security database) will NOT be automatically deleted by these two lines.
    ---USE [auxiliary_security]; DROP USER [OUHSC\lsuarez3]
    ---USE [master]; DROP LOGIN [OUHSC\lsuarez3]
    -
    ------------------------------------------------------------------------
    --- REFERENCES & NOTES
    -  --The @qualified_user_name must have both (a) the 'OUHSC' domain qualification, and (b) the square brackets (to escape the backslash).
    -  --Using sp_executesql to add users: http://www.sqlservercentral.com/Forums/Topic497615-359-1.aspx
    -  --Check if a server login exists: http://stackoverflow.com/questions/37275/sql-query-for-logins
    -  --Retrieve database users: http://stackoverflow.com/questions/2445444/how-to-get-a-list-of-users-for-all-instances-databases
    -  --Concatenating strings: http://blog.sqlauthority.com/2010/11/25/sql-server-concat-function-in-sql-server-sql-concatenation/
    -  --DROP USER from database: http://msdn.microsoft.com/en-us/library/ms189438.aspx
    -  --DROP LOGIN from server: http://msdn.microsoft.com/en-us/library/ms188012.aspx
    -  --Declaring variables (eg, the username above): http://technet.microsoft.com/en-us/library/aa258839.aspx
    -  --A different (& non-dynamic) way to establish a user: http://pic.dhe.ibm.com/infocenter/dmndhelp/v8r5m0/index.jsp?topic=%2Fcom.ibm.wbpm.imuc.sbpm.doc%2Ftopics%2Fdb_create_users_nd_aix.html
    -  --If the variable has to cross a 'GO' (which the current version of the script doesn't need): http://stackoverflow.com/questions/937336/is-there-a-way-to-persist-a-variable-across-a-go
    +
    -----------------------------------------------------------------------
    +-- Add a OUHSC's user account to the auxiliary_security database so that they can query the tables to retrieve their API.
    +-- Notice that this only gives the permissions to retrieve the token.  You must still:
    +--   1) grant them API privileges to each appropriate REDCap project, and
    +--   2) copy the API from the REDCap database into the  auxiliary_security database.
    +-- Also, do not give typical users authorization for the 'redcap_private' schema.  The current system allows the to view only their own tokens.
    +-----------------------------------------------------------------------
    +
    +-- STEP #1: Declare the user name.  If everything runs correctly, this should be the only piece of code that you need to modify.
    +print 'Step #1 executing....'
    +USE [master]
    +GO
    +DECLARE @qualified_user_name varchar(255); SET @qualified_user_name = '[OUHSC\lsuarez3]'
    +print 'Resulting login name: ' + @qualified_user_name; print ''
    +
    +--EXEC sp_helplogins @LoginNamePattern=@qualified_user_name
    +--SELECT * FROM master..syslogins WHERE name = @qualified_user_name
    +--SELECT * FROM auxiliary_security.sys.sysusers
    +--SELECT * FROM sys.database_permissions
    +--SELECT * FROM sys.server_principals
    +
    +-----------------------------------------------------------------------
    +-- STEP #2: Create a login for the *server*.
    +print 'Step #2 executing....'
    +DECLARE @sql_create_login nvarchar(max)
    +SET @sql_create_login = 'CREATE LOGIN ' + @qualified_user_name + ' FROM WINDOWS WITH DEFAULT_DATABASE=[auxiliary_security]'
    +EXECUTE sp_executesql @sql_create_login
    +DECLARE @login_count AS INT; SET @login_count = (SELECT COUNT(*) AS login_count FROM master..syslogins WHERE '[' + loginname + ']' = @qualified_user_name)
    +print 'Logins matching desired name should equal 1.  It equals: ' + CONVERT(varchar, @login_count); print ''
    +
    +-----------------------------------------------------------------------
    +-- STEP #3: Create a user account for the *data base*, after switching the database under focus to auxiliary_security.
    +print 'Step #3 executing....'
    +USE [auxiliary_security]
    +DECLARE @sql_create_user nvarchar(max)
    +SET @sql_create_user = 'CREATE USER ' + @qualified_user_name + ' FOR LOGIN ' + @qualified_user_name
    +EXECUTE sp_executesql @sql_create_user
    +DECLARE @user_count AS INT; SET @user_count = (SELECT COUNT(*) AS user_count FROM auxiliary_security.sys.sysusers WHERE '[' + name + ']' = @qualified_user_name)
    +print 'User accounts matching desired name should equal 1.  It equals: ' + CONVERT(varchar, @user_count); print ''
    +
    +-----------------------------------------------------------------------
    +-- STEP #4: Grant appropriate privileges for the 'redcap' schema.
    +print 'Step #4 executing....'
    +DECLARE @sql_grant_schema_redcap nvarchar(max)
    +SET @sql_grant_schema_redcap = 'GRANT EXECUTE ON SCHEMA::[redcap] TO ' + @qualified_user_name
    +EXECUTE sp_executesql @sql_grant_schema_redcap
    +print 'Step #4 executed'; print ''
    +
    +-----------------------------------------------------------------------
    +-- STEP #5: Grant appropriate privileges for the 'Security' schema.
    +print 'Step #5 executing....'
    +DECLARE @sql_grant_schema_security nvarchar(max)
    +SET @sql_grant_schema_security = 'GRANT EXECUTE ON SCHEMA::[security] TO ' + @qualified_user_name
    +EXECUTE sp_executesql @sql_grant_schema_security
    +print 'Step #5 executed'; print ''
    +
    +-----------------------------------------------------------------------
    +-- OPTIONAL STEP: Delete the user from the database (the first line) and then the server (the second line).
    +-- The person's other database user accounts (besides with the auxiliary_security database) will NOT be automatically deleted by these two lines.
    +--USE [auxiliary_security]; DROP USER [OUHSC\lsuarez3]
    +--USE [master]; DROP LOGIN [OUHSC\lsuarez3]
    +
    +-----------------------------------------------------------------------
    +-- REFERENCES & NOTES
    +  --The @qualified_user_name must have both (a) the 'OUHSC' domain qualification, and (b) the square brackets (to escape the backslash).
    +  --Using sp_executesql to add users: http://www.sqlservercentral.com/Forums/Topic497615-359-1.aspx
    +  --Check if a server login exists: http://stackoverflow.com/questions/37275/sql-query-for-logins
    +  --Retrieve database users: http://stackoverflow.com/questions/2445444/how-to-get-a-list-of-users-for-all-instances-databases
    +  --Concatenating strings: http://blog.sqlauthority.com/2010/11/25/sql-server-concat-function-in-sql-server-sql-concatenation/
    +  --DROP USER from database: http://msdn.microsoft.com/en-us/library/ms189438.aspx
    +  --DROP LOGIN from server: http://msdn.microsoft.com/en-us/library/ms188012.aspx
    +  --Declaring variables (eg, the username above): http://technet.microsoft.com/en-us/library/aa258839.aspx
    +  --A different (& non-dynamic) way to establish a user: http://pic.dhe.ibm.com/infocenter/dmndhelp/v8r5m0/index.jsp?topic=%2Fcom.ibm.wbpm.imuc.sbpm.doc%2Ftopics%2Fdb_create_users_nd_aix.html
    +  --If the variable has to cross a 'GO' (which the current version of the script doesn't need): http://stackoverflow.com/questions/937336/is-there-a-way-to-persist-a-variable-across-a-go

    @@ -275,131 +275,131 @@

  • Combine & groom the credentials.
  • Upload to SQL Server.
  • -
    rm(list=ls(all=TRUE)) #Clear the memory for any variables set from any previous runs.
    -
    -# ---- load-sources ------------------------------------------------------------
    -
    -# ---- load-packages -----------------------------------------------------------
    -library(magrittr)
    -requireNamespace("RODBC")
    -requireNamespace("dplyr")
    -requireNamespace("readr")
    -requireNamespace("tibble")
    -requireNamespace("checkmate")
    -
    -# ---- declare-globals ---------------------------------------------------------
    -
    -# The Activity Directory name that should precede each username.
    -#   This should correspond with the result of `SYSTEM_USER`
    -#   (https://msdn.microsoft.com/en-us/library/ms179930.aspx)
    -ldap_prefix <- "OUHSC\\"
    -
    -#Create a SQL statement for each REDCap instance.  Only the `instance` value should change.
    -sql <- "
    -  SELECT username, project_id, api_token
    -  FROM redcap_user_rights
    -  WHERE api_token IS NOT NULL"
    -
    -#Update this ad-hoc CSV.  Each row should represent one REDCap instance.
    -#   Choose any casual name for the first variable, consistent with the `tweak-data` chunk below.
    -#   Enter the exact URL for the second variable.
    -ds_url <- readr::read_csv(paste(
    -  "instance,redcap_uri",
    -  "production,https://redcap-production.ouhsc.edu/redcap/api/",
    -  "dev,https://redcap-dev.ouhsc.edu/redcap/api/",
    -sep="\n"))
    -
    -
    -# ---- load-data ---------------------------------------------------------------
    -
    -# Load the credentials from the first instance.
    -channel <- RODBC::odbcConnect("redcap-production") # odbcGetInfo(channel)
    -ds_prod <- RODBC::sqlQuery(channel, query=sql, stringsAsFactors=F)
    -RODBC::odbcClose(channel); rm(channel)
    -
    -# Load the credentials from the second instance.
    -#   Duplicate or remove this block, dependending on the number of instances.
    -channel <- RODBC::odbcConnect("redcap-dev") # odbcGetInfo(channel)
    -ds_dev  <- RODBC::sqlQuery(channel, query=sql, stringsAsFactors=F)
    -RODBC::odbcClose(channel); rm(channel, sql)
    -
    -# Assert these variables contain valid datasets (instead of a character error message), and
    -#   that at least some rows were returned.
    -#   Adjust this to smaller values if necessary.  It's really just to catch blatant retrieval problems.
    -checkmate::assert_data_frame(ds_bbmc, min.rows=5)
    -checkmate::assert_data_frame(ds_dev , min.rows=5)
    -
    -
    -# ---- tweak-data --------------------------------------------------------------
    -
    -#Label each instance, so they're distinguishable later.  Add/remove lines, depending on the number of campus instances
    -ds_prod$instance <- "production"
    -ds_dev$instance  <- "dev"
    -
    -#Combine the token collection from each instance.  Then prefix the username and include the URL of each instance.
    -ds <- ds_prod %>%
    -  dplyr::union(ds_dev) %>%                                     # Add/remove unions, based on the number of REDCap instances.
    -  dplyr::select_(
    -    "username"             = "`username`"
    -    , "project_id"         = "`project_id`"
    -    , "instance"           = "`instance`"
    -    , "token"              = "`api_token`"
    -  ) %>%
    -  dplyr::arrange(instance, project_id, username) %>%
    -  dplyr::mutate(
    -    username               = paste0(ldap_prefix, username),    # Qualify for the Active Directory.
    -    id                     = seq_len(n())                      # For the sake of a clustered primary key.
    -  ) %>%
    -  dplyr::left_join( ds_url, by="instance")                     # Include the instance URL.
    -
    -rm(ds_prod, ds_dev, ds_url)
    -
    -
    -# ---- verify-values -----------------------------------------------------------
    -# devtools::install_github("OuhscBbmc/OuhscMunge"); OuhscMunge::verify_value_headstart(ds)
    -# Assert that the dataset is well-behaved.
    -checkmate::assert_integer(  ds$id         , any.missing=F , lower=1, upper=2^31-1 , unique=T)
    -checkmate::assert_character(ds$username   , any.missing=F , pattern="^.{1,255}$"            )
    -checkmate::assert_integer(  ds$project_id , any.missing=F , lower=1, upper=2^31-1           )
    -checkmate::assert_character(ds$token      , any.missing=F , pattern="^.{32}$"     , unique=T)
    -checkmate::assert_character(ds$instance   , any.missing=F , pattern="^.{1,255}$"            )
    -checkmate::assert_character(ds$redcap_uri , any.missing=F , pattern="^.{1,255}$"            )
    -
    -testit::assert(
    -  "The `username` x `project_id` x `instance` must be unique.",
    -  sum(duplicated(paste0(ds$username, "-", ds$project_id, "-", ds$instance))) == 0L
    -)
    -
    -testit::assert("There should be at least 10 tokens written." , 10L <= nrow(ds))
    -
    -
    -# ---- specify-columns-to-upload -----------------------------------------------
    -
    -# Dictate the exact columns and order that will be uploaded.
    -columns_to_write <- c("id", "username", "project_id", "instance", "token", "redcap_uri")
    -ds_slim <- ds[, columns_to_write]
    -
    -rm(columns_to_write)
    -
    -
    -# ---- upload-to-db-credential ------------------------------------------------------------
    -
    -#Upload to SQL Server through ODBC.
    -(start_time <- Sys.time())
    -
    -db_table         <- "redcap_private.tbl_credential"
    -channel          <- RODBC::odbcConnect("auxiliary_security") #getSqlTypeInfo("Microsoft SQL Server") #;odbcGetInfo(channel)
    -
    -column_info      <- RODBC::sqlColumns(channel, db_table)
    -var_types        <- as.character(column_info$TYPE_NAME)
    -names(var_types) <- as.character(column_info$COLUMN_NAME)  #var_types
    -
    -RODBC::sqlClear(channel, db_table)
    -RODBC::sqlSave(channel, ds_slim, db_table, append=TRUE, rownames=FALSE, fast=TRUE, varTypes=var_types)
    -RODBC::odbcClose(channel); rm(channel)
    -
    -(elapsed_duration <-  Sys.time() - start_time) #0.6026149 secs 2016-08-29.
    -rm(db_table, column_info, var_types, start_time, elapsed_duration)
    +
    rm(list=ls(all=TRUE)) #Clear the memory for any variables set from any previous runs.
    +
    +# ---- load-sources ------------------------------------------------------------
    +
    +# ---- load-packages -----------------------------------------------------------
    +library(magrittr)
    +requireNamespace("RODBC")
    +requireNamespace("dplyr")
    +requireNamespace("readr")
    +requireNamespace("tibble")
    +requireNamespace("checkmate")
    +
    +# ---- declare-globals ---------------------------------------------------------
    +
    +# The Activity Directory name that should precede each username.
    +#   This should correspond with the result of `SYSTEM_USER`
    +#   (https://msdn.microsoft.com/en-us/library/ms179930.aspx)
    +ldap_prefix <- "OUHSC\\"
    +
    +#Create a SQL statement for each REDCap instance.  Only the `instance` value should change.
    +sql <- "
    +  SELECT username, project_id, api_token
    +  FROM redcap_user_rights
    +  WHERE api_token IS NOT NULL"
    +
    +#Update this ad-hoc CSV.  Each row should represent one REDCap instance.
    +#   Choose any casual name for the first variable, consistent with the `tweak-data` chunk below.
    +#   Enter the exact URL for the second variable.
    +ds_url <- readr::read_csv(paste(
    +  "instance,redcap_uri",
    +  "production,https://redcap-production.ouhsc.edu/redcap/api/",
    +  "dev,https://redcap-dev.ouhsc.edu/redcap/api/",
    +sep="\n"))
    +
    +
    +# ---- load-data ---------------------------------------------------------------
    +
    +# Load the credentials from the first instance.
    +channel <- RODBC::odbcConnect("redcap-production") # odbcGetInfo(channel)
    +ds_prod <- RODBC::sqlQuery(channel, query=sql, stringsAsFactors=F)
    +RODBC::odbcClose(channel); rm(channel)
    +
    +# Load the credentials from the second instance.
    +#   Duplicate or remove this block, dependending on the number of instances.
    +channel <- RODBC::odbcConnect("redcap-dev") # odbcGetInfo(channel)
    +ds_dev  <- RODBC::sqlQuery(channel, query=sql, stringsAsFactors=F)
    +RODBC::odbcClose(channel); rm(channel, sql)
    +
    +# Assert these variables contain valid datasets (instead of a character error message), and
    +#   that at least some rows were returned.
    +#   Adjust this to smaller values if necessary.  It's really just to catch blatant retrieval problems.
    +checkmate::assert_data_frame(ds_bbmc, min.rows=5)
    +checkmate::assert_data_frame(ds_dev , min.rows=5)
    +
    +
    +# ---- tweak-data --------------------------------------------------------------
    +
    +#Label each instance, so they're distinguishable later.  Add/remove lines, depending on the number of campus instances
    +ds_prod$instance <- "production"
    +ds_dev$instance  <- "dev"
    +
    +#Combine the token collection from each instance.  Then prefix the username and include the URL of each instance.
    +ds <- ds_prod %>%
    +  dplyr::union(ds_dev) %>%                                     # Add/remove unions, based on the number of REDCap instances.
    +  dplyr::select_(
    +    "username"             = "`username`"
    +    , "project_id"         = "`project_id`"
    +    , "instance"           = "`instance`"
    +    , "token"              = "`api_token`"
    +  ) %>%
    +  dplyr::arrange(instance, project_id, username) %>%
    +  dplyr::mutate(
    +    username               = paste0(ldap_prefix, username),    # Qualify for the Active Directory.
    +    id                     = seq_len(n())                      # For the sake of a clustered primary key.
    +  ) %>%
    +  dplyr::left_join( ds_url, by="instance")                     # Include the instance URL.
    +
    +rm(ds_prod, ds_dev, ds_url)
    +
    +
    +# ---- verify-values -----------------------------------------------------------
    +# devtools::install_github("OuhscBbmc/OuhscMunge"); OuhscMunge::verify_value_headstart(ds)
    +# Assert that the dataset is well-behaved.
    +checkmate::assert_integer(  ds$id         , any.missing=F , lower=1, upper=2^31-1 , unique=T)
    +checkmate::assert_character(ds$username   , any.missing=F , pattern="^.{1,255}$"            )
    +checkmate::assert_integer(  ds$project_id , any.missing=F , lower=1, upper=2^31-1           )
    +checkmate::assert_character(ds$token      , any.missing=F , pattern="^.{32}$"     , unique=T)
    +checkmate::assert_character(ds$instance   , any.missing=F , pattern="^.{1,255}$"            )
    +checkmate::assert_character(ds$redcap_uri , any.missing=F , pattern="^.{1,255}$"            )
    +
    +testit::assert(
    +  "The `username` x `project_id` x `instance` must be unique.",
    +  sum(duplicated(paste0(ds$username, "-", ds$project_id, "-", ds$instance))) == 0L
    +)
    +
    +testit::assert("There should be at least 10 tokens written." , 10L <= nrow(ds))
    +
    +
    +# ---- specify-columns-to-upload -----------------------------------------------
    +
    +# Dictate the exact columns and order that will be uploaded.
    +columns_to_write <- c("id", "username", "project_id", "instance", "token", "redcap_uri")
    +ds_slim <- ds[, columns_to_write]
    +
    +rm(columns_to_write)
    +
    +
    +# ---- upload-to-db-credential ------------------------------------------------------------
    +
    +#Upload to SQL Server through ODBC.
    +(start_time <- Sys.time())
    +
    +db_table         <- "redcap_private.tbl_credential"
    +channel          <- RODBC::odbcConnect("auxiliary_security") #getSqlTypeInfo("Microsoft SQL Server") #;odbcGetInfo(channel)
    +
    +column_info      <- RODBC::sqlColumns(channel, db_table)
    +var_types        <- as.character(column_info$TYPE_NAME)
    +names(var_types) <- as.character(column_info$COLUMN_NAME)  #var_types
    +
    +RODBC::sqlClear(channel, db_table)
    +RODBC::sqlSave(channel, ds_slim, db_table, append=TRUE, rownames=FALSE, fast=TRUE, varTypes=var_types)
    +RODBC::odbcClose(channel); rm(channel)
    +
    +(elapsed_duration <-  Sys.time() - start_time) #0.6026149 secs 2016-08-29.
    +rm(db_table, column_info, var_types, start_time, elapsed_duration)

    diff --git a/docs/articles/TroubleshootingApiCalls.html b/docs/articles/TroubleshootingApiCalls.html index e6bb8bb8..c3007bed 100644 --- a/docs/articles/TroubleshootingApiCalls.html +++ b/docs/articles/TroubleshootingApiCalls.html @@ -55,6 +55,9 @@ @@ -96,7 +96,7 @@
    @@ -172,84 +169,84 @@

    1. Is httr installed on the user’s local machine? If so, running library(httr) should not produce any error messages if you’re starting with a fresh session of R:

      -
      > library(httr)
      +
    2. Does the user have the most recent version of httr? There are several ways to do this, but the easiest is probably to run update.packages(ask=FALSE, repos="http://cran.rstudio.com"). The optional argument ask prevents the user from needing to respond ‘Y’ to each outdated package.

    3. Can the user query a test project using httr? Both the redcapAPI and REDCapR packages employ something similar to the following function in httr. If you’re curious, here is the relevant source code for redcapAPI and REDCapR.

      If this check fails, consider attempting again with the uri and token used above in the Postman example.

      This check avoids checking the SSL certificate in order to simplify the troubleshooting. SSL verification is supported by default in the PyCap, redcapAPI, and REDCapR packages.

      -
      redcap_uri <- "https://bbmc.ouhsc.edu/redcap/api/"
      -token      <- "9A81268476645C4E5F03428B8AC3AA7B"
      -
      -raw_text <- RCurl::postForm(
      -  uri                         = redcap_uri
      -  , token                     = token
      -  , content                   = 'record'
      -  , format                    = 'csv'
      -  , type                      = 'flat'
      -  , rawOrLabel                = 'raw'
      -  , exportDataAccessGroups    = 'true'
      -  , .opts                     = RCurl::curlOptions(ssl.verifypeer=FALSE)
      -)
      +

      Alternatively, you can try using the httr package, which uses RCurl underneath. REDCapR and a recent fork of redcap actually uses httr directly, instead of RCurl. As of 2014-07-06, this works with the Windows 8 version for libcurl (which is underneath `RCurl), but not with some Linux versions; in this case pass the location of the SSL cert file.

      -
      post_body <- list(
      -  token                       = token,
      -  content                     = 'record',
      -  format                      = 'csv',
      -  type                        = 'flat',
      -  rawOrLabel                  = 'raw',
      -  exportDataAccessGroups      = 'true'
      -)
      -
      -raw_text <- httr::POST(
      -  url                         = redcap_uri,
      -  body                        = post_body,
      -  config                      = httr::config(ssl.verifypeer=FALSE),
      -  httr::verbose() #Remove this line to suppress the frequent console updates.
      -)
      +
    4. Can the user query a subset of their project using RCurl? This step is like the previous one, but with two differences. First, it’s using their REDCap project (instead of the test project). Second, it pulls fewer records, and a smaller collection of fields. Subsetting can help troubleshoot by avoiding (and thus identifying) cells with problematic values.

      Notice this call to RCurl::postForm() now passes values to the records and fields parameters. Also notice the value is a single long string, rather a vector of shorter strings (which is more natural to most R users).

      -
      redcap_uri         <- "https://the.urlofyourinsitution.edu/api/"
      -token              <- "your-secret-token"
      -records_collapsed  <- "1,2,3"                             # Assumes dataset contains ID values of 1-3.
      -fields_collapsed   <- "record_id,name_first,name_last"    # Assumes dataset contains these variables.
      -
      -raw_text <- RCurl::postForm(
      -  uri                          = redcap_uri
      -  , token                      = token
      -  , content                    = 'record'
      -  , format                     = 'csv'
      -  , type                       = 'flat'
      -  , rawOrLabel                 = 'raw'
      -  , exportDataAccessGroups     = 'true'
      -  , records                    = records_collapsed
      -  , fields                     = fields_collapsed
      -  , .opts                      = RCurl::curlOptions(ssl.verifypeer=FALSE)
      -)
      +
    5. Can the user query an entire project using RCurl? There are two advantages of trying a subset of the data. First, small datasets avoid the time-out errors that plague large datasets. Second, it may avoid problematic values being passed through the pipeline. If the current check fails but the previous check succeedes, then experiment with different expanses of records and fields. This should help determine which values are causing the problems, or if there’s simply too much data being pulled in one pass.

      If the desired dataset is too large, consider if you can prune unnecessary records or fields. If not, one solution is to pull smaller, multiple batches using the API, then reassemble them. The redcap_read() function in REDCapR does this automatically, and allows the user to specify a batch_size.

      -
      redcap_uri                  <- "https://the.urlofyourinsitution.edu/api/"
      -token                       <- "your-secret-token"
      -records_collapsed           <- NULL
      -fields_collapsed            <- NULL
      -
      -raw_text <- RCurl::postForm(
      -  uri                        = redcap_uri
      -  , token                    = token
      -  , content                  = 'record'
      -  , format                   = 'csv'
      -  , type                     = 'flat'
      -  , rawOrLabel               = 'raw'
      -  , exportDataAccessGroups   = 'true'
      -  , records                  = records_collapsed
      -  , fields                   = fields_collapsed
      -  , .opts                    = RCurl::curlOptions(ssl.verifypeer=FALSE)
      -)
      +

    @@ -259,75 +256,24 @@

    REDCapR is a package that uses cURL (via httr) to communicate with REDCap, and wraps convenience functions around it to reduce the size and complexity of the user’s code. The package’s basic functions are demonstrated in a vignette and are documented in its reference manual (a downloadable pdf of the functions are also available).

    If you’re not using REDCapR, you can skip this section and proceed to ‘Importing into REDCap from R’ below.

      +
    1. Is REDCapR installed on the user’s machine? Currently the easiest way to install REDCapR is with the devtools. The follow code installs devtools, then installs REDCapR. r install.packages("devtools", repos="http://cran.rstudio.com") devtools::install_github(repo="OuhscBbmc/REDCapR")

    2. +
    3. Does REDCapR load successfully on the user’s machine? If so, running library(REDCapR) should produce the following output if you’re starting with a fresh session of R: r library(REDCapR) ## Loading required package: REDCapR

    4. -

      Is REDCapR installed on the user’s machine? Currently the easiest way to install REDCapR is with the devtools. The follow code installs devtools, then installs REDCapR.

      -
      install.packages("devtools", repos="http://cran.rstudio.com")
      -devtools::install_github(repo="OuhscBbmc/REDCapR")
      +

      Can the user export from an example project? This is the same fake data hosted by the OUHSC BBMC as in the previous section. r library(REDCapR) #Load the package into the current R session. uri <- "https://bbmc.ouhsc.edu/redcap/api/" token <- "9A81268476645C4E5F03428B8AC3AA7B" redcap_read(redcap_uri=uri, token=token)$data

      +

      The previous code should produce similar output. Notice there are five rows and the columns will wrap around if your console window is too narrow. ``` 5 records and 1 columns were read from REDCap in 0.41 seconds. Starting to read 5 records at 2014-06-27 17:19:49 Reading batch 1 of 1, with ids 1 through 5. 5 records and 16 columns were read from REDCap in 0.42 seconds.

      +

      record_id name_first name_last address telephone email 1 1 Nutmeg Nutmouse 14 Rose Cottage St.UK, 323232 (432) 456-4848 nutty@mouse.com 2 2 Tumtum Nutmouse 14 Rose Cottage Blvd.UK 34243 (234) 234-2343 tummy@mouse.comm 3 3 Marcus Wood 243 Hill St.OK 73402 (433) 435-9865 mw@mwood.net 4 4 Trudy DAG 342 ElmTX, 75116 (987) 654-3210 peroxide@blonde.com 5 5 John Lee Walker Hotel SuiteOrleans LA, 70115 (333) 333-4444 left@hippocket.com

      +
            dob age ethnicity race sex height weight   bmi
      +

      1 2003-08-30 10 1 2 0 5.00 1 400.0 2 2003-03-10 10 1 6 1 6.00 1 277.8 3 1934-04-09 79 0 4 1 180.00 80 24.7 4 1952-11-02 61 1 4 0 165.00 54 19.8 5 1955-04-15 58 1 4 1 193.04 104 27.9

      +
                                                                                                        comments
      +

      1 Character in a book, with some guessing 2 A mouse character from a good book 3 completely made up 4 This record doesn’t have a DAG assignedcall up Trudy on the telephoneher a letter in the mail 5 Had a hand for trouble and a eye for cashhad a gold watch chain and a black mustache

      +

      demographics_complete 1 2 2 2 3 2 4 2 5 2 ```

    5. -

      Does REDCapR load successfully on the user’s machine? If so, running library(REDCapR) should produce the following output if you’re starting with a fresh session of R:

      -
      library(REDCapR)
      -## Loading required package: REDCapR
      -
    6. -
    7. -

      Can the user export from an example project? This is the same fake data hosted by the OUHSC BBMC as in the previous section.

      -
      library(REDCapR) #Load the package into the current R session.
      -uri   <- "https://bbmc.ouhsc.edu/redcap/api/"
      -token <- "9A81268476645C4E5F03428B8AC3AA7B"
      -redcap_read(redcap_uri=uri, token=token)$data
      -

      The previous code should produce similar output. Notice there are five rows and the columns will wrap around if your console window is too narrow.

      -
      5 records and 1 columns were read from REDCap in 0.41 seconds.
      -Starting to read 5 records  at 2014-06-27 17:19:49
      -Reading batch 1 of 1, with ids 1 through 5.
      -5 records and 16 columns were read from REDCap in 0.42 seconds.
      -
      -  record_id name_first name_last                                 address      telephone               email
      -1         1     Nutmeg  Nutmouse 14 Rose Cottage St.\nKenning UK, 323232 (432) 456-4848     nutty@mouse.com
      -2         2     Tumtum  Nutmouse 14 Rose Cottage Blvd.\nKenning UK 34243 (234) 234-2343    tummy@mouse.comm
      -3         3     Marcus      Wood          243 Hill St.\nGuthrie OK 73402 (433) 435-9865        mw@mwood.net
      -4         4      Trudy       DAG          342 Elm\nDuncanville TX, 75116 (987) 654-3210 peroxide@blonde.com
      -5         5   John Lee    Walker      Hotel Suite\nNew Orleans LA, 70115 (333) 333-4444  left@hippocket.com
      -
      -         dob age ethnicity race sex height weight   bmi
      -1 2003-08-30  10         1    2   0   5.00      1 400.0
      -2 2003-03-10  10         1    6   1   6.00      1 277.8
      -3 1934-04-09  79         0    4   1 180.00     80  24.7
      -4 1952-11-02  61         1    4   0 165.00     54  19.8
      -5 1955-04-15  58         1    4   1 193.04    104  27.9
      -
      -                                                                                                     comments
      -1                                                                     Character in a book, with some guessing
      -2                                                                          A mouse character from a good book
      -3                                                                                          completely made up
      -4 This record doesn't have a DAG assigned\n\nSo call up Trudy on the telephone\nSend her a letter in the mail
      -5                 Had a hand for trouble and a eye for cash\n\nHe had a gold watch chain and a black mustache
      -
      -  demographics_complete
      -1                     2
      -2                     2
      -3                     2
      -4                     2
      -5                     2
      -
    8. -
    9. -

      Can the user export from their own project? The code is similar to the previous check, but the uri and token values will need to be modified.

      -
      library(REDCapR) #Load the package into the current R session, if you haven't already.
      -redcap_uri       <- "https://the.urlofyourinsitution.edu/api/"
      -token            <- "your-secret-token"
      -redcap_read(redcap_uri=uri, token=token)$data
      -

      Alternatively, a redcap_project object can be declared initially, which makes subsequent calls cleaner when the token and url are required only the when the object is declared.

      -
      library(REDCapR) #Load the package into the current R session, if you haven't already.
      -uri               <- "https://bbmc.ouhsc.edu/redcap/api/"
      -token             <- "9A81268476645C4E5F03428B8AC3AA7B"
      -project           <- redcap_project$new(redcap_uri=uri, token=token)
      -
      -ds_three_columns  <- project$read(fields=c("record_id", "sex", "age"))$data
      -
      -ids_of_males      <- ds_three_columns$record_id[ds_three_columns$sex==1]
      -ids_of_minors     <- ds_three_columns$record_id[ds_three_columns$age < 18]
      -
      -ds_males          <- project$read(records=ids_of_males, batch_size=2)$data
      -ds_minors         <- project$read(records=ids_of_minors)$data
      +

      Can the user export from their own project? The code is similar to the previous check, but the uri and token values will need to be modified. r library(REDCapR) #Load the package into the current R session, if you haven't already. redcap_uri <- "https://the.urlofyourinsitution.edu/api/" token <- "your-secret-token" redcap_read(redcap_uri=uri, token=token)$data

      +

      Alternatively, a redcap_project object can be declared initially, which makes subsequent calls cleaner when the token and url are required only the when the object is declared. ```r library(REDCapR) #Load the package into the current R session, if you haven’t already. uri <- “https://bbmc.ouhsc.edu/redcap/api/” token <- “9A81268476645C4E5F03428B8AC3AA7B” project <- redcap_project$new(redcap_uri=uri, token=token)

      +

      ds_three_columns <- project\(read(fields=c("record_id", "sex", "age"))\)data

      +

      ids_of_males <- ds_three_columns\(record_id[ds_three_columns\)sex==1] ids_of_minors <- ds_three_columns\(record_id[ds_three_columns\)age < 18]

      +

      ds_males <- project\(read(records=ids_of_males, batch_size=2)\)data ds_minors <- project\(read(records=ids_of_minors)\)data ```

    10. Is the export operation still unsuccessful using REDCapR? If so the “Can the user query a entire REDCap project using RCurl?” check succeeded, but the REDCapR checks did not, consider posting a new GitHub issue to the package developers.

    @@ -339,63 +285,20 @@

    If you’re not using redcapAPI, you can skip this section and proceed to ‘Importing into REDCap from R’ below. More specific discussion about redcapAPI can be found at the package’s wiki.

    1. -

      Is redcapAPI installed on the user’s machine? Currently, the easiest way to install redcapAPI is from CRAN.

      -
      install.packages("redcapAPI")
      -

      Developmental versions may be available on GitHub.

      -
      install.packages("devtools", repos="http://cran.rstudio.com")
      -devtools::install_github(repo="nutterb/redcapAPI")
      +

      Is redcapAPI installed on the user’s machine? Currently, the easiest way to install redcapAPI is from CRAN. r install.packages("redcapAPI")

      +

      Developmental versions may be available on GitHub. r install.packages("devtools", repos="http://cran.rstudio.com") devtools::install_github(repo="nutterb/redcapAPI")

    2. +
    3. Does redcapAPI load successfully on the user’s machine? If so, running library(redcapAPI) should produce the following output if you’re starting with a fresh session of R: r library(redcapAPI) ## Loading required package: redcapAPI

    4. -

      Does redcapAPI load successfully on the user’s machine? If so, running library(redcapAPI) should produce the following output if you’re starting with a fresh session of R:

      -
      library(redcapAPI)
      -## Loading required package: redcapAPI
      -
    5. -
    6. -

      Can the user export from an example project? This is the same fake data hosted by the OUHSC BBMC as in the previous section.

      -
      library(redcapAPI) #Load the package into the current R session.
      -rcon <- redcapConnection(
      -  url   = "https://bbmc.ouhsc.edu/redcap/api/",
      -  token = "9A81268476645C4E5F03428B8AC3AA7B"
      -)
      -exportRecords(rcon)
      -

      The previous code should produce similar output. Notice there are five rows and the columns will wrap around if your console window is too narrow.

      -
      record_id name_first name_last                                 address      telephone               email
      -1         1     Nutmeg  Nutmouse 14 Rose Cottage St.\nKenning UK, 323232 (432) 456-4848     nutty@mouse.com
      -2         2     Tumtum  Nutmouse 14 Rose Cottage Blvd.\nKenning UK 34243 (234) 234-2343    tummy@mouse.comm
      -3         3     Marcus      Wood          243 Hill St.\nGuthrie OK 73402 (433) 435-9865        mw@mwood.net
      -4         4      Trudy       DAG          342 Elm\nDuncanville TX, 75116 (987) 654-3210 peroxide@blonde.com
      -5         5   John Lee    Walker      Hotel Suite\nNew Orleans LA, 70115 (333) 333-4444  left@hippocket.com
      -
      -       dob age ethnicity race sex height weight   bmi
      -1 2003-08-30  10         1    2   0   5.00      1 400.0
      -2 2003-03-10  10         1    6   1   6.00      1 277.8
      -3 1934-04-09  79         0    4   1 180.00     80  24.7
      -4 1952-11-02  61         1    4   0 165.00     54  19.8
      -5 1955-04-15  58         1    4   1 193.04    104  27.9
      -
      -                                                                                                   comments
      -1                                                                     Character in a book, with some guessing
      -2                                                                          A mouse character from a good book
      -3                                                                                          completely made up
      -4 This record doesn't have a DAG assigned\n\nSo call up Trudy on the telephone\nSend her a letter in the mail
      -5                 Had a hand for trouble and a eye for cash\n\nHe had a gold watch chain and a black mustache
      -
      -demographics_complete
      -1                     2
      -2                     2
      -3                     2
      -4                     2
      -5                     2
      -
    7. -
    8. -

      Can the user export from their own project? The code is similar to the previous check, but the uri and token values will need to be modified.

      -
      library(redcapAPI) #Load the package into the current R session, if you haven't already.
      -rcon <- redcapConnection(
      -  url   = "https://the.urlofyourinsitution.edu/api/", # Adapt this to your server.
      -  token = "your-secret-token"                         # Adapt this to your user's token.
      -)
      -exportRecords(rcon)
      +

      Can the user export from an example project? This is the same fake data hosted by the OUHSC BBMC as in the previous section. r library(redcapAPI) #Load the package into the current R session. rcon <- redcapConnection( url = "https://bbmc.ouhsc.edu/redcap/api/", token = "9A81268476645C4E5F03428B8AC3AA7B" ) exportRecords(rcon)

      +

      The previous code should produce similar output. Notice there are five rows and the columns will wrap around if your console window is too narrow. ``` record_id name_first name_last address telephone email 1 1 Nutmeg Nutmouse 14 Rose Cottage St.UK, 323232 (432) 456-4848 nutty@mouse.com 2 2 Tumtum Nutmouse 14 Rose Cottage Blvd.UK 34243 (234) 234-2343 tummy@mouse.comm 3 3 Marcus Wood 243 Hill St.OK 73402 (433) 435-9865 mw@mwood.net 4 4 Trudy DAG 342 ElmTX, 75116 (987) 654-3210 peroxide@blonde.com 5 5 John Lee Walker Hotel SuiteOrleans LA, 70115 (333) 333-4444 left@hippocket.com

      +
          dob age ethnicity race sex height weight   bmi
      +

      1 2003-08-30 10 1 2 0 5.00 1 400.0 2 2003-03-10 10 1 6 1 6.00 1 277.8 3 1934-04-09 79 0 4 1 180.00 80 24.7 4 1952-11-02 61 1 4 0 165.00 54 19.8 5 1955-04-15 58 1 4 1 193.04 104 27.9

      +
                                                                                                      comments
      +

      1 Character in a book, with some guessing 2 A mouse character from a good book 3 completely made up 4 This record doesn’t have a DAG assignedcall up Trudy on the telephoneher a letter in the mail 5 Had a hand for trouble and a eye for cashhad a gold watch chain and a black mustache

      +

      demographics_complete 1 2 2 2 3 2 4 2 5 2 ```

    9. +
    10. Can the user export from their own project? The code is similar to the previous check, but the uri and token values will need to be modified. r library(redcapAPI) #Load the package into the current R session, if you haven't already. rcon <- redcapConnection( url = "https://the.urlofyourinsitution.edu/api/", # Adapt this to your server. token = "your-secret-token" # Adapt this to your user's token. ) exportRecords(rcon)

    11. Is the export operation still unsuccessful using redcapAPI? If so the “Can the user query a entire REDCap project using RCurl?” check succeeded, but the redcapAPI checks did not, consider posting a new GitHub issue to the package developers.

    @@ -428,16 +331,14 @@

    Other good resources

    diff --git a/docs/articles/advanced-redcapr-operations.html b/docs/articles/advanced-redcapr-operations.html index b37a62b1..9add1d1e 100644 --- a/docs/articles/advanced-redcapr-operations.html +++ b/docs/articles/advanced-redcapr-operations.html @@ -55,6 +55,9 @@ @@ -96,7 +96,7 @@

    Converting from tall/long to wide

    Disclaimer: Occasionally we’re asked for a longitudinal dataset to be converted from a “long/tall format” (where typically each row is one observation for a participant) to a “wide format” (where each row is on participant). Usually we advise against it. Besides all the database benefits of a long structure, a wide structure restricts your options with the stat routine. No modern longitudinal analysis procedures (eg, growth curve models or multilevel/hierarchical models) accept wide. You’re pretty much stuck with repeated measures anova, which is very inflexible for real-world medical-ish analyses. It requires a patient to have a measurement at every time point; otherwise the anova excludes the patient entirely.

    However we like going wide to produce visual tables for publications, and here’s one way to do it in R. First retrieve the dataset from REDCap.

    -
    library(magrittr); 
    -suppressPackageStartupMessages(requireNamespace("dplyr"))
    -suppressPackageStartupMessages(requireNamespace("tidyr"))
    -events_to_retain  <- c("dose_1_arm_1", "visit_1_arm_1", "dose_2_arm_1", "visit_2_arm_1")
    -
    -ds_long <- REDCapR::redcap_read_oneshot(redcap_uri=uri, token=token_longitudinal)$data
    -
    #> 18 records and 125 columns were read from REDCap in 0.6 seconds.  The http status code was 200.
    -
    ds_long %>% 
    -  dplyr::select(study_id, redcap_event_name, pmq1, pmq2, pmq3, pmq4)
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -study id - -redcap event name - -pmq1 - -pmq2 - -pmq3 - -pmq4 -
    -100 - -enrollment_arm_1 - -NA - -NA - -NA - -NA -
    -100 - -dose_1_arm_1 - -2 - -2 - -1 - -1 -
    -100 - -visit_1_arm_1 - -1 - -0 - -0 - -0 -
    -100 - -dose_2_arm_1 - -3 - -1 - -0 - -0 -
    -100 - -visit_2_arm_1 - -0 - -1 - -0 - -0 -
    -100 - -final_visit_arm_1 - -NA - -NA - -NA - -NA -
    -220 - -enrollment_arm_1 - -NA - -NA - -NA - -NA -
    -220 - -dose_1_arm_1 - -0 - -1 - -0 - -2 -
    -220 - -visit_1_arm_1 - -0 - -3 - -1 - -0 -
    -220 - -dose_2_arm_1 - -1 - -2 - -0 - -1 -
    -220 - -visit_2_arm_1 - -3 - -4 - -1 - -0 -
    -220 - -final_visit_arm_1 - -NA - -NA - -NA - -NA -
    -304 - -enrollment_arm_2 - -NA - -NA - -NA - -NA -
    -304 - -deadline_to_opt_ou_arm_2 - -NA - -NA - -NA - -NA -
    -304 - -first_dose_arm_2 - -0 - -1 - -0 - -0 -
    -304 - -first_visit_arm_2 - -2 - -0 - -0 - -0 -
    -304 - -final_visit_arm_2 - -NA - -NA - -NA - -NA -
    -304 - -deadline_to_return_arm_2 - -NA - -NA - -NA - -NA -
    + +
    #> 18 records and 125 columns were read from REDCap in 0.4 seconds.  The http status code was 200.
    +
    ds_long %>% 
    +  dplyr::select(study_id, redcap_event_name, pmq1, pmq2, pmq3, pmq4)
    +
    #>    study_id        redcap_event_name pmq1 pmq2 pmq3 pmq4
    +#> 1       100         enrollment_arm_1   NA   NA   NA   NA
    +#> 2       100             dose_1_arm_1    2    2    1    1
    +#> 3       100            visit_1_arm_1    1    0    0    0
    +#> 4       100             dose_2_arm_1    3    1    0    0
    +#> 5       100            visit_2_arm_1    0    1    0    0
    +#> 6       100        final_visit_arm_1   NA   NA   NA   NA
    +#> 7       220         enrollment_arm_1   NA   NA   NA   NA
    +#> 8       220             dose_1_arm_1    0    1    0    2
    +#> 9       220            visit_1_arm_1    0    3    1    0
    +#> 10      220             dose_2_arm_1    1    2    0    1
    +#> 11      220            visit_2_arm_1    3    4    1    0
    +#> 12      220        final_visit_arm_1   NA   NA   NA   NA
    +#> 13      304         enrollment_arm_2   NA   NA   NA   NA
    +#> 14      304 deadline_to_opt_ou_arm_2   NA   NA   NA   NA
    +#> 15      304         first_dose_arm_2    0    1    0    0
    +#> 16      304        first_visit_arm_2    2    0    0    0
    +#> 17      304        final_visit_arm_2   NA   NA   NA   NA
    +#> 18      304 deadline_to_return_arm_2   NA   NA   NA   NA

    When widening only one variable (eg, pmq1), the code’s pretty simple:

    -
    ds_wide <- ds_long %>% 
    -  dplyr::select(study_id, redcap_event_name, pmq1) %>% 
    -  dplyr::filter(redcap_event_name %in% events_to_retain) %>% 
    -  tidyr::spread(key=redcap_event_name, value=pmq1)
    -ds_wide
    - - - - - - - - - - - - - - - - - - - - - - - - -
    -study id - -dose 1 arm 1 - -dose 2 arm 1 - -visit 1 arm 1 - -visit 2 arm 1 -
    -100 - -2 - -3 - -1 - -0 -
    -220 - -0 - -1 - -0 - -3 -
    +
    ds_wide <- ds_long %>% 
    +  dplyr::select(study_id, redcap_event_name, pmq1) %>% 
    +  dplyr::filter(redcap_event_name %in% events_to_retain) %>% 
    +  tidyr::spread(key=redcap_event_name, value=pmq1)
    +ds_wide
    +
    #>   study_id dose_1_arm_1 dose_2_arm_1 visit_1_arm_1 visit_2_arm_1
    +#> 1      100            2            3             1             0
    +#> 2      220            0            1             0             3

    When widening more than one variable (eg, pmq1 - pmq4), it’s usually easiest to go even longer/taller (eg, ds_eav) before reversing direction and going wide:

    -
    pattern <- "^(\\w+?)_arm_(\\d)$"
    -
    -ds_eav <- ds_long %>% 
    -  dplyr::select(study_id, redcap_event_name, pmq1, pmq2, pmq3, pmq4) %>% 
    -  dplyr::mutate(
    -    event      = sub(pattern, "\\1", redcap_event_name),
    -    arm        = as.integer(sub(pattern, "\\2", redcap_event_name))
    -  ) %>% 
    -  dplyr::select(study_id, event, arm, pmq1, pmq2, pmq3, pmq4) %>% 
    -  tidyr::gather(key=key, value=value, pmq1, pmq2, pmq3, pmq4) %>% 
    -  dplyr::filter(!(event %in% c(
    -    "enrollment", "final_visit", "deadline_to_return", "deadline_to_opt_ou")
    -  )) %>% 
    -  dplyr::mutate( # Simulate correcting for mismatched names across arms:
    -    event = dplyr::recode(event, "first_dose"="dose_1", "first_visit"="visit_1"),
    -    key = paste0(event, "_", key)
    -  ) %>% 
    -  dplyr::select(-event)
    -
    -# Show the first 10 rows of the EAV table.
    -ds_eav %>% 
    -  head(10)
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -study id - -arm - -key - -value -
    -100 - -1 - -dose_1_pmq1 - -2 -
    -100 - -1 - -visit_1_pmq1 - -1 -
    -100 - -1 - -dose_2_pmq1 - -3 -
    -100 - -1 - -visit_2_pmq1 - -0 -
    -220 - -1 - -dose_1_pmq1 - -0 -
    -220 - -1 - -visit_1_pmq1 - -0 -
    -220 - -1 - -dose_2_pmq1 - -1 -
    -220 - -1 - -visit_2_pmq1 - -3 -
    -304 - -2 - -dose_1_pmq1 - -0 -
    -304 - -2 - -visit_1_pmq1 - -2 -
    -
    # Spread the EAV to wide.
    -ds_wide <- ds_eav %>% 
    -  tidyr::spread(key=key, value=value)
    -ds_wide
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -study id - -arm - -dose 1 pmq1 - -dose 1 pmq2 - -dose 1 pmq3 - -dose 1 pmq4 - -dose 2 pmq1 - -dose 2 pmq2 - -dose 2 pmq3 - -dose 2 pmq4 - -visit 1 pmq1 - -visit 1 pmq2 - -visit 1 pmq3 - -visit 1 pmq4 - -visit 2 pmq1 - -visit 2 pmq2 - -visit 2 pmq3 - -visit 2 pmq4 -
    -100 - -1 - -2 - -2 - -1 - -1 - -3 - -1 - -0 - -0 - -1 - -0 - -0 - -0 - -0 - -1 - -0 - -0 -
    -220 - -1 - -0 - -1 - -0 - -2 - -1 - -2 - -0 - -1 - -0 - -3 - -1 - -0 - -3 - -4 - -1 - -0 -
    -304 - -2 - -0 - -1 - -0 - -0 - -NA - -NA - -NA - -NA - -2 - -0 - -0 - -0 - -NA - -NA - -NA - -NA -
    +
    pattern <- "^(\\w+?)_arm_(\\d)$"
    +
    +ds_eav <- ds_long %>% 
    +  dplyr::select(study_id, redcap_event_name, pmq1, pmq2, pmq3, pmq4) %>% 
    +  dplyr::mutate(
    +    event      = sub(pattern, "\\1", redcap_event_name),
    +    arm        = as.integer(sub(pattern, "\\2", redcap_event_name))
    +  ) %>% 
    +  dplyr::select(study_id, event, arm, pmq1, pmq2, pmq3, pmq4) %>% 
    +  tidyr::gather(key=key, value=value, pmq1, pmq2, pmq3, pmq4) %>% 
    +  dplyr::filter(!(event %in% c(
    +    "enrollment", "final_visit", "deadline_to_return", "deadline_to_opt_ou")
    +  )) %>% 
    +  dplyr::mutate( # Simulate correcting for mismatched names across arms:
    +    event = dplyr::recode(event, "first_dose"="dose_1", "first_visit"="visit_1"),
    +    key = paste0(event, "_", key)
    +  ) %>% 
    +  dplyr::select(-event)
    +
    +# Show the first 10 rows of the EAV table.
    +ds_eav %>% 
    +  head(10)
    +
    #>    study_id arm          key value
    +#> 1       100   1  dose_1_pmq1     2
    +#> 2       100   1 visit_1_pmq1     1
    +#> 3       100   1  dose_2_pmq1     3
    +#> 4       100   1 visit_2_pmq1     0
    +#> 5       220   1  dose_1_pmq1     0
    +#> 6       220   1 visit_1_pmq1     0
    +#> 7       220   1  dose_2_pmq1     1
    +#> 8       220   1 visit_2_pmq1     3
    +#> 9       304   2  dose_1_pmq1     0
    +#> 10      304   2 visit_1_pmq1     2
    + +
    #>   study_id arm dose_1_pmq1 dose_1_pmq2 dose_1_pmq3 dose_1_pmq4 dose_2_pmq1
    +#> 1      100   1           2           2           1           1           3
    +#> 2      220   1           0           1           0           2           1
    +#> 3      304   2           0           1           0           0          NA
    +#>   dose_2_pmq2 dose_2_pmq3 dose_2_pmq4 visit_1_pmq1 visit_1_pmq2
    +#> 1           1           0           0            1            0
    +#> 2           2           0           1            0            3
    +#> 3          NA          NA          NA            2            0
    +#>   visit_1_pmq3 visit_1_pmq4 visit_2_pmq1 visit_2_pmq2 visit_2_pmq3
    +#> 1            0            0            0            1            0
    +#> 2            1            0            3            4            1
    +#> 3            0            0           NA           NA           NA
    +#>   visit_2_pmq4
    +#> 1            0
    +#> 2            0
    +#> 3           NA

    SSL Options

    The official cURL site discusses the process of using SSL to verify the server being connected to.

    Use the SSL cert file that come with the openssl package.

    -
    cert_location <- system.file("cacert.pem", package="openssl")
    -if( file.exists(cert_location) ) {
    -  config_options         <- list(cainfo=cert_location)
    -  ds_different_cert_file <- redcap_read_oneshot(
    -    redcap_uri     = uri,
    -    token          = token_simple,
    -    config_options = config_options
    -  )$data
    -}
    -
    #> 5 records and 24 columns were read from REDCap in 0.7 seconds.  The http status code was 200.
    -

    Force the connection to use SSL=3 (which is not preferred, and possibly insecure).

    -
    config_options <- list(sslversion=3)
    -ds_ssl_3 <- redcap_read_oneshot(
    -  redcap_uri     = uri,
    -  token          = token_simple,
    -  config_options = config_options
    -)$data
    +
    #> 5 records and 24 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    -
    config_options <- list(ssl.verifypeer=FALSE)
    -ds_no_ssl <- redcap_read_oneshot(
    -   redcap_uri     = uri,
    -   token          = token_simple,
    -   config_options = config_options
    -)$data
    +

    Force the connection to use SSL=3 (which is not preferred, and possibly insecure).

    +
    #> 5 records and 24 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    + +
    #> 5 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.

    Session Information

    For the sake of documentation and reproducibility, the current report was rendered in the following environment. Click the line below to expand.

    -

    Environment

    -
    #> ─ Session info ──────────────────────────────────────────────────────────
    -#>  setting  value                       
    -#>  version  R version 3.4.4 (2018-03-15)
    -#>  os       Ubuntu 18.04 LTS            
    -#>  system   x86_64, linux-gnu           
    -#>  ui       X11                         
    -#>  language (EN)                        
    -#>  collate  en_US.UTF-8                 
    -#>  tz       America/Chicago             
    -#>  date     2018-07-12                  
    +

    Environment

    +
    #> - Session info ----------------------------------------------------------
    +#>  setting  value                                      
    +#>  version  R version 3.5.1 Patched (2018-08-06 r75070)
    +#>  os       Windows >= 8 x64                           
    +#>  system   x86_64, mingw32                            
    +#>  ui       RTerm                                      
    +#>  language (EN)                                       
    +#>  collate  English_United States.1252                 
    +#>  tz       America/Chicago                            
    +#>  date     2018-08-10                                 
     #> 
    -#> ─ Packages ──────────────────────────────────────────────────────────────
    +#> - Packages --------------------------------------------------------------
     #>  package     * version     date       source                          
    -#>  assertthat    0.2.0       2017-04-11 cran (@0.2.0)                   
    -#>  backports     1.1.2       2017-12-13 cran (@1.1.2)                   
    -#>  bindr         0.1.1       2018-03-13 CRAN (R 3.4.3)                  
    -#>  bindrcpp    * 0.2.2       2018-03-29 CRAN (R 3.4.3)                  
    -#>  checkmate     1.8.6       2018-04-10 Github (mllg/checkmate@489319a) 
    -#>  clisymbols    1.2.0       2017-05-21 CRAN (R 3.4.3)                  
    -#>  colorspace    1.3-2       2016-12-14 CRAN (R 3.4.3)                  
    -#>  commonmark    1.5         2018-04-28 CRAN (R 3.4.4)                  
    -#>  crayon        1.3.4       2017-09-16 CRAN (R 3.4.3)                  
    -#>  curl          3.2         2018-03-28 CRAN (R 3.4.4)                  
    -#>  desc          1.2.0       2018-05-01 CRAN (R 3.4.4)                  
    -#>  devtools      1.13.6      2018-06-27 CRAN (R 3.4.4)                  
    -#>  digest        0.6.15      2018-01-28 CRAN (R 3.4.3)                  
    -#>  dplyr         0.7.6       2018-06-29 CRAN (R 3.4.4)                  
    -#>  evaluate      0.10.1      2017-06-24 CRAN (R 3.4.3)                  
    -#>  fs            1.2.3       2018-06-08 CRAN (R 3.4.4)                  
    -#>  glue          1.2.0       2017-10-29 cran (@1.2.0)                   
    -#>  highr         0.7         2018-06-09 CRAN (R 3.4.4)                  
    -#>  hms           0.4.2.9000  2018-05-26 Github (tidyverse/hms@14e74ab)  
    -#>  htmltools     0.3.6       2017-04-28 CRAN (R 3.4.3)                  
    -#>  httr          1.3.1       2017-08-20 CRAN (R 3.4.3)                  
    -#>  kableExtra    0.9.0       2018-05-21 CRAN (R 3.4.4)                  
    -#>  knitr       * 1.20        2018-02-20 CRAN (R 3.4.3)                  
    -#>  magrittr    * 1.5         2014-11-22 cran (@1.5)                     
    -#>  MASS          7.3-50      2018-04-30 CRAN (R 3.4.4)                  
    -#>  memoise       1.1.0       2017-04-21 CRAN (R 3.4.3)                  
    -#>  munsell       0.5.0       2018-06-12 CRAN (R 3.4.4)                  
    -#>  pillar        1.2.3       2018-05-25 CRAN (R 3.4.4)                  
    -#>  pkgconfig     2.0.1       2017-03-21 cran (@2.0.1)                   
    -#>  pkgdown       1.1.0       2018-06-02 CRAN (R 3.4.4)                  
    -#>  plyr          1.8.4       2016-06-08 CRAN (R 3.4.3)                  
    -#>  purrr         0.2.5       2018-05-29 CRAN (R 3.4.4)                  
    -#>  R6            2.2.2       2017-06-17 CRAN (R 3.4.3)                  
    -#>  Rcpp          0.12.17     2018-05-18 CRAN (R 3.4.4)                  
    -#>  readr         1.2.0       2018-05-26 Github (tidyverse/readr@d6d622b)
    -#>  REDCapR     * 0.9.10.9001 2018-07-11 local                           
    -#>  rlang         0.2.1       2018-05-30 CRAN (R 3.4.4)                  
    -#>  rmarkdown     1.10        2018-06-11 CRAN (R 3.4.4)                  
    -#>  roxygen2      6.0.1       2017-02-06 CRAN (R 3.4.4)                  
    -#>  rprojroot     1.3-2       2018-01-03 CRAN (R 3.4.3)                  
    -#>  rstudioapi    0.7         2017-09-07 CRAN (R 3.4.3)                  
    -#>  rvest         0.3.2       2016-06-17 CRAN (R 3.4.3)                  
    -#>  scales        0.5.0.9000  2018-03-29 Github (hadley/scales@d767915)  
    -#>  sessioninfo   1.0.0       2017-06-21 CRAN (R 3.4.3)                  
    -#>  stringi       1.2.3       2018-06-12 CRAN (R 3.4.4)                  
    -#>  stringr       1.3.1       2018-05-10 CRAN (R 3.4.4)                  
    -#>  tibble        1.4.2       2018-01-22 CRAN (R 3.4.3)                  
    -#>  tidyr         0.8.1       2018-05-18 CRAN (R 3.4.4)                  
    -#>  tidyselect    0.2.4       2018-02-26 CRAN (R 3.4.3)                  
    -#>  viridisLite   0.3.0       2018-02-01 CRAN (R 3.4.3)                  
    -#>  withr         2.1.2       2018-03-29 Github (jimhester/withr@79d7b0d)
    -#>  xml2          1.2.0       2018-01-24 CRAN (R 3.4.3)                  
    -#>  yaml          2.1.19      2018-05-01 CRAN (R 3.4.4)
    -

    -

    Report rendered by wibeasley at 2018-07-12, 00:22 -0500 in 5 seconds.

    +#> assertthat 0.2.0 2017-04-11 CRAN (R 3.5.0) +#> backports 1.1.2 2017-12-13 CRAN (R 3.5.0) +#> bindr 0.1.1 2018-03-13 CRAN (R 3.5.0) +#> bindrcpp * 0.2.2 2018-03-29 CRAN (R 3.5.0) +#> checkmate 1.8.9-9000 2018-08-09 Github (mllg/checkmate@29a1fb9) +#> clisymbols 1.2.0 2017-05-21 CRAN (R 3.5.0) +#> colorspace 1.3-2 2016-12-14 CRAN (R 3.5.0) +#> commonmark 1.5 2018-04-28 CRAN (R 3.5.0) +#> crayon 1.3.4 2017-09-16 CRAN (R 3.5.0) +#> curl 3.2 2018-03-28 CRAN (R 3.5.0) +#> desc 1.2.0 2018-05-01 CRAN (R 3.5.0) +#> devtools 1.13.6 2018-06-27 CRAN (R 3.5.0) +#> digest 0.6.15 2018-01-28 CRAN (R 3.5.0) +#> dplyr 0.7.6 2018-06-29 CRAN (R 3.5.1) +#> evaluate 0.11 2018-07-17 CRAN (R 3.5.1) +#> fs 1.2.5 2018-07-30 CRAN (R 3.5.1) +#> glue 1.3.0 2018-07-17 CRAN (R 3.5.1) +#> hms 0.4.2.9001 2018-08-09 Github (tidyverse/hms@979286f) +#> htmltools 0.3.6 2017-04-28 CRAN (R 3.5.0) +#> httr 1.3.1 2017-08-20 CRAN (R 3.5.0) +#> kableExtra 0.9.0 2018-05-21 CRAN (R 3.5.0) +#> knitr * 1.20 2018-02-20 CRAN (R 3.5.0) +#> magrittr * 1.5 2014-11-22 CRAN (R 3.5.0) +#> MASS 7.3-50 2018-04-30 CRAN (R 3.5.1) +#> memoise 1.1.0 2017-04-21 CRAN (R 3.5.0) +#> munsell 0.5.0 2018-06-12 CRAN (R 3.5.0) +#> pillar 1.3.0 2018-07-14 CRAN (R 3.5.1) +#> pkgconfig 2.0.1 2017-03-21 CRAN (R 3.5.0) +#> pkgdown 1.1.0 2018-06-02 CRAN (R 3.5.1) +#> purrr 0.2.5 2018-05-29 CRAN (R 3.5.0) +#> R6 2.2.2 2017-06-17 CRAN (R 3.5.0) +#> Rcpp 0.12.18 2018-07-23 CRAN (R 3.5.1) +#> readr 1.2.0 2018-08-09 Github (tidyverse/readr@4b2e93a) +#> REDCapR * 0.9.10.9001 2018-08-10 local +#> rlang 0.2.1 2018-05-30 CRAN (R 3.5.0) +#> rmarkdown 1.10 2018-06-11 CRAN (R 3.5.0) +#> roxygen2 6.1.0 2018-07-27 CRAN (R 3.5.1) +#> rprojroot 1.3-2 2018-01-03 CRAN (R 3.5.0) +#> rstudioapi 0.7 2017-09-07 CRAN (R 3.5.0) +#> rvest 0.3.2 2016-06-17 CRAN (R 3.5.0) +#> scales 1.0.0 2018-08-09 CRAN (R 3.5.1) +#> sessioninfo 1.0.0 2017-06-21 CRAN (R 3.5.0) +#> stringi 1.2.4 2018-07-20 CRAN (R 3.5.1) +#> stringr 1.3.1 2018-05-10 CRAN (R 3.5.0) +#> tibble 1.4.2 2018-01-22 CRAN (R 3.5.0) +#> tidyr 0.8.1 2018-05-18 CRAN (R 3.5.0) +#> tidyselect 0.2.4 2018-02-26 CRAN (R 3.5.0) +#> viridisLite 0.3.0 2018-02-01 CRAN (R 3.5.0) +#> withr 2.1.2 2018-03-15 CRAN (R 3.5.0) +#> xml2 1.2.0 2018-01-24 CRAN (R 3.5.0) +#> yaml 2.2.0 2018-07-25 CRAN (R 3.5.1)
    +

    Report rendered by Will at 2018-08-10, 17:44 -0500 in 3 seconds.

    diff --git a/docs/articles/index.html b/docs/articles/index.html index 592f0fec..2e67174b 100644 --- a/docs/articles/index.html +++ b/docs/articles/index.html @@ -89,6 +89,9 @@ diff --git a/docs/authors.html b/docs/authors.html index 91a3b195..3e381db6 100644 --- a/docs/authors.html +++ b/docs/authors.html @@ -89,6 +89,9 @@ diff --git a/docs/favicon.ico b/docs/favicon.ico index 15798f1fd06d35934a5369816884130e624c0b8e..c712399a0f11bc66320564808412eb0b32556b54 100644 GIT binary patch delta 1691 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 diff --git a/docs/index.html b/docs/index.html index 522cd8cd..5603ee95 100644 --- a/docs/index.html +++ b/docs/index.html @@ -60,6 +60,9 @@ @@ -103,33 +103,33 @@ REDCapR

    We’ve been using R with REDCap’s API since 2012 and have developed REDCapR. Before encapsulating these functions in a package, we were replicating 50+ lines of code to contact REDCap and robustly transform the returned csv into an R data.frame; it took twice that much to implement batching. All this can be done in one call to redcap_read():

    -
    ds <- redcap_read(redcap_uri=uri, token=token)$data
    +
    ds <- redcap_read(redcap_uri=uri, token=token)$data

    The redcap_read() function also accepts values for subsetting/filtering the records and fields. Here are two examples; the first selects only a portion of the rows, while the second selects only a portion of the columns. Documentation for the additional 20+ functions are found at ouhscbbmc.github.io/REDCapR/reference.

    -
    # Return only records with IDs of 1 and 4
    -desired_records <- c(1, 4)
    -ds_some_rows <- redcap_read(
    -  redcap_uri   = uri,
    -  token        = token,
    -  records      = desired_records
    -)$data
    -
    -# Return only the fields record_id, name_first, and age
    -desired_fields <- c("record_id", "name_first", "age")
    -ds_some_fields <- redcap_read(
    -  redcap_uri  = uri,
    -  token       = token,
    -  fields      = desired_fields
    -)$data
    +

    The REDCapR package includes the SSL certificate retrieved by httr::find_cert_bundle(). Your REDCap server’s identity is always verified, unless the setting is overridden (alternative certificates can also be provided).

    To keep our maintenance efforts manageable, the package implements only the REDCap API functions that have been requested. If there’s a feature that would help your projects, please tell us in a new issue in REDCapR’s GitHub repository. A troubleshooting document helps diagnose issues with the API.

    Installation and Documentation

    The release version can be installed from CRAN.

    -
    install.packages("REDCapR")
    +

    The development version can be installed from GitHub after installing the devtools package.

    -
    install.packages("devtools") # Run this line if the 'devtools' package isn't installed already.
    -devtools::install_github(repo="OuhscBbmc/REDCapR")
    +

    The ouhscbbmc.github.io/REDCapR site describes the package functions, and includes documents involving basic operations, advanced operations, token security, and troubleshooting.

    Also checkout the other packages that exist for communicating with REDCap, which are listed in the REDCap Tools directory.

    diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index 22697aa6..a574ae7c 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -1,11 +1,11 @@ -pandoc: 1.19.2.1 +pandoc: 2.2.1 pkgdown: 1.1.0 pkgdown_sha: ~ articles: + advanced-redcapr-operations: advanced-redcapr-operations.html BasicREDCapROperations: BasicREDCapROperations.html SecurityDatabase: SecurityDatabase.html TroubleshootingApiCalls: TroubleshootingApiCalls.html - advanced-redcapr-operations: advanced-redcapr-operations.html urls: reference: https://ouhscbbmc.github.io/REDCapR/reference article: https://ouhscbbmc.github.io/REDCapR/articles diff --git a/docs/reference/REDCapR-package.html b/docs/reference/REDCapR-package.html index 511b6c03..44c2ea5e 100644 --- a/docs/reference/REDCapR-package.html +++ b/docs/reference/REDCapR-package.html @@ -100,6 +100,9 @@ diff --git a/docs/reference/collapse_vector.html b/docs/reference/collapse_vector.html index e86a29df..c5814781 100644 --- a/docs/reference/collapse_vector.html +++ b/docs/reference/collapse_vector.html @@ -93,6 +93,9 @@ diff --git a/docs/reference/constant.html b/docs/reference/constant.html index 0d9562a9..13e32a3d 100644 --- a/docs/reference/constant.html +++ b/docs/reference/constant.html @@ -91,6 +91,9 @@ diff --git a/docs/reference/create_batch_glossary.html b/docs/reference/create_batch_glossary.html index 31954963..9daef37b 100644 --- a/docs/reference/create_batch_glossary.html +++ b/docs/reference/create_batch_glossary.html @@ -93,6 +93,9 @@ diff --git a/docs/reference/index.html b/docs/reference/index.html index b824cab1..f8429810 100644 --- a/docs/reference/index.html +++ b/docs/reference/index.html @@ -89,6 +89,9 @@ diff --git a/docs/reference/kernel_api.html b/docs/reference/kernel_api.html index c8d52d98..70b2e1ff 100644 --- a/docs/reference/kernel_api.html +++ b/docs/reference/kernel_api.html @@ -91,6 +91,9 @@ @@ -186,7 +186,7 @@

    Examp # Consume the results in a few different ways. kernel$result
    #> Response [https://bbmc.ouhsc.edu/redcap/api/] -#> Date: 2018-07-12 05:22 +#> Date: 2018-08-10 22:43 #> Status: 200 #> Content-Type: text/csv; charset=utf-8 #> Size: 557 B diff --git a/docs/reference/metadata_utilities.html b/docs/reference/metadata_utilities.html index 01121038..a9e9a75e 100644 --- a/docs/reference/metadata_utilities.html +++ b/docs/reference/metadata_utilities.html @@ -91,6 +91,9 @@ @@ -209,7 +209,7 @@

    Examp #> 5 5 White #> 6 6 Unknown / Not Reported

    #This function is designed specifically for the checkbox values. -REDCapR::checkbox_choices(select_choices=choices_1)
    #> id label +REDCapR::checkbox_choices(select_choices=choices_1)
    #> id label #> 1 1 American Indian/Alaska Native #> 2 2 Asian #> 3 3 Native Hawaiian or Other Pacific Islander diff --git a/docs/reference/redcap_column_sanitize.html b/docs/reference/redcap_column_sanitize.html index 7c98e3ce..1034ff5e 100644 --- a/docs/reference/redcap_column_sanitize.html +++ b/docs/reference/redcap_column_sanitize.html @@ -91,6 +91,9 @@ @@ -181,10 +181,10 @@

    Details

    Examples

    dirty <- data.frame(id=1:3, names=c("Ekstr\xf8m", "J\xf6reskog", "bi\xdfchen Z\xfcrcher")) -REDCapR::redcap_column_sanitize(dirty)
    #> id names -#> 1 1 Ekstrom -#> 2 2 Joreskog -#> 3 3 bisschen Zurcher
    # Produces the dataset: +REDCapR::redcap_column_sanitize(dirty)
    #> id names +#> 1 1 Ekstrom +#> 2 2 Joreskog +#> 3 3 bi?chen Zurcher
    # Produces the dataset: # id names #1 1 Ekstr?m #2 2 Joreskog diff --git a/docs/reference/redcap_download_file_oneshot.html b/docs/reference/redcap_download_file_oneshot.html index 37c0672b..69115f65 100644 --- a/docs/reference/redcap_download_file_oneshot.html +++ b/docs/reference/redcap_download_file_oneshot.html @@ -91,6 +91,9 @@ diff --git a/docs/reference/redcap_metadata_read.html b/docs/reference/redcap_metadata_read.html index d1c132c4..5985c471 100644 --- a/docs/reference/redcap_metadata_read.html +++ b/docs/reference/redcap_metadata_read.html @@ -92,6 +92,9 @@ @@ -145,9 +145,9 @@

    Export the metadata of a REDCap project.

    -
    redcap_metadata_read(redcap_uri, token, forms = NULL, forms_collapsed = "",
    -  fields = NULL, fields_collapsed = "", verbose = TRUE,
    -  config_options = NULL)
    +
    redcap_metadata_read(redcap_uri, token, forms = NULL,
    +  forms_collapsed = "", fields = NULL, fields_collapsed = "",
    +  verbose = TRUE, config_options = NULL)

    Arguments

    diff --git a/docs/reference/redcap_project.html b/docs/reference/redcap_project.html index 429d6a56..55ea15e8 100644 --- a/docs/reference/redcap_project.html +++ b/docs/reference/redcap_project.html @@ -93,6 +93,9 @@ diff --git a/docs/reference/redcap_read.html b/docs/reference/redcap_read.html index 471648cf..3dab7e3d 100644 --- a/docs/reference/redcap_read.html +++ b/docs/reference/redcap_read.html @@ -94,6 +94,9 @@ diff --git a/docs/reference/redcap_read_oneshot.html b/docs/reference/redcap_read_oneshot.html index 1e1e5834..7fd7bffb 100644 --- a/docs/reference/redcap_read_oneshot.html +++ b/docs/reference/redcap_read_oneshot.html @@ -91,6 +91,9 @@ diff --git a/docs/reference/redcap_read_oneshot_eav.html b/docs/reference/redcap_read_oneshot_eav.html index 026317fe..46617cb4 100644 --- a/docs/reference/redcap_read_oneshot_eav.html +++ b/docs/reference/redcap_read_oneshot_eav.html @@ -91,6 +91,9 @@ diff --git a/docs/reference/redcap_upload_file_oneshot.html b/docs/reference/redcap_upload_file_oneshot.html index 2a2a5d9e..5c45bccf 100644 --- a/docs/reference/redcap_upload_file_oneshot.html +++ b/docs/reference/redcap_upload_file_oneshot.html @@ -91,6 +91,9 @@ diff --git a/docs/reference/redcap_variables.html b/docs/reference/redcap_variables.html index 450a07ea..e643da5a 100644 --- a/docs/reference/redcap_variables.html +++ b/docs/reference/redcap_variables.html @@ -91,6 +91,9 @@ @@ -143,7 +143,8 @@

    Enumerate the exported variables.

    -
    redcap_variables(redcap_uri, token, verbose = TRUE, config_options = NULL)
    +
    redcap_variables(redcap_uri, token, verbose = TRUE,
    +  config_options = NULL)

    Arguments

    diff --git a/docs/reference/redcap_version.html b/docs/reference/redcap_version.html index 4d5679c4..f3c6cad9 100644 --- a/docs/reference/redcap_version.html +++ b/docs/reference/redcap_version.html @@ -91,6 +91,9 @@ @@ -143,7 +143,8 @@

    Determine version of REDCap instance

    -
    redcap_version(redcap_uri, token, verbose = TRUE, config_options = NULL)
    +
    redcap_version(redcap_uri, token, verbose = TRUE,
    +  config_options = NULL)

    Arguments

    @@ -180,7 +181,7 @@

    Details

    Examples

    uri <- "https://bbmc.ouhsc.edu/redcap/api/" token <- "9A81268476645C4E5F03428B8AC3AA7B" -REDCapR::redcap_version(redcap_uri=uri, token=token)
    #> The REDCap version was successfully determined in 0.4 seconds. The http status code was 200. It is 8.4.0.
    #> [1] ‘8.4.0’
    +REDCapR::redcap_version(redcap_uri=uri, token=token)
    #> The REDCap version was successfully determined in 0.2 seconds. The http status code was 200. It is 8.4.0.
    #> [1] '8.4.0'
    #> # A tibble: 2 x 4 -#> field_name field_index concern suggestion -#> <chr> <int> <chr> <chr> -#> 1 flag_logical 2 The REDCap API does no… Convert the variable with… -#> 2 flag_Uppercase 3 A REDCap project does … Change the uppercase lett…
    +#> field_name field_index concern suggestion +#> <chr> <int> <chr> <chr> +#> 1 flag_logical 2 The REDCap API does not~ Convert the variable with ~ +#> 2 flag_Upperc~ 3 A REDCap project does n~ Change the uppercase lette~
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -record id - -name first - -name last - -address - -telephone - -email - -dob - -age - -sex - -demographics complete - -height - -weight - -bmi - -comments - -mugshot - -health complete - -race 1 - -race 2 - -race 3 - -race 4 - -race 5 - -race 6 - -ethnicity - -race and ethnicity complete -
    -1 - -Nutmeg - -Nutmouse - -14 Rose Cottage St. Kenning UK, 323232 - -
      -
    1. 321-1111 -
    -nutty@mouse.com - -2003-08-30 - -11 - -0 - -2 - -7.00 - -1 - -204.1 - -Character in a book, with some guessing - -[document] - -1 - -0 - -0 - -0 - -0 - -1 - -0 - -1 - -2 -
    -2 - -Tumtum - -Nutmouse - -14 Rose Cottage Blvd. Kenning UK 34243 - -
      -
    1. 321-2222 -
    -tummy@mouse.comm - -2003-03-10 - -11 - -1 - -2 - -6.00 - -1 - -277.8 - -A mouse character from a good book - -[document] - -0 - -0 - -0 - -1 - -0 - -1 - -0 - -1 - -0 -
    -3 - -Marcus - -Wood - -243 Hill St. Guthrie OK 73402 - -
      -
    1. 321-3333 -
    -mw@mwood.net - -1934-04-09 - -80 - -1 - -2 - -180.00 - -80 - -24.7 - -completely made up - -[document] - -2 - -0 - -0 - -0 - -1 - -1 - -0 - -0 - -2 -
    -4 - -Trudy - -DAG - -342 Elm Duncanville TX, 75116 - -
      -
    1. 321-4444 -
    -peroxide@blonde.com - -1952-11-02 - -61 - -0 - -2 - -165.00 - -54 - -19.8 - -This record doesn’t have a DAG assigned - - - - -So call up Trudy on the telephone Send her a letter in the mail - -[document] - -2 - -0 - -1 - -0 - -0 - -1 - -0 - -1 - -2 -
    -5 - -John Lee - -Walker - -Hotel Suite New Orleans LA, 70115 - -
      -
    1. 321-5555 -
    -left@hippocket.com - -1955-04-15 - -59 - -1 - -2 - -193.04 - -104 - -27.9 - -Had a hand for trouble and a eye for cash - -He had a gold watch chain and a black mustache - -[document] - -0 - -1 - -0 - -0 - -0 - -0 - -1 - -2 - -2 -
    +
    5 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    + +
      record_id name_first name_last                                 address
    +1         1     Nutmeg  Nutmouse 14 Rose Cottage St.\nKenning UK, 323232
    +2         2     Tumtum  Nutmouse 14 Rose Cottage Blvd.\nKenning UK 34243
    +3         3     Marcus      Wood          243 Hill St.\nGuthrie OK 73402
    +4         4      Trudy       DAG          342 Elm\nDuncanville TX, 75116
    +5         5   John Lee    Walker      Hotel Suite\nNew Orleans LA, 70115
    +       telephone               email        dob age sex
    +1 (405) 321-1111     nutty@mouse.com 2003-08-30  11   0
    +2 (405) 321-2222    tummy@mouse.comm 2003-03-10  11   1
    +3 (405) 321-3333        mw@mwood.net 1934-04-09  80   1
    +4 (405) 321-4444 peroxide@blonde.com 1952-11-02  61   0
    +5 (405) 321-5555  left@hippocket.com 1955-04-15  59   1
    +  demographics_complete height weight   bmi
    +1                     2   7.00      1 204.1
    +2                     2   6.00      1 277.8
    +3                     2 180.00     80  24.7
    +4                     2 165.00     54  19.8
    +5                     2 193.04    104  27.9
    +                                                                                                     comments
    +1                                                                     Character in a book, with some guessing
    +2                                                                          A mouse character from a good book
    +3                                                                                          completely made up
    +4 This record doesn't have a DAG assigned\n\nSo call up Trudy on the telephone\nSend her a letter in the mail
    +5                 Had a hand for trouble and a eye for cash\n\nHe had a gold watch chain and a black mustache
    +     mugshot health_complete race___1 race___2 race___3 race___4 race___5
    +1 [document]               1        0        0        0        0        1
    +2 [document]               0        0        0        1        0        1
    +3 [document]               2        0        0        0        1        1
    +4 [document]               2        0        1        0        0        1
    +5 [document]               0        1        0        0        0        0
    +  race___6 ethnicity race_and_ethnicity_complete
    +1        0         1                           2
    +2        0         1                           0
    +3        0         0                           2
    +4        0         1                           2
    +5        1         2                           2

    Read a subset of the records.

    If only a subset of the records is desired, the two approaches are shown below. The first is to pass an array (where each element is an ID) to the records parameter. The second is to pass a single string (where the elements are separated by commas) to the records_collapsed parameter.

    The first format is more natural for more R users. The second format is what is expected by the REDCap API. If a value for records is specified, but records_collapsed is not specified, then redcap_read_oneshot automatically converts the array into the format needed by the API.

    -
    #Return only records with IDs of 1 and 3
    -desired_records_v1 <- c(1, 3)
    -ds_some_rows_v1 <- redcap_read(
    -   redcap_uri = uri, 
    -   token      = token, 
    -   records    = desired_records_v1
    -)$data
    -
    The data dictionary describing 16 fields was read from REDCap in 0.4 seconds.  The http status code was 200.
    -
    2 records and 1 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    -
    Starting to read 2 records  at 2018-07-11 11:04:15.
    -
    Reading batch 1 of 1, with subjects 1 through 3 (ie, 2 unique subject records).
    -
    2 records and 24 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    -
    #Return only records with IDs of 1 and 3 (alternate way)
    -desired_records_v2 <- "1, 3"
    -ds_some_rows_v2 <- redcap_read(
    -   redcap_uri        = uri, 
    -   token             = token, 
    -   records_collapsed = desired_records_v2
    -)$data
    +
    The data dictionary describing 16 fields was read from REDCap in 0.3 seconds.  The http status code was 200.
    -
    2 records and 1 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    -
    Starting to read 2 records  at 2018-07-11 11:04:16.
    +
    2 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    +
    Starting to read 2 records  at 2018-08-10 17:43:31.
    Reading batch 1 of 1, with subjects 1 through 3 (ie, 2 unique subject records).
    2 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    ds_some_rows_v2 #Inspect the returned dataset
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -record id - -name first - -name last - -address - -telephone - -email - -dob - -age - -sex - -demographics complete - -height - -weight - -bmi - -comments - -mugshot - -health complete - -race 1 - -race 2 - -race 3 - -race 4 - -race 5 - -race 6 - -ethnicity - -race and ethnicity complete -
    -1 - -Nutmeg - -Nutmouse - -14 Rose Cottage St. Kenning UK, 323232 - -
      -
    1. 321-1111 -
    -nutty@mouse.com - -2003-08-30 - -11 - -0 - -2 - -7 - -1 - -204.1 - -Character in a book, with some guessing - -[document] - -1 - -0 - -0 - -0 - -0 - -1 - -0 - -1 - -2 -
    -3 - -Marcus - -Wood - -243 Hill St. Guthrie OK 73402 - -
      -
    1. 321-3333 -
    -mw@mwood.net - -1934-04-09 - -80 - -1 - -2 - -180 - -80 - -24.7 - -completely made up - -[document] - -2 - -0 - -0 - -0 - -1 - -1 - -0 - -0 - -2 -
    - - + +
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    +
    2 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    +
    Starting to read 2 records  at 2018-08-10 17:43:32.
    +
    Reading batch 1 of 1, with subjects 1 through 3 (ie, 2 unique subject records).
    +
    2 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    + +
      record_id name_first name_last                                 address
    +1         1     Nutmeg  Nutmouse 14 Rose Cottage St.\nKenning UK, 323232
    +2         3     Marcus      Wood          243 Hill St.\nGuthrie OK 73402
    +       telephone           email        dob age sex demographics_complete
    +1 (405) 321-1111 nutty@mouse.com 2003-08-30  11   0                     2
    +2 (405) 321-3333    mw@mwood.net 1934-04-09  80   1                     2
    +  height weight   bmi                                comments    mugshot
    +1      7      1 204.1 Character in a book, with some guessing [document]
    +2    180     80  24.7                      completely made up [document]
    +  health_complete race___1 race___2 race___3 race___4 race___5 race___6
    +1               1        0        0        0        0        1        0
    +2               2        0        0        0        1        1        0
    +  ethnicity race_and_ethnicity_complete
    +1         1                           2
    +2         0                           2

    Read a subset of the fields.

    If only a subset of the fields is desired, then two approaches exist. The first is to pass an array (where each element is an field) to the fields parameter. The second is to pass a single string (where the elements are separated by commas) to the fields_collapsed parameter. Like with records and records_collapsed described above, this function converts the more natural format (ie, fields) to the format required by the API (ie, fields_collapsed) if fields is specified and fields_collapsed is not.

    -
    #Return only the fields record_id, name_first, and age
    -desired_fields_v1 <- c("record_id", "name_first", "age")
    -ds_some_fields_v1 <- redcap_read(
    -   redcap_uri = uri, 
    -   token      = token, 
    -   fields     = desired_fields_v1
    -)$data
    -
    The data dictionary describing 16 fields was read from REDCap in 0.4 seconds.  The http status code was 200.
    -
    5 records and 1 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-07-11 11:04:18.
    + +
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    +
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    +
    Starting to read 5 records  at 2018-08-10 17:43:33.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    #Return only the fields record_id, name_first, and age (alternate way)
    -desired_fields_v2 <- "record_id, name_first, age"
    -ds_some_fields_v2 <- redcap_read(
    -   redcap_uri       = uri, 
    -   token            = token, 
    -   fields_collapsed = desired_fields_v2
    -)$data
    -
    The data dictionary describing 16 fields was read from REDCap in 0.4 seconds.  The http status code was 200.
    -
    5 records and 1 columns were read from REDCap in 0.4 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-07-11 11:04:19.
    + +
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    +
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    +
    Starting to read 5 records  at 2018-08-10 17:43:34.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    -
    5 records and 3 columns were read from REDCap in 0.4 seconds.  The http status code was 200.
    -
    ds_some_fields_v2 #Inspect the returned dataset
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -record id - -name first - -age -
    -1 - -Nutmeg - -11 -
    -2 - -Tumtum - -11 -
    -3 - -Marcus - -80 -
    -4 - -Trudy - -61 -
    -5 - -John Lee - -59 -
    +
    5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    + +
      record_id name_first age
    +1         1     Nutmeg  11
    +2         2     Tumtum  11
    +3         3     Marcus  80
    +4         4      Trudy  61
    +5         5   John Lee  59

    Read a subset of records, conditioned on the values in some variables.

    The two techniques above can be combined when your datasets are large and you don’t want to pull records with certain values. Suppose you want to select subjects from the previous dataset if the were born before 1960 and their weight was over 70kg. Two calls to the server are required. The first call to REDCap pulls all the records, but for only three columns: record_id, dob, and weight. From this subset, identify the records that you want to pull all the data for; in this case, the desired record_id values are 3 & 5. The second call to REDCap pulls all the columns, but only for the identified records.

    -
    ######
    -## Step 1: First call to REDCap
    -desired_fields_v3 <- c("record_id", "dob", "weight")
    -ds_some_fields_v3 <- redcap_read(
    -   redcap_uri = uri, 
    -   token      = token, 
    -   fields     = desired_fields_v3
    -)$data
    -
    The data dictionary describing 16 fields was read from REDCap in 0.3 seconds.  The http status code was 200.
    -
    5 records and 1 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-07-11 11:04:21.
    + +
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    +
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    +
    Starting to read 5 records  at 2018-08-10 17:43:35.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    -
    5 records and 3 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    -
    ds_some_fields_v3 #Examine the these three variables.
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -record id - -dob - -weight -
    -1 - -2003-08-30 - -1 -
    -2 - -2003-03-10 - -1 -
    -3 - -1934-04-09 - -80 -
    -4 - -1952-11-02 - -54 -
    -5 - -1955-04-15 - -104 -
    -
    ######
    -## Step 2: identify desired records, based on age & weight
    -before_1960 <- (ds_some_fields_v3$dob <= as.Date("1960-01-01"))
    -heavier_than_70_kg <- (ds_some_fields_v3$weight > 70)
    -desired_records_v3 <- ds_some_fields_v3[before_1960 & heavier_than_70_kg, ]$record_id
    -
    -desired_records_v3 #Peek at IDs of the identified records
    +
    5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    + +
      record_id        dob weight
    +1         1 2003-08-30      1
    +2         2 2003-03-10      1
    +3         3 1934-04-09     80
    +4         4 1952-11-02     54
    +5         5 1955-04-15    104
    +
    [1] 3 5
    -
    ######
    -## Step 3: second call to REDCap
    -#Return only records that met the age & weight criteria.
    -ds_some_rows_v3 <- redcap_read(
    -   redcap_uri = uri, 
    -   token      = token, 
    -   records    = desired_records_v3
    -)$data
    +
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    2 records and 1 columns were read from REDCap in 0.4 seconds.  The http status code was 200.
    -
    Starting to read 2 records  at 2018-07-11 11:04:22.
    +
    2 records and 1 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    +
    Starting to read 2 records  at 2018-08-10 17:43:37.
    Reading batch 1 of 1, with subjects 3 through 5 (ie, 2 unique subject records).
    -
    2 records and 24 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    -
    ds_some_rows_v3 #Examine the results.
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -record id - -name first - -name last - -address - -telephone - -email - -dob - -age - -sex - -demographics complete - -height - -weight - -bmi - -comments - -mugshot - -health complete - -race 1 - -race 2 - -race 3 - -race 4 - -race 5 - -race 6 - -ethnicity - -race and ethnicity complete -
    -3 - -Marcus - -Wood - -243 Hill St. Guthrie OK 73402 - -
      -
    1. 321-3333 -
    -mw@mwood.net - -1934-04-09 - -80 - -1 - -2 - -180.00 - -80 - -24.7 - -completely made up - -[document] - -2 - -0 - -0 - -0 - -1 - -1 - -0 - -0 - -2 -
    -5 - -John Lee - -Walker - -Hotel Suite New Orleans LA, 70115 - -
      -
    1. 321-5555 -
    -left@hippocket.com - -1955-04-15 - -59 - -1 - -2 - -193.04 - -104 - -27.9 - -Had a hand for trouble and a eye for cash - - -He had a gold watch chain and a black mustache - -[document] - -0 - -1 - -0 - -0 - -0 - -0 - -1 - -2 - -2 -
    +
    2 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    + +
      record_id name_first name_last                            address
    +1         3     Marcus      Wood     243 Hill St.\nGuthrie OK 73402
    +2         5   John Lee    Walker Hotel Suite\nNew Orleans LA, 70115
    +       telephone              email        dob age sex
    +1 (405) 321-3333       mw@mwood.net 1934-04-09  80   1
    +2 (405) 321-5555 left@hippocket.com 1955-04-15  59   1
    +  demographics_complete height weight  bmi
    +1                     2 180.00     80 24.7
    +2                     2 193.04    104 27.9
    +                                                                                     comments
    +1                                                                          completely made up
    +2 Had a hand for trouble and a eye for cash\n\nHe had a gold watch chain and a black mustache
    +     mugshot health_complete race___1 race___2 race___3 race___4 race___5
    +1 [document]               2        0        0        0        1        1
    +2 [document]               0        1        0        0        0        0
    +  race___6 ethnicity race_and_ethnicity_complete
    +1        0         0                           2
    +2        1         2                           2

    Additional Returned Information

    @@ -1277,18 +497,18 @@

    Additional Returned Information

  • The fields_collapsed fields passed to the API. This shows which field subsets, if any, were requested.
  • The elapsed_seconds measures the duration of the call.
  • -
    #Return only the fields record_id, name_first, and age
    -all_information <- redcap_read(
    -   redcap_uri = uri, 
    -   token      = token, 
    -   fields     = desired_fields_v1
    -)
    -
    The data dictionary describing 16 fields was read from REDCap in 0.3 seconds.  The http status code was 200.
    + +
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-07-11 11:04:24.
    +
    Starting to read 5 records  at 2018-08-10 17:43:38.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    -
    5 records and 3 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    -
    all_information #Inspect the additional information
    +
    5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    +
    $data
       record_id name_first age
     1         1     Nutmeg  11
    @@ -1304,7 +524,7 @@ 

    Additional Returned Information

    [1] "200" $outcome_messages -[1] "5 records and 3 columns were read from REDCap in 0.3 seconds. The http status code was 200." +[1] "5 records and 3 columns were read from REDCap in 0.2 seconds. The http status code was 200." $records_collapsed [1] "" @@ -1322,80 +542,81 @@

    Additional Returned Information

    [1] "" $elapsed_seconds -[1] 1.348229
    +[1] 1.169355

    Session Information

    For the sake of documentation and reproducibility, the current report was rendered in the following environment. Click the line below to expand.

    -

    Environment

    -
    ─ Session info ──────────────────────────────────────────────────────────
    - setting  value                       
    - version  R version 3.4.4 (2018-03-15)
    - os       Ubuntu 18.04 LTS            
    - system   x86_64, linux-gnu           
    - ui       RStudio                     
    - language (EN)                        
    - collate  en_US.UTF-8                 
    - tz       America/Chicago             
    - date     2018-07-11                  
    -
    -─ Packages ──────────────────────────────────────────────────────────────
    +
    +

    Environment

    +
    - Session info ----------------------------------------------------------
    + setting  value                                      
    + version  R version 3.5.1 Patched (2018-08-06 r75070)
    + os       Windows >= 8 x64                           
    + system   x86_64, mingw32                            
    + ui       RStudio                                    
    + language (EN)                                       
    + collate  English_United States.1252                 
    + tz       America/Chicago                            
    + date     2018-08-10                                 
    +
    +- Packages --------------------------------------------------------------
      package     * version     date       source                          
    - assertthat    0.2.0       2017-04-11 cran (@0.2.0)                   
    - backports     1.1.2       2017-12-13 cran (@1.1.2)                   
    - bindr         0.1.1       2018-03-13 CRAN (R 3.4.3)                  
    - bindrcpp    * 0.2.2       2018-03-29 CRAN (R 3.4.3)                  
    - checkmate     1.8.6       2018-04-10 Github (mllg/checkmate@489319a) 
    - clisymbols    1.2.0       2017-05-21 CRAN (R 3.4.3)                  
    - codetools     0.2-15      2016-10-05 CRAN (R 3.4.3)                  
    - colorspace    1.3-2       2016-12-14 CRAN (R 3.4.3)                  
    - commonmark    1.5         2018-04-28 CRAN (R 3.4.4)                  
    - crayon        1.3.4       2017-09-16 CRAN (R 3.4.3)                  
    - curl          3.2         2018-03-28 CRAN (R 3.4.4)                  
    - desc          1.2.0       2018-05-01 CRAN (R 3.4.4)                  
    - devtools      1.13.6      2018-06-27 CRAN (R 3.4.4)                  
    - digest        0.6.15      2018-01-28 CRAN (R 3.4.3)                  
    - dplyr         0.7.6       2018-06-29 CRAN (R 3.4.4)                  
    - evaluate      0.10.1      2017-06-24 CRAN (R 3.4.3)                  
    - glue          1.2.0       2017-10-29 cran (@1.2.0)                   
    - highr         0.7         2018-06-09 CRAN (R 3.4.4)                  
    - hms           0.4.2.9000  2018-05-26 Github (tidyverse/hms@14e74ab)  
    - htmltools     0.3.6       2017-04-28 CRAN (R 3.4.3)                  
    - httr          1.3.1       2017-08-20 CRAN (R 3.4.3)                  
    - kableExtra    0.9.0       2018-05-21 CRAN (R 3.4.4)                  
    - knitr       * 1.20        2018-02-20 CRAN (R 3.4.3)                  
    - magrittr    * 1.5         2014-11-22 cran (@1.5)                     
    - memoise       1.1.0       2017-04-21 CRAN (R 3.4.3)                  
    - munsell       0.5.0       2018-06-12 CRAN (R 3.4.4)                  
    - pillar        1.2.3       2018-05-25 CRAN (R 3.4.4)                  
    - pkgconfig     2.0.1       2017-03-21 cran (@2.0.1)                   
    - plyr          1.8.4       2016-06-08 CRAN (R 3.4.3)                  
    - purrr         0.2.5       2018-05-29 CRAN (R 3.4.4)                  
    - R6            2.2.2       2017-06-17 CRAN (R 3.4.3)                  
    - Rcpp          0.12.17     2018-05-18 CRAN (R 3.4.4)                  
    - readr         1.2.0       2018-05-26 Github (tidyverse/readr@d6d622b)
    + assertthat    0.2.0       2017-04-11 CRAN (R 3.5.0)                  
    + backports     1.1.2       2017-12-13 CRAN (R 3.5.0)                  
    + bindr         0.1.1       2018-03-13 CRAN (R 3.5.0)                  
    + bindrcpp    * 0.2.2       2018-03-29 CRAN (R 3.5.0)                  
    + checkmate     1.8.9-9000  2018-08-09 Github (mllg/checkmate@29a1fb9) 
    + clisymbols    1.2.0       2017-05-21 CRAN (R 3.5.0)                  
    + codetools     0.2-15      2016-10-05 CRAN (R 3.5.1)                  
    + colorspace    1.3-2       2016-12-14 CRAN (R 3.5.0)                  
    + commonmark    1.5         2018-04-28 CRAN (R 3.5.0)                  
    + crayon        1.3.4       2017-09-16 CRAN (R 3.5.0)                  
    + curl          3.2         2018-03-28 CRAN (R 3.5.0)                  
    + desc          1.2.0       2018-05-01 CRAN (R 3.5.0)                  
    + devtools      1.13.6      2018-06-27 CRAN (R 3.5.0)                  
    + digest        0.6.15      2018-01-28 CRAN (R 3.5.0)                  
    + dplyr         0.7.6       2018-06-29 CRAN (R 3.5.1)                  
    + evaluate      0.11        2018-07-17 CRAN (R 3.5.1)                  
    + git2r         0.23.0      2018-07-17 CRAN (R 3.5.1)                  
    + glue          1.3.0       2018-07-17 CRAN (R 3.5.1)                  
    + hms           0.4.2.9001  2018-08-09 Github (tidyverse/hms@979286f)  
    + htmltools     0.3.6       2017-04-28 CRAN (R 3.5.0)                  
    + httr          1.3.1       2017-08-20 CRAN (R 3.5.0)                  
    + kableExtra    0.9.0       2018-05-21 CRAN (R 3.5.0)                  
    + knitr       * 1.20        2018-02-20 CRAN (R 3.5.0)                  
    + magrittr    * 1.5         2014-11-22 CRAN (R 3.5.0)                  
    + memoise       1.1.0       2017-04-21 CRAN (R 3.5.0)                  
    + munsell       0.5.0       2018-06-12 CRAN (R 3.5.0)                  
    + packrat       0.4.9-3     2018-06-01 CRAN (R 3.5.0)                  
    + pillar        1.3.0       2018-07-14 CRAN (R 3.5.1)                  
    + pkgconfig     2.0.1       2017-03-21 CRAN (R 3.5.0)                  
    + purrr         0.2.5       2018-05-29 CRAN (R 3.5.0)                  
    + R6            2.2.2       2017-06-17 CRAN (R 3.5.0)                  
    + Rcpp          0.12.18     2018-07-23 CRAN (R 3.5.1)                  
    + readr         1.2.0       2018-08-09 Github (tidyverse/readr@4b2e93a)
      REDCapR     * 0.9.10.9001 <NA>       local                           
    - rlang         0.2.1       2018-05-30 CRAN (R 3.4.4)                  
    - rmarkdown     1.10        2018-06-11 CRAN (R 3.4.4)                  
    - roxygen2      6.0.1       2017-02-06 CRAN (R 3.4.4)                  
    - rprojroot     1.3-2       2018-01-03 CRAN (R 3.4.3)                  
    - rstudioapi    0.7         2017-09-07 CRAN (R 3.4.3)                  
    - rvest         0.3.2       2016-06-17 CRAN (R 3.4.3)                  
    - scales        0.5.0.9000  2018-03-29 Github (hadley/scales@d767915)  
    - sessioninfo   1.0.0       2017-06-21 CRAN (R 3.4.3)                  
    - stringi       1.2.3       2018-06-12 CRAN (R 3.4.4)                  
    - stringr       1.3.1       2018-05-10 CRAN (R 3.4.4)                  
    - testthat      2.0.0       2017-12-13 CRAN (R 3.4.3)                  
    - tibble        1.4.2       2018-01-22 CRAN (R 3.4.3)                  
    - tidyr         0.8.1       2018-05-18 CRAN (R 3.4.4)                  
    - tidyselect    0.2.4       2018-02-26 CRAN (R 3.4.3)                  
    - viridisLite   0.3.0       2018-02-01 CRAN (R 3.4.3)                  
    - withr         2.1.2       2018-03-29 Github (jimhester/withr@79d7b0d)
    - xml2          1.2.0       2018-01-24 CRAN (R 3.4.3)                  
    - yaml          2.1.19      2018-05-01 CRAN (R 3.4.4)                  
    -

    -

    Report rendered by wibeasley at 2018-07-11, 11:04 -0500 in 12 seconds.

    + rlang 0.2.1 2018-05-30 CRAN (R 3.5.0) + rmarkdown 1.10 2018-06-11 CRAN (R 3.5.0) + roxygen2 6.1.0 2018-07-27 CRAN (R 3.5.1) + rprojroot 1.3-2 2018-01-03 CRAN (R 3.5.0) + rstudioapi 0.7 2017-09-07 CRAN (R 3.5.0) + rvest 0.3.2 2016-06-17 CRAN (R 3.5.0) + scales 1.0.0 2018-08-09 CRAN (R 3.5.1) + sessioninfo 1.0.0 2017-06-21 CRAN (R 3.5.0) + stringi 1.2.4 2018-07-20 CRAN (R 3.5.1) + stringr 1.3.1 2018-05-10 CRAN (R 3.5.0) + testthat 2.0.0 2017-12-13 CRAN (R 3.5.0) + tibble 1.4.2 2018-01-22 CRAN (R 3.5.0) + tidyr 0.8.1 2018-05-18 CRAN (R 3.5.0) + tidyselect 0.2.4 2018-02-26 CRAN (R 3.5.0) + viridisLite 0.3.0 2018-02-01 CRAN (R 3.5.0) + withr 2.1.2 2018-03-15 CRAN (R 3.5.0) + xml2 1.2.0 2018-01-24 CRAN (R 3.5.0) + yaml 2.2.0 2018-07-25 CRAN (R 3.5.1)
    +
    +

    Report rendered by Will at 2018-08-10, 17:43 -0500 in 10 seconds.

    diff --git a/inst/doc/SecurityDatabase.html b/inst/doc/SecurityDatabase.html index a24dd751..3cbdada0 100644 --- a/inst/doc/SecurityDatabase.html +++ b/inst/doc/SecurityDatabase.html @@ -11,7 +11,7 @@ - + Security Database @@ -19,46 +19,256 @@ - + @@ -68,7 +278,7 @@

    Security Database

    -

    2018-07-11

    +

    2018-08-10

    @@ -93,59 +303,59 @@

    Note

    Create Database

    This SQL code is run once inside an existing database to establish the schemas, table, and stored procedure used by REDCapR::retrieve_credential_mssql().

    -
    ------- SQL code to create necessary components in a Microsoft SQL Sever database -------
    -
    ------------------------------------------------------------------------
    --- Create two schemas.
    --- The first scehma is accessible by all REDCap API users.
    --- The second scehma is restricted to administrators.
    ---
    -CREATE SCHEMA [redcap]
    -CREATE SCHEMA [redcap_private]
    -GO
    -
    ------------------------------------------------------------------------
    --- Create a table to contain the token
    ---
    -CREATE TABLE [redcap_private].[tbl_credential](
    -  [id]              [smallint]          NOT NULL,
    -  [username]        [varchar](30)       NOT NULL,
    -  [project_id]      [smallint]          NOT NULL,
    -  [instance]        [varchar](30)       NOT NULL,
    -  [token]           [char](32)          NOT NULL,
    -  [redcap_uri]      [varchar](255)      NOT NULL,
    - CONSTRAINT [PK_credential] PRIMARY KEY CLUSTERED
    -(
    -  [id] ASC
    -)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    -) ON [PRIMARY]
    -
    -CREATE UNIQUE NONCLUSTERED INDEX [IX_tbl_credential_unique] ON [redcap_private].[tbl_credential]
    -(
    -  [instance]        ASC,
    -  [project_id]      ASC,
    -  [username]        ASC
    -)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    -GO
    -
    ------------------------------------------------------------------------
    --- Create a stored procedure for users to call to retrieve the token.
    --- Notice it should a different (and more permissive) schema than the table.
    ---
    -CREATE PROCEDURE [redcap].[prc_credential]
    -  -- Add the parameters for the stored procedure here
    -
    -  @project_id smallint,
    -  @instance varchar(30)
    -AS
    -BEGIN
    -  -- SET NOCOUNT ON added to prevent extra result sets from
    -  -- interfering with SELECT statements.
    -  SET NOCOUNT ON;
    -
    -  SELECT username, project_id, token, redcap_uri FROM [redcap_private].[tbl_credential]
    -  WHERE username=system_user AND project_id=@project_id AND instance=@instance
    -END
    +
    ------- SQL code to create necessary components in a Microsoft SQL Sever database -------
    +
    +-----------------------------------------------------------------------
    +-- Create two schemas.
    +-- The first scehma is accessible by all REDCap API users.
    +-- The second scehma is restricted to administrators.
    +--
    +CREATE SCHEMA [redcap]
    +CREATE SCHEMA [redcap_private]
    +GO
    +
    +-----------------------------------------------------------------------
    +-- Create a table to contain the token
    +--
    +CREATE TABLE [redcap_private].[tbl_credential](
    +  [id]              [smallint]          NOT NULL,
    +  [username]        [varchar](30)       NOT NULL,
    +  [project_id]      [smallint]          NOT NULL,
    +  [instance]        [varchar](30)       NOT NULL,
    +  [token]           [char](32)          NOT NULL,
    +  [redcap_uri]      [varchar](255)      NOT NULL,
    + CONSTRAINT [PK_credential] PRIMARY KEY CLUSTERED
    +(
    +  [id] ASC
    +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    +) ON [PRIMARY]
    +
    +CREATE UNIQUE NONCLUSTERED INDEX [IX_tbl_credential_unique] ON [redcap_private].[tbl_credential]
    +(
    +  [instance]        ASC,
    +  [project_id]      ASC,
    +  [username]        ASC
    +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    +GO
    +
    +-----------------------------------------------------------------------
    +-- Create a stored procedure for users to call to retrieve the token.
    +-- Notice it should a different (and more permissive) schema than the table.
    +--
    +CREATE PROCEDURE [redcap].[prc_credential]
    +  -- Add the parameters for the stored procedure here
    +
    +  @project_id smallint,
    +  @instance varchar(30)
    +AS
    +BEGIN
    +  -- SET NOCOUNT ON added to prevent extra result sets from
    +  -- interfering with SELECT statements.
    +  SET NOCOUNT ON;
    +
    +  SELECT username, project_id, token, redcap_uri FROM [redcap_private].[tbl_credential]
    +  WHERE username=system_user AND project_id=@project_id AND instance=@instance
    +END

    Create user credentials to the auxiliary database

    @@ -153,80 +363,80 @@

    Create user credentials to the auxiliary database

    Notice that this only gives the permissions to retrieve the token. You must still: 1. grant them API privileges to each appropriate REDCap project, and 2. copy the API from the REDCap database into the SecurityAuxiliary database.

    In the future REDCapR may expose a function that allows the user to perform the second step (through a stored procedure).

    Also, do not give typical users authorization for the ‘redcap_private’ schema. The current system allows the to view only their own tokens.

    -
    -----------------------------------------------------------------------
    --- Add a OUHSC's user account to the auxiliary_security database so that they can query the tables to retrieve their API.
    --- Notice that this only gives the permissions to retrieve the token.  You must still:
    ---   1) grant them API privileges to each appropriate REDCap project, and
    ---   2) copy the API from the REDCap database into the  auxiliary_security database.
    --- Also, do not give typical users authorization for the 'redcap_private' schema.  The current system allows the to view only their own tokens.
    ------------------------------------------------------------------------
    -
    --- STEP #1: Declare the user name.  If everything runs correctly, this should be the only piece of code that you need to modify.
    -print 'Step #1 executing....'
    -USE [master]
    -GO
    -DECLARE @qualified_user_name varchar(255); SET @qualified_user_name = '[OUHSC\lsuarez3]'
    -print 'Resulting login name: ' + @qualified_user_name; print ''
    -
    ---EXEC sp_helplogins @LoginNamePattern=@qualified_user_name
    ---SELECT * FROM master..syslogins WHERE name = @qualified_user_name
    ---SELECT * FROM auxiliary_security.sys.sysusers
    ---SELECT * FROM sys.database_permissions
    ---SELECT * FROM sys.server_principals
    -
    ------------------------------------------------------------------------
    --- STEP #2: Create a login for the *server*.
    -print 'Step #2 executing....'
    -DECLARE @sql_create_login nvarchar(max)
    -SET @sql_create_login = 'CREATE LOGIN ' + @qualified_user_name + ' FROM WINDOWS WITH DEFAULT_DATABASE=[auxiliary_security]'
    -EXECUTE sp_executesql @sql_create_login
    -DECLARE @login_count AS INT; SET @login_count = (SELECT COUNT(*) AS login_count FROM master..syslogins WHERE '[' + loginname + ']' = @qualified_user_name)
    -print 'Logins matching desired name should equal 1.  It equals: ' + CONVERT(varchar, @login_count); print ''
    -
    ------------------------------------------------------------------------
    --- STEP #3: Create a user account for the *data base*, after switching the database under focus to auxiliary_security.
    -print 'Step #3 executing....'
    -USE [auxiliary_security]
    -DECLARE @sql_create_user nvarchar(max)
    -SET @sql_create_user = 'CREATE USER ' + @qualified_user_name + ' FOR LOGIN ' + @qualified_user_name
    -EXECUTE sp_executesql @sql_create_user
    -DECLARE @user_count AS INT; SET @user_count = (SELECT COUNT(*) AS user_count FROM auxiliary_security.sys.sysusers WHERE '[' + name + ']' = @qualified_user_name)
    -print 'User accounts matching desired name should equal 1.  It equals: ' + CONVERT(varchar, @user_count); print ''
    -
    ------------------------------------------------------------------------
    --- STEP #4: Grant appropriate privileges for the 'redcap' schema.
    -print 'Step #4 executing....'
    -DECLARE @sql_grant_schema_redcap nvarchar(max)
    -SET @sql_grant_schema_redcap = 'GRANT EXECUTE ON SCHEMA::[redcap] TO ' + @qualified_user_name
    -EXECUTE sp_executesql @sql_grant_schema_redcap
    -print 'Step #4 executed'; print ''
    -
    ------------------------------------------------------------------------
    --- STEP #5: Grant appropriate privileges for the 'Security' schema.
    -print 'Step #5 executing....'
    -DECLARE @sql_grant_schema_security nvarchar(max)
    -SET @sql_grant_schema_security = 'GRANT EXECUTE ON SCHEMA::[security] TO ' + @qualified_user_name
    -EXECUTE sp_executesql @sql_grant_schema_security
    -print 'Step #5 executed'; print ''
    -
    ------------------------------------------------------------------------
    --- OPTIONAL STEP: Delete the user from the database (the first line) and then the server (the second line).
    --- The person's other database user accounts (besides with the auxiliary_security database) will NOT be automatically deleted by these two lines.
    ---USE [auxiliary_security]; DROP USER [OUHSC\lsuarez3]
    ---USE [master]; DROP LOGIN [OUHSC\lsuarez3]
    -
    ------------------------------------------------------------------------
    --- REFERENCES & NOTES
    -  --The @qualified_user_name must have both (a) the 'OUHSC' domain qualification, and (b) the square brackets (to escape the backslash).
    -  --Using sp_executesql to add users: http://www.sqlservercentral.com/Forums/Topic497615-359-1.aspx
    -  --Check if a server login exists: http://stackoverflow.com/questions/37275/sql-query-for-logins
    -  --Retrieve database users: http://stackoverflow.com/questions/2445444/how-to-get-a-list-of-users-for-all-instances-databases
    -  --Concatenating strings: http://blog.sqlauthority.com/2010/11/25/sql-server-concat-function-in-sql-server-sql-concatenation/
    -  --DROP USER from database: http://msdn.microsoft.com/en-us/library/ms189438.aspx
    -  --DROP LOGIN from server: http://msdn.microsoft.com/en-us/library/ms188012.aspx
    -  --Declaring variables (eg, the username above): http://technet.microsoft.com/en-us/library/aa258839.aspx
    -  --A different (& non-dynamic) way to establish a user: http://pic.dhe.ibm.com/infocenter/dmndhelp/v8r5m0/index.jsp?topic=%2Fcom.ibm.wbpm.imuc.sbpm.doc%2Ftopics%2Fdb_create_users_nd_aix.html
    -  --If the variable has to cross a 'GO' (which the current version of the script doesn't need): http://stackoverflow.com/questions/937336/is-there-a-way-to-persist-a-variable-across-a-go
    +
    -----------------------------------------------------------------------
    +-- Add a OUHSC's user account to the auxiliary_security database so that they can query the tables to retrieve their API.
    +-- Notice that this only gives the permissions to retrieve the token.  You must still:
    +--   1) grant them API privileges to each appropriate REDCap project, and
    +--   2) copy the API from the REDCap database into the  auxiliary_security database.
    +-- Also, do not give typical users authorization for the 'redcap_private' schema.  The current system allows the to view only their own tokens.
    +-----------------------------------------------------------------------
    +
    +-- STEP #1: Declare the user name.  If everything runs correctly, this should be the only piece of code that you need to modify.
    +print 'Step #1 executing....'
    +USE [master]
    +GO
    +DECLARE @qualified_user_name varchar(255); SET @qualified_user_name = '[OUHSC\lsuarez3]'
    +print 'Resulting login name: ' + @qualified_user_name; print ''
    +
    +--EXEC sp_helplogins @LoginNamePattern=@qualified_user_name
    +--SELECT * FROM master..syslogins WHERE name = @qualified_user_name
    +--SELECT * FROM auxiliary_security.sys.sysusers
    +--SELECT * FROM sys.database_permissions
    +--SELECT * FROM sys.server_principals
    +
    +-----------------------------------------------------------------------
    +-- STEP #2: Create a login for the *server*.
    +print 'Step #2 executing....'
    +DECLARE @sql_create_login nvarchar(max)
    +SET @sql_create_login = 'CREATE LOGIN ' + @qualified_user_name + ' FROM WINDOWS WITH DEFAULT_DATABASE=[auxiliary_security]'
    +EXECUTE sp_executesql @sql_create_login
    +DECLARE @login_count AS INT; SET @login_count = (SELECT COUNT(*) AS login_count FROM master..syslogins WHERE '[' + loginname + ']' = @qualified_user_name)
    +print 'Logins matching desired name should equal 1.  It equals: ' + CONVERT(varchar, @login_count); print ''
    +
    +-----------------------------------------------------------------------
    +-- STEP #3: Create a user account for the *data base*, after switching the database under focus to auxiliary_security.
    +print 'Step #3 executing....'
    +USE [auxiliary_security]
    +DECLARE @sql_create_user nvarchar(max)
    +SET @sql_create_user = 'CREATE USER ' + @qualified_user_name + ' FOR LOGIN ' + @qualified_user_name
    +EXECUTE sp_executesql @sql_create_user
    +DECLARE @user_count AS INT; SET @user_count = (SELECT COUNT(*) AS user_count FROM auxiliary_security.sys.sysusers WHERE '[' + name + ']' = @qualified_user_name)
    +print 'User accounts matching desired name should equal 1.  It equals: ' + CONVERT(varchar, @user_count); print ''
    +
    +-----------------------------------------------------------------------
    +-- STEP #4: Grant appropriate privileges for the 'redcap' schema.
    +print 'Step #4 executing....'
    +DECLARE @sql_grant_schema_redcap nvarchar(max)
    +SET @sql_grant_schema_redcap = 'GRANT EXECUTE ON SCHEMA::[redcap] TO ' + @qualified_user_name
    +EXECUTE sp_executesql @sql_grant_schema_redcap
    +print 'Step #4 executed'; print ''
    +
    +-----------------------------------------------------------------------
    +-- STEP #5: Grant appropriate privileges for the 'Security' schema.
    +print 'Step #5 executing....'
    +DECLARE @sql_grant_schema_security nvarchar(max)
    +SET @sql_grant_schema_security = 'GRANT EXECUTE ON SCHEMA::[security] TO ' + @qualified_user_name
    +EXECUTE sp_executesql @sql_grant_schema_security
    +print 'Step #5 executed'; print ''
    +
    +-----------------------------------------------------------------------
    +-- OPTIONAL STEP: Delete the user from the database (the first line) and then the server (the second line).
    +-- The person's other database user accounts (besides with the auxiliary_security database) will NOT be automatically deleted by these two lines.
    +--USE [auxiliary_security]; DROP USER [OUHSC\lsuarez3]
    +--USE [master]; DROP LOGIN [OUHSC\lsuarez3]
    +
    +-----------------------------------------------------------------------
    +-- REFERENCES & NOTES
    +  --The @qualified_user_name must have both (a) the 'OUHSC' domain qualification, and (b) the square brackets (to escape the backslash).
    +  --Using sp_executesql to add users: http://www.sqlservercentral.com/Forums/Topic497615-359-1.aspx
    +  --Check if a server login exists: http://stackoverflow.com/questions/37275/sql-query-for-logins
    +  --Retrieve database users: http://stackoverflow.com/questions/2445444/how-to-get-a-list-of-users-for-all-instances-databases
    +  --Concatenating strings: http://blog.sqlauthority.com/2010/11/25/sql-server-concat-function-in-sql-server-sql-concatenation/
    +  --DROP USER from database: http://msdn.microsoft.com/en-us/library/ms189438.aspx
    +  --DROP LOGIN from server: http://msdn.microsoft.com/en-us/library/ms188012.aspx
    +  --Declaring variables (eg, the username above): http://technet.microsoft.com/en-us/library/aa258839.aspx
    +  --A different (& non-dynamic) way to establish a user: http://pic.dhe.ibm.com/infocenter/dmndhelp/v8r5m0/index.jsp?topic=%2Fcom.ibm.wbpm.imuc.sbpm.doc%2Ftopics%2Fdb_create_users_nd_aix.html
    +  --If the variable has to cross a 'GO' (which the current version of the script doesn't need): http://stackoverflow.com/questions/937336/is-there-a-way-to-persist-a-variable-across-a-go

    Transfer Credentials

    @@ -236,131 +446,131 @@

    Transfer Credentials

  • Combine & groom the credentials.
  • Upload to SQL Server.
  • -
    rm(list=ls(all=TRUE)) #Clear the memory for any variables set from any previous runs.
    -
    -# ---- load-sources ------------------------------------------------------------
    -
    -# ---- load-packages -----------------------------------------------------------
    -library(magrittr)
    -requireNamespace("RODBC")
    -requireNamespace("dplyr")
    -requireNamespace("readr")
    -requireNamespace("tibble")
    -requireNamespace("checkmate")
    -
    -# ---- declare-globals ---------------------------------------------------------
    -
    -# The Activity Directory name that should precede each username.
    -#   This should correspond with the result of `SYSTEM_USER`
    -#   (https://msdn.microsoft.com/en-us/library/ms179930.aspx)
    -ldap_prefix <- "OUHSC\\"
    -
    -#Create a SQL statement for each REDCap instance.  Only the `instance` value should change.
    -sql <- "
    -  SELECT username, project_id, api_token
    -  FROM redcap_user_rights
    -  WHERE api_token IS NOT NULL"
    -
    -#Update this ad-hoc CSV.  Each row should represent one REDCap instance.
    -#   Choose any casual name for the first variable, consistent with the `tweak-data` chunk below.
    -#   Enter the exact URL for the second variable.
    -ds_url <- readr::read_csv(paste(
    -  "instance,redcap_uri",
    -  "production,https://redcap-production.ouhsc.edu/redcap/api/",
    -  "dev,https://redcap-dev.ouhsc.edu/redcap/api/",
    -sep="\n"))
    -
    -
    -# ---- load-data ---------------------------------------------------------------
    -
    -# Load the credentials from the first instance.
    -channel <- RODBC::odbcConnect("redcap-production") # odbcGetInfo(channel)
    -ds_prod <- RODBC::sqlQuery(channel, query=sql, stringsAsFactors=F)
    -RODBC::odbcClose(channel); rm(channel)
    -
    -# Load the credentials from the second instance.
    -#   Duplicate or remove this block, dependending on the number of instances.
    -channel <- RODBC::odbcConnect("redcap-dev") # odbcGetInfo(channel)
    -ds_dev  <- RODBC::sqlQuery(channel, query=sql, stringsAsFactors=F)
    -RODBC::odbcClose(channel); rm(channel, sql)
    -
    -# Assert these variables contain valid datasets (instead of a character error message), and
    -#   that at least some rows were returned.
    -#   Adjust this to smaller values if necessary.  It's really just to catch blatant retrieval problems.
    -checkmate::assert_data_frame(ds_bbmc, min.rows=5)
    -checkmate::assert_data_frame(ds_dev , min.rows=5)
    -
    -
    -# ---- tweak-data --------------------------------------------------------------
    -
    -#Label each instance, so they're distinguishable later.  Add/remove lines, depending on the number of campus instances
    -ds_prod$instance <- "production"
    -ds_dev$instance  <- "dev"
    -
    -#Combine the token collection from each instance.  Then prefix the username and include the URL of each instance.
    -ds <- ds_prod %>%
    -  dplyr::union(ds_dev) %>%                                     # Add/remove unions, based on the number of REDCap instances.
    -  dplyr::select_(
    -    "username"             = "`username`"
    -    , "project_id"         = "`project_id`"
    -    , "instance"           = "`instance`"
    -    , "token"              = "`api_token`"
    -  ) %>%
    -  dplyr::arrange(instance, project_id, username) %>%
    -  dplyr::mutate(
    -    username               = paste0(ldap_prefix, username),    # Qualify for the Active Directory.
    -    id                     = seq_len(n())                      # For the sake of a clustered primary key.
    -  ) %>%
    -  dplyr::left_join( ds_url, by="instance")                     # Include the instance URL.
    -
    -rm(ds_prod, ds_dev, ds_url)
    -
    -
    -# ---- verify-values -----------------------------------------------------------
    -# devtools::install_github("OuhscBbmc/OuhscMunge"); OuhscMunge::verify_value_headstart(ds)
    -# Assert that the dataset is well-behaved.
    -checkmate::assert_integer(  ds$id         , any.missing=F , lower=1, upper=2^31-1 , unique=T)
    -checkmate::assert_character(ds$username   , any.missing=F , pattern="^.{1,255}$"            )
    -checkmate::assert_integer(  ds$project_id , any.missing=F , lower=1, upper=2^31-1           )
    -checkmate::assert_character(ds$token      , any.missing=F , pattern="^.{32}$"     , unique=T)
    -checkmate::assert_character(ds$instance   , any.missing=F , pattern="^.{1,255}$"            )
    -checkmate::assert_character(ds$redcap_uri , any.missing=F , pattern="^.{1,255}$"            )
    -
    -testit::assert(
    -  "The `username` x `project_id` x `instance` must be unique.",
    -  sum(duplicated(paste0(ds$username, "-", ds$project_id, "-", ds$instance))) == 0L
    -)
    -
    -testit::assert("There should be at least 10 tokens written." , 10L <= nrow(ds))
    -
    -
    -# ---- specify-columns-to-upload -----------------------------------------------
    -
    -# Dictate the exact columns and order that will be uploaded.
    -columns_to_write <- c("id", "username", "project_id", "instance", "token", "redcap_uri")
    -ds_slim <- ds[, columns_to_write]
    -
    -rm(columns_to_write)
    -
    -
    -# ---- upload-to-db-credential ------------------------------------------------------------
    -
    -#Upload to SQL Server through ODBC.
    -(start_time <- Sys.time())
    -
    -db_table         <- "redcap_private.tbl_credential"
    -channel          <- RODBC::odbcConnect("auxiliary_security") #getSqlTypeInfo("Microsoft SQL Server") #;odbcGetInfo(channel)
    -
    -column_info      <- RODBC::sqlColumns(channel, db_table)
    -var_types        <- as.character(column_info$TYPE_NAME)
    -names(var_types) <- as.character(column_info$COLUMN_NAME)  #var_types
    -
    -RODBC::sqlClear(channel, db_table)
    -RODBC::sqlSave(channel, ds_slim, db_table, append=TRUE, rownames=FALSE, fast=TRUE, varTypes=var_types)
    -RODBC::odbcClose(channel); rm(channel)
    -
    -(elapsed_duration <-  Sys.time() - start_time) #0.6026149 secs 2016-08-29.
    -rm(db_table, column_info, var_types, start_time, elapsed_duration)
    +
    rm(list=ls(all=TRUE)) #Clear the memory for any variables set from any previous runs.
    +
    +# ---- load-sources ------------------------------------------------------------
    +
    +# ---- load-packages -----------------------------------------------------------
    +library(magrittr)
    +requireNamespace("RODBC")
    +requireNamespace("dplyr")
    +requireNamespace("readr")
    +requireNamespace("tibble")
    +requireNamespace("checkmate")
    +
    +# ---- declare-globals ---------------------------------------------------------
    +
    +# The Activity Directory name that should precede each username.
    +#   This should correspond with the result of `SYSTEM_USER`
    +#   (https://msdn.microsoft.com/en-us/library/ms179930.aspx)
    +ldap_prefix <- "OUHSC\\"
    +
    +#Create a SQL statement for each REDCap instance.  Only the `instance` value should change.
    +sql <- "
    +  SELECT username, project_id, api_token
    +  FROM redcap_user_rights
    +  WHERE api_token IS NOT NULL"
    +
    +#Update this ad-hoc CSV.  Each row should represent one REDCap instance.
    +#   Choose any casual name for the first variable, consistent with the `tweak-data` chunk below.
    +#   Enter the exact URL for the second variable.
    +ds_url <- readr::read_csv(paste(
    +  "instance,redcap_uri",
    +  "production,https://redcap-production.ouhsc.edu/redcap/api/",
    +  "dev,https://redcap-dev.ouhsc.edu/redcap/api/",
    +sep="\n"))
    +
    +
    +# ---- load-data ---------------------------------------------------------------
    +
    +# Load the credentials from the first instance.
    +channel <- RODBC::odbcConnect("redcap-production") # odbcGetInfo(channel)
    +ds_prod <- RODBC::sqlQuery(channel, query=sql, stringsAsFactors=F)
    +RODBC::odbcClose(channel); rm(channel)
    +
    +# Load the credentials from the second instance.
    +#   Duplicate or remove this block, dependending on the number of instances.
    +channel <- RODBC::odbcConnect("redcap-dev") # odbcGetInfo(channel)
    +ds_dev  <- RODBC::sqlQuery(channel, query=sql, stringsAsFactors=F)
    +RODBC::odbcClose(channel); rm(channel, sql)
    +
    +# Assert these variables contain valid datasets (instead of a character error message), and
    +#   that at least some rows were returned.
    +#   Adjust this to smaller values if necessary.  It's really just to catch blatant retrieval problems.
    +checkmate::assert_data_frame(ds_bbmc, min.rows=5)
    +checkmate::assert_data_frame(ds_dev , min.rows=5)
    +
    +
    +# ---- tweak-data --------------------------------------------------------------
    +
    +#Label each instance, so they're distinguishable later.  Add/remove lines, depending on the number of campus instances
    +ds_prod$instance <- "production"
    +ds_dev$instance  <- "dev"
    +
    +#Combine the token collection from each instance.  Then prefix the username and include the URL of each instance.
    +ds <- ds_prod %>%
    +  dplyr::union(ds_dev) %>%                                     # Add/remove unions, based on the number of REDCap instances.
    +  dplyr::select_(
    +    "username"             = "`username`"
    +    , "project_id"         = "`project_id`"
    +    , "instance"           = "`instance`"
    +    , "token"              = "`api_token`"
    +  ) %>%
    +  dplyr::arrange(instance, project_id, username) %>%
    +  dplyr::mutate(
    +    username               = paste0(ldap_prefix, username),    # Qualify for the Active Directory.
    +    id                     = seq_len(n())                      # For the sake of a clustered primary key.
    +  ) %>%
    +  dplyr::left_join( ds_url, by="instance")                     # Include the instance URL.
    +
    +rm(ds_prod, ds_dev, ds_url)
    +
    +
    +# ---- verify-values -----------------------------------------------------------
    +# devtools::install_github("OuhscBbmc/OuhscMunge"); OuhscMunge::verify_value_headstart(ds)
    +# Assert that the dataset is well-behaved.
    +checkmate::assert_integer(  ds$id         , any.missing=F , lower=1, upper=2^31-1 , unique=T)
    +checkmate::assert_character(ds$username   , any.missing=F , pattern="^.{1,255}$"            )
    +checkmate::assert_integer(  ds$project_id , any.missing=F , lower=1, upper=2^31-1           )
    +checkmate::assert_character(ds$token      , any.missing=F , pattern="^.{32}$"     , unique=T)
    +checkmate::assert_character(ds$instance   , any.missing=F , pattern="^.{1,255}$"            )
    +checkmate::assert_character(ds$redcap_uri , any.missing=F , pattern="^.{1,255}$"            )
    +
    +testit::assert(
    +  "The `username` x `project_id` x `instance` must be unique.",
    +  sum(duplicated(paste0(ds$username, "-", ds$project_id, "-", ds$instance))) == 0L
    +)
    +
    +testit::assert("There should be at least 10 tokens written." , 10L <= nrow(ds))
    +
    +
    +# ---- specify-columns-to-upload -----------------------------------------------
    +
    +# Dictate the exact columns and order that will be uploaded.
    +columns_to_write <- c("id", "username", "project_id", "instance", "token", "redcap_uri")
    +ds_slim <- ds[, columns_to_write]
    +
    +rm(columns_to_write)
    +
    +
    +# ---- upload-to-db-credential ------------------------------------------------------------
    +
    +#Upload to SQL Server through ODBC.
    +(start_time <- Sys.time())
    +
    +db_table         <- "redcap_private.tbl_credential"
    +channel          <- RODBC::odbcConnect("auxiliary_security") #getSqlTypeInfo("Microsoft SQL Server") #;odbcGetInfo(channel)
    +
    +column_info      <- RODBC::sqlColumns(channel, db_table)
    +var_types        <- as.character(column_info$TYPE_NAME)
    +names(var_types) <- as.character(column_info$COLUMN_NAME)  #var_types
    +
    +RODBC::sqlClear(channel, db_table)
    +RODBC::sqlSave(channel, ds_slim, db_table, append=TRUE, rownames=FALSE, fast=TRUE, varTypes=var_types)
    +RODBC::odbcClose(channel); rm(channel)
    +
    +(elapsed_duration <-  Sys.time() - start_time) #0.6026149 secs 2016-08-29.
    +rm(db_table, column_info, var_types, start_time, elapsed_duration)

    Document Info

    diff --git a/inst/doc/TroubleshootingApiCalls.html b/inst/doc/TroubleshootingApiCalls.html index 2c4ffc7f..5773d97b 100644 --- a/inst/doc/TroubleshootingApiCalls.html +++ b/inst/doc/TroubleshootingApiCalls.html @@ -11,7 +11,7 @@ - + Troubleshooting REDCap API Calls @@ -19,46 +19,256 @@ - + @@ -68,7 +278,7 @@

    Troubleshooting REDCap API Calls

    -

    2018-07-11

    +

    2018-08-10

    @@ -115,8 +325,7 @@

    Language Agnostic API

  • Can an administrator query the API successfully with Postman with the admin token? As an administrator, create an account for yourself, and verify that your token works on your server and project.

  • Can an administrator query the API successfully with Postman with the user’s token? Use Postman as before, but replace your token with the user’s token. Once the whole problem is solved, reissue new API tokens to both you and the user.

  • Can an user query the API successfully with Postman with the their own token? The values they enter should be exactly the same as those entered in the previous step. A failure here (assuming the previous step was successful) suggests a network or firewall issue. If the server is behind your institution’s firewall, verify the user is connecting successfully through the VPN.

  • -
  • Can a user query the API with cURL? cURL is a command line tool that’s underneath a lot of libraries. If it’s installed correctly on your location machine, it can be executed from the terminal or command line.

    -
    curl -X POST -H "Cache-Control: no-cache" -F "token=9A81268476645C4E5F03428B8AC3AA7B" -F "content=record" -F "format=csv" "https://bbmc.ouhsc.edu/redcap/api/"
  • +
  • Can a user query the API with cURL? cURL is a command line tool that’s underneath a lot of libraries. If it’s installed correctly on your location machine, it can be executed from the terminal or command line. shell curl -X POST -H "Cache-Control: no-cache" -F "token=9A81268476645C4E5F03428B8AC3AA7B" -F "content=record" -F "format=csv" "https://bbmc.ouhsc.edu/redcap/api/"

  • @@ -124,78 +333,78 @@

    Exporting from REDCap to R

    There are several ways to call REDCap’s API from R. The packages redcapAPI and REDCapR both rely on the httr package, which calls the curl package.

    1. Is httr installed on the user’s local machine? If so, running library(httr) should not produce any error messages if you’re starting with a fresh session of R:

      -
      > library(httr)
    2. +
    3. Does the user have the most recent version of httr? There are several ways to do this, but the easiest is probably to run update.packages(ask=FALSE, repos="http://cran.rstudio.com"). The optional argument ask prevents the user from needing to respond ‘Y’ to each outdated package.

    4. Can the user query a test project using httr? Both the redcapAPI and REDCapR packages employ something similar to the following function in httr. If you’re curious, here is the relevant source code for redcapAPI and REDCapR.

      If this check fails, consider attempting again with the uri and token used above in the Postman example.

      This check avoids checking the SSL certificate in order to simplify the troubleshooting. SSL verification is supported by default in the PyCap, redcapAPI, and REDCapR packages.

      -
      redcap_uri <- "https://bbmc.ouhsc.edu/redcap/api/"
      -token      <- "9A81268476645C4E5F03428B8AC3AA7B"
      -
      -raw_text <- RCurl::postForm(
      -  uri                         = redcap_uri
      -  , token                     = token
      -  , content                   = 'record'
      -  , format                    = 'csv'
      -  , type                      = 'flat'
      -  , rawOrLabel                = 'raw'
      -  , exportDataAccessGroups    = 'true'
      -  , .opts                     = RCurl::curlOptions(ssl.verifypeer=FALSE)
      -)
      +

      Alternatively, you can try using the httr package, which uses RCurl underneath. REDCapR and a recent fork of redcap actually uses httr directly, instead of RCurl. As of 2014-07-06, this works with the Windows 8 version for libcurl (which is underneath `RCurl), but not with some Linux versions; in this case pass the location of the SSL cert file.

      -
      post_body <- list(
      -  token                       = token,
      -  content                     = 'record',
      -  format                      = 'csv',
      -  type                        = 'flat',
      -  rawOrLabel                  = 'raw',
      -  exportDataAccessGroups      = 'true'
      -)
      -
      -raw_text <- httr::POST(
      -  url                         = redcap_uri,
      -  body                        = post_body,
      -  config                      = httr::config(ssl.verifypeer=FALSE),
      -  httr::verbose() #Remove this line to suppress the frequent console updates.
      -)
    5. +
    6. Can the user query a subset of their project using RCurl? This step is like the previous one, but with two differences. First, it’s using their REDCap project (instead of the test project). Second, it pulls fewer records, and a smaller collection of fields. Subsetting can help troubleshoot by avoiding (and thus identifying) cells with problematic values.

      Notice this call to RCurl::postForm() now passes values to the records and fields parameters. Also notice the value is a single long string, rather a vector of shorter strings (which is more natural to most R users).

      -
      redcap_uri         <- "https://the.urlofyourinsitution.edu/api/"
      -token              <- "your-secret-token"
      -records_collapsed  <- "1,2,3"                             # Assumes dataset contains ID values of 1-3.
      -fields_collapsed   <- "record_id,name_first,name_last"    # Assumes dataset contains these variables.
      -
      -raw_text <- RCurl::postForm(
      -  uri                          = redcap_uri
      -  , token                      = token
      -  , content                    = 'record'
      -  , format                     = 'csv'
      -  , type                       = 'flat'
      -  , rawOrLabel                 = 'raw'
      -  , exportDataAccessGroups     = 'true'
      -  , records                    = records_collapsed
      -  , fields                     = fields_collapsed
      -  , .opts                      = RCurl::curlOptions(ssl.verifypeer=FALSE)
      -)
    7. +
    8. Can the user query an entire project using RCurl? There are two advantages of trying a subset of the data. First, small datasets avoid the time-out errors that plague large datasets. Second, it may avoid problematic values being passed through the pipeline. If the current check fails but the previous check succeedes, then experiment with different expanses of records and fields. This should help determine which values are causing the problems, or if there’s simply too much data being pulled in one pass.

      If the desired dataset is too large, consider if you can prune unnecessary records or fields. If not, one solution is to pull smaller, multiple batches using the API, then reassemble them. The redcap_read() function in REDCapR does this automatically, and allows the user to specify a batch_size.

      -
      redcap_uri                  <- "https://the.urlofyourinsitution.edu/api/"
      -token                       <- "your-secret-token"
      -records_collapsed           <- NULL
      -fields_collapsed            <- NULL
      -
      -raw_text <- RCurl::postForm(
      -  uri                        = redcap_uri
      -  , token                    = token
      -  , content                  = 'record'
      -  , format                   = 'csv'
      -  , type                     = 'flat'
      -  , rawOrLabel               = 'raw'
      -  , exportDataAccessGroups   = 'true'
      -  , records                  = records_collapsed
      -  , fields                   = fields_collapsed
      -  , .opts                    = RCurl::curlOptions(ssl.verifypeer=FALSE)
      -)
    9. +
    @@ -203,68 +412,21 @@

    Exporting from REDCap to R, using REDCapR

    REDCapR is a package that uses cURL (via httr) to communicate with REDCap, and wraps convenience functions around it to reduce the size and complexity of the user’s code. The package’s basic functions are demonstrated in a vignette and are documented in its reference manual (a downloadable pdf of the functions are also available).

    If you’re not using REDCapR, you can skip this section and proceed to ‘Importing into REDCap from R’ below.

      -
    1. Is REDCapR installed on the user’s machine? Currently the easiest way to install REDCapR is with the devtools. The follow code installs devtools, then installs REDCapR.

      -
      install.packages("devtools", repos="http://cran.rstudio.com")
      -devtools::install_github(repo="OuhscBbmc/REDCapR")
    2. -
    3. Does REDCapR load successfully on the user’s machine? If so, running library(REDCapR) should produce the following output if you’re starting with a fresh session of R:

      -
      library(REDCapR)
      -## Loading required package: REDCapR
    4. -
    5. Can the user export from an example project? This is the same fake data hosted by the OUHSC BBMC as in the previous section.

      -
      library(REDCapR) #Load the package into the current R session.
      -uri   <- "https://bbmc.ouhsc.edu/redcap/api/"
      -token <- "9A81268476645C4E5F03428B8AC3AA7B"
      -redcap_read(redcap_uri=uri, token=token)$data
      -

      The previous code should produce similar output. Notice there are five rows and the columns will wrap around if your console window is too narrow.

      -
      5 records and 1 columns were read from REDCap in 0.41 seconds.
      -Starting to read 5 records  at 2014-06-27 17:19:49
      -Reading batch 1 of 1, with ids 1 through 5.
      -5 records and 16 columns were read from REDCap in 0.42 seconds.
      -
      -  record_id name_first name_last                                 address      telephone               email
      -1         1     Nutmeg  Nutmouse 14 Rose Cottage St.\nKenning UK, 323232 (432) 456-4848     nutty@mouse.com
      -2         2     Tumtum  Nutmouse 14 Rose Cottage Blvd.\nKenning UK 34243 (234) 234-2343    tummy@mouse.comm
      -3         3     Marcus      Wood          243 Hill St.\nGuthrie OK 73402 (433) 435-9865        mw@mwood.net
      -4         4      Trudy       DAG          342 Elm\nDuncanville TX, 75116 (987) 654-3210 peroxide@blonde.com
      -5         5   John Lee    Walker      Hotel Suite\nNew Orleans LA, 70115 (333) 333-4444  left@hippocket.com
      -
      -         dob age ethnicity race sex height weight   bmi
      -1 2003-08-30  10         1    2   0   5.00      1 400.0
      -2 2003-03-10  10         1    6   1   6.00      1 277.8
      -3 1934-04-09  79         0    4   1 180.00     80  24.7
      -4 1952-11-02  61         1    4   0 165.00     54  19.8
      -5 1955-04-15  58         1    4   1 193.04    104  27.9
      -
      -                                                                                                     comments
      -1                                                                     Character in a book, with some guessing
      -2                                                                          A mouse character from a good book
      -3                                                                                          completely made up
      -4 This record doesn't have a DAG assigned\n\nSo call up Trudy on the telephone\nSend her a letter in the mail
      -5                 Had a hand for trouble and a eye for cash\n\nHe had a gold watch chain and a black mustache
      -
      -  demographics_complete
      -1                     2
      -2                     2
      -3                     2
      -4                     2
      -5                     2
    6. -
    7. Can the user export from their own project? The code is similar to the previous check, but the uri and token values will need to be modified.

      -
      library(REDCapR) #Load the package into the current R session, if you haven't already.
      -redcap_uri       <- "https://the.urlofyourinsitution.edu/api/"
      -token            <- "your-secret-token"
      -redcap_read(redcap_uri=uri, token=token)$data
      -

      Alternatively, a redcap_project object can be declared initially, which makes subsequent calls cleaner when the token and url are required only the when the object is declared.

      -
      library(REDCapR) #Load the package into the current R session, if you haven't already.
      -uri               <- "https://bbmc.ouhsc.edu/redcap/api/"
      -token             <- "9A81268476645C4E5F03428B8AC3AA7B"
      -project           <- redcap_project$new(redcap_uri=uri, token=token)
      -
      -ds_three_columns  <- project$read(fields=c("record_id", "sex", "age"))$data
      -
      -ids_of_males      <- ds_three_columns$record_id[ds_three_columns$sex==1]
      -ids_of_minors     <- ds_three_columns$record_id[ds_three_columns$age < 18]
      -
      -ds_males          <- project$read(records=ids_of_males, batch_size=2)$data
      -ds_minors         <- project$read(records=ids_of_minors)$data
    8. +
    9. Is REDCapR installed on the user’s machine? Currently the easiest way to install REDCapR is with the devtools. The follow code installs devtools, then installs REDCapR. r install.packages("devtools", repos="http://cran.rstudio.com") devtools::install_github(repo="OuhscBbmc/REDCapR")

    10. +
    11. Does REDCapR load successfully on the user’s machine? If so, running library(REDCapR) should produce the following output if you’re starting with a fresh session of R: r library(REDCapR) ## Loading required package: REDCapR

    12. +
    13. Can the user export from an example project? This is the same fake data hosted by the OUHSC BBMC as in the previous section. r library(REDCapR) #Load the package into the current R session. uri <- "https://bbmc.ouhsc.edu/redcap/api/" token <- "9A81268476645C4E5F03428B8AC3AA7B" redcap_read(redcap_uri=uri, token=token)$data

      +

      The previous code should produce similar output. Notice there are five rows and the columns will wrap around if your console window is too narrow. ``` 5 records and 1 columns were read from REDCap in 0.41 seconds. Starting to read 5 records at 2014-06-27 17:19:49 Reading batch 1 of 1, with ids 1 through 5. 5 records and 16 columns were read from REDCap in 0.42 seconds.

      +

      record_id name_first name_last address telephone email 1 1 Nutmeg Nutmouse 14 Rose Cottage St.UK, 323232 (432) 456-4848 nutty@mouse.com 2 2 Tumtum Nutmouse 14 Rose Cottage Blvd.UK 34243 (234) 234-2343 tummy@mouse.comm 3 3 Marcus Wood 243 Hill St.OK 73402 (433) 435-9865 mw@mwood.net 4 4 Trudy DAG 342 ElmTX, 75116 (987) 654-3210 peroxide@blonde.com 5 5 John Lee Walker Hotel SuiteOrleans LA, 70115 (333) 333-4444 left@hippocket.com

      +
            dob age ethnicity race sex height weight   bmi
      +

      1 2003-08-30 10 1 2 0 5.00 1 400.0 2 2003-03-10 10 1 6 1 6.00 1 277.8 3 1934-04-09 79 0 4 1 180.00 80 24.7 4 1952-11-02 61 1 4 0 165.00 54 19.8 5 1955-04-15 58 1 4 1 193.04 104 27.9

      +
                                                                                                        comments
      +

      1 Character in a book, with some guessing 2 A mouse character from a good book 3 completely made up 4 This record doesn’t have a DAG assignedcall up Trudy on the telephoneher a letter in the mail 5 Had a hand for trouble and a eye for cashhad a gold watch chain and a black mustache

      +

      demographics_complete 1 2 2 2 3 2 4 2 5 2 ```

    14. +
    15. Can the user export from their own project? The code is similar to the previous check, but the uri and token values will need to be modified. r library(REDCapR) #Load the package into the current R session, if you haven't already. redcap_uri <- "https://the.urlofyourinsitution.edu/api/" token <- "your-secret-token" redcap_read(redcap_uri=uri, token=token)$data

      +

      Alternatively, a redcap_project object can be declared initially, which makes subsequent calls cleaner when the token and url are required only the when the object is declared. ```r library(REDCapR) #Load the package into the current R session, if you haven’t already. uri <- “https://bbmc.ouhsc.edu/redcap/api/” token <- “9A81268476645C4E5F03428B8AC3AA7B” project <- redcap_project$new(redcap_uri=uri, token=token)

      +

      ds_three_columns <- project\(read(fields=c("record_id", "sex", "age"))\)data

      +

      ids_of_males <- ds_three_columns\(record_id[ds_three_columns\)sex==1] ids_of_minors <- ds_three_columns\(record_id[ds_three_columns\)age < 18]

      +

      ds_males <- project\(read(records=ids_of_males, batch_size=2)\)data ds_minors <- project\(read(records=ids_of_minors)\)data ```

    16. Is the export operation still unsuccessful using REDCapR? If so the “Can the user query a entire REDCap project using RCurl?” check succeeded, but the REDCapR checks did not, consider posting a new GitHub issue to the package developers.

    @@ -273,56 +435,17 @@

    Exporting from REDCap to R, using redcapAPI

    redcapAPI is a package that uses cURL (via httr) to communicate with REDCap, and wraps convenience functions around it to reduce the size and complexity of the user’s code.

    If you’re not using redcapAPI, you can skip this section and proceed to ‘Importing into REDCap from R’ below. More specific discussion about redcapAPI can be found at the package’s wiki.

      -
    1. Is redcapAPI installed on the user’s machine? Currently, the easiest way to install redcapAPI is from CRAN.

      -
      install.packages("redcapAPI")
      -

      Developmental versions may be available on GitHub.

      -
      install.packages("devtools", repos="http://cran.rstudio.com")
      -devtools::install_github(repo="nutterb/redcapAPI")
    2. -
    3. Does redcapAPI load successfully on the user’s machine? If so, running library(redcapAPI) should produce the following output if you’re starting with a fresh session of R:

      -
      library(redcapAPI)
      -## Loading required package: redcapAPI
    4. -
    5. Can the user export from an example project? This is the same fake data hosted by the OUHSC BBMC as in the previous section.

      -
      library(redcapAPI) #Load the package into the current R session.
      -rcon <- redcapConnection(
      -  url   = "https://bbmc.ouhsc.edu/redcap/api/",
      -  token = "9A81268476645C4E5F03428B8AC3AA7B"
      -)
      -exportRecords(rcon)
      -

      The previous code should produce similar output. Notice there are five rows and the columns will wrap around if your console window is too narrow.

      -
      record_id name_first name_last                                 address      telephone               email
      -1         1     Nutmeg  Nutmouse 14 Rose Cottage St.\nKenning UK, 323232 (432) 456-4848     nutty@mouse.com
      -2         2     Tumtum  Nutmouse 14 Rose Cottage Blvd.\nKenning UK 34243 (234) 234-2343    tummy@mouse.comm
      -3         3     Marcus      Wood          243 Hill St.\nGuthrie OK 73402 (433) 435-9865        mw@mwood.net
      -4         4      Trudy       DAG          342 Elm\nDuncanville TX, 75116 (987) 654-3210 peroxide@blonde.com
      -5         5   John Lee    Walker      Hotel Suite\nNew Orleans LA, 70115 (333) 333-4444  left@hippocket.com
      -
      -       dob age ethnicity race sex height weight   bmi
      -1 2003-08-30  10         1    2   0   5.00      1 400.0
      -2 2003-03-10  10         1    6   1   6.00      1 277.8
      -3 1934-04-09  79         0    4   1 180.00     80  24.7
      -4 1952-11-02  61         1    4   0 165.00     54  19.8
      -5 1955-04-15  58         1    4   1 193.04    104  27.9
      -
      -                                                                                                   comments
      -1                                                                     Character in a book, with some guessing
      -2                                                                          A mouse character from a good book
      -3                                                                                          completely made up
      -4 This record doesn't have a DAG assigned\n\nSo call up Trudy on the telephone\nSend her a letter in the mail
      -5                 Had a hand for trouble and a eye for cash\n\nHe had a gold watch chain and a black mustache
      -
      -demographics_complete
      -1                     2
      -2                     2
      -3                     2
      -4                     2
      -5                     2
    6. -
    7. Can the user export from their own project? The code is similar to the previous check, but the uri and token values will need to be modified.

      -
      library(redcapAPI) #Load the package into the current R session, if you haven't already.
      -rcon <- redcapConnection(
      -  url   = "https://the.urlofyourinsitution.edu/api/", # Adapt this to your server.
      -  token = "your-secret-token"                         # Adapt this to your user's token.
      -)
      -exportRecords(rcon)
    8. +
    9. Is redcapAPI installed on the user’s machine? Currently, the easiest way to install redcapAPI is from CRAN. r install.packages("redcapAPI")

      +

      Developmental versions may be available on GitHub. r install.packages("devtools", repos="http://cran.rstudio.com") devtools::install_github(repo="nutterb/redcapAPI")

    10. +
    11. Does redcapAPI load successfully on the user’s machine? If so, running library(redcapAPI) should produce the following output if you’re starting with a fresh session of R: r library(redcapAPI) ## Loading required package: redcapAPI

    12. +
    13. Can the user export from an example project? This is the same fake data hosted by the OUHSC BBMC as in the previous section. r library(redcapAPI) #Load the package into the current R session. rcon <- redcapConnection( url = "https://bbmc.ouhsc.edu/redcap/api/", token = "9A81268476645C4E5F03428B8AC3AA7B" ) exportRecords(rcon)

      +

      The previous code should produce similar output. Notice there are five rows and the columns will wrap around if your console window is too narrow. ``` record_id name_first name_last address telephone email 1 1 Nutmeg Nutmouse 14 Rose Cottage St.UK, 323232 (432) 456-4848 nutty@mouse.com 2 2 Tumtum Nutmouse 14 Rose Cottage Blvd.UK 34243 (234) 234-2343 tummy@mouse.comm 3 3 Marcus Wood 243 Hill St.OK 73402 (433) 435-9865 mw@mwood.net 4 4 Trudy DAG 342 ElmTX, 75116 (987) 654-3210 peroxide@blonde.com 5 5 John Lee Walker Hotel SuiteOrleans LA, 70115 (333) 333-4444 left@hippocket.com

      +
          dob age ethnicity race sex height weight   bmi
      +

      1 2003-08-30 10 1 2 0 5.00 1 400.0 2 2003-03-10 10 1 6 1 6.00 1 277.8 3 1934-04-09 79 0 4 1 180.00 80 24.7 4 1952-11-02 61 1 4 0 165.00 54 19.8 5 1955-04-15 58 1 4 1 193.04 104 27.9

      +
                                                                                                      comments
      +

      1 Character in a book, with some guessing 2 A mouse character from a good book 3 completely made up 4 This record doesn’t have a DAG assignedcall up Trudy on the telephoneher a letter in the mail 5 Had a hand for trouble and a eye for cashhad a gold watch chain and a black mustache

      +

      demographics_complete 1 2 2 2 3 2 4 2 5 2 ```

    14. +
    15. Can the user export from their own project? The code is similar to the previous check, but the uri and token values will need to be modified. r library(redcapAPI) #Load the package into the current R session, if you haven't already. rcon <- redcapConnection( url = "https://the.urlofyourinsitution.edu/api/", # Adapt this to your server. token = "your-secret-token" # Adapt this to your user's token. ) exportRecords(rcon)

    16. Is the export operation still unsuccessful using redcapAPI? If so the “Can the user query a entire REDCap project using RCurl?” check succeeded, but the redcapAPI checks did not, consider posting a new GitHub issue to the package developers.

    @@ -352,12 +475,11 @@

    Other software for communication and automation with REDCap

    Other good resources

    diff --git a/inst/doc/advanced-redcapr-operations.html b/inst/doc/advanced-redcapr-operations.html index 6f7a9dc0..7b5f5556 100644 --- a/inst/doc/advanced-redcapr-operations.html +++ b/inst/doc/advanced-redcapr-operations.html @@ -11,7 +11,7 @@ - + Advanced REDCapR Operations @@ -19,46 +19,256 @@ - + @@ -68,7 +278,7 @@

    Advanced REDCapR Operations

    -

    2018-07-11

    +

    2018-08-10

    @@ -79,992 +289,209 @@

    Next Steps

    Set project-wide values.

    There is some information that is specific to a REDCap project, as opposed to an individual operation. This includes the (1) uri of the server, and the (2) token for the user’s project. This is hosted on a machine used in REDCapR’s public test suite, so you can run this example from any computer. Unless tests are running.

    -
    library(REDCapR) #Load the package into the current R session.
    -uri                   <- "https://bbmc.ouhsc.edu/redcap/api/"
    -token_simple          <- "9A81268476645C4E5F03428B8AC3AA7B"
    -token_longitudinal    <- "0434F0E9CF53ED0587847AB6E51DE762"
    +

    Converting from tall/long to wide

    Disclaimer: Occasionally we’re asked for a longitudinal dataset to be converted from a “long/tall format” (where typically each row is one observation for a participant) to a “wide format” (where each row is on participant). Usually we advise against it. Besides all the database benefits of a long structure, a wide structure restricts your options with the stat routine. No modern longitudinal analysis procedures (eg, growth curve models or multilevel/hierarchical models) accept wide. You’re pretty much stuck with repeated measures anova, which is very inflexible for real-world medical-ish analyses. It requires a patient to have a measurement at every time point; otherwise the anova excludes the patient entirely.

    However we like going wide to produce visual tables for publications, and here’s one way to do it in R. First retrieve the dataset from REDCap.

    -
    library(magrittr); 
    -suppressPackageStartupMessages(requireNamespace("dplyr"))
    -suppressPackageStartupMessages(requireNamespace("tidyr"))
    -events_to_retain  <- c("dose_1_arm_1", "visit_1_arm_1", "dose_2_arm_1", "visit_2_arm_1")
    -
    -ds_long <- REDCapR::redcap_read_oneshot(redcap_uri=uri, token=token_longitudinal)$data
    -
    #> 18 records and 125 columns were read from REDCap in 0.7 seconds.  The http status code was 200.
    -
    ds_long %>% 
    -  dplyr::select(study_id, redcap_event_name, pmq1, pmq2, pmq3, pmq4)
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -study id - -redcap event name - -pmq1 - -pmq2 - -pmq3 - -pmq4 -
    -100 - -enrollment_arm_1 - -NA - -NA - -NA - -NA -
    -100 - -dose_1_arm_1 - -2 - -2 - -1 - -1 -
    -100 - -visit_1_arm_1 - -1 - -0 - -0 - -0 -
    -100 - -dose_2_arm_1 - -3 - -1 - -0 - -0 -
    -100 - -visit_2_arm_1 - -0 - -1 - -0 - -0 -
    -100 - -final_visit_arm_1 - -NA - -NA - -NA - -NA -
    -220 - -enrollment_arm_1 - -NA - -NA - -NA - -NA -
    -220 - -dose_1_arm_1 - -0 - -1 - -0 - -2 -
    -220 - -visit_1_arm_1 - -0 - -3 - -1 - -0 -
    -220 - -dose_2_arm_1 - -1 - -2 - -0 - -1 -
    -220 - -visit_2_arm_1 - -3 - -4 - -1 - -0 -
    -220 - -final_visit_arm_1 - -NA - -NA - -NA - -NA -
    -304 - -enrollment_arm_2 - -NA - -NA - -NA - -NA -
    -304 - -deadline_to_opt_ou_arm_2 - -NA - -NA - -NA - -NA -
    -304 - -first_dose_arm_2 - -0 - -1 - -0 - -0 -
    -304 - -first_visit_arm_2 - -2 - -0 - -0 - -0 -
    -304 - -final_visit_arm_2 - -NA - -NA - -NA - -NA -
    -304 - -deadline_to_return_arm_2 - -NA - -NA - -NA - -NA -
    + +
    #> 18 records and 125 columns were read from REDCap in 0.4 seconds.  The http status code was 200.
    + +
    #>    study_id        redcap_event_name pmq1 pmq2 pmq3 pmq4
    +#> 1       100         enrollment_arm_1   NA   NA   NA   NA
    +#> 2       100             dose_1_arm_1    2    2    1    1
    +#> 3       100            visit_1_arm_1    1    0    0    0
    +#> 4       100             dose_2_arm_1    3    1    0    0
    +#> 5       100            visit_2_arm_1    0    1    0    0
    +#> 6       100        final_visit_arm_1   NA   NA   NA   NA
    +#> 7       220         enrollment_arm_1   NA   NA   NA   NA
    +#> 8       220             dose_1_arm_1    0    1    0    2
    +#> 9       220            visit_1_arm_1    0    3    1    0
    +#> 10      220             dose_2_arm_1    1    2    0    1
    +#> 11      220            visit_2_arm_1    3    4    1    0
    +#> 12      220        final_visit_arm_1   NA   NA   NA   NA
    +#> 13      304         enrollment_arm_2   NA   NA   NA   NA
    +#> 14      304 deadline_to_opt_ou_arm_2   NA   NA   NA   NA
    +#> 15      304         first_dose_arm_2    0    1    0    0
    +#> 16      304        first_visit_arm_2    2    0    0    0
    +#> 17      304        final_visit_arm_2   NA   NA   NA   NA
    +#> 18      304 deadline_to_return_arm_2   NA   NA   NA   NA

    When widening only one variable (eg, pmq1), the code’s pretty simple:

    -
    ds_wide <- ds_long %>% 
    -  dplyr::select(study_id, redcap_event_name, pmq1) %>% 
    -  dplyr::filter(redcap_event_name %in% events_to_retain) %>% 
    -  tidyr::spread(key=redcap_event_name, value=pmq1)
    -ds_wide
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -study id - -dose 1 arm 1 - -dose 2 arm 1 - -visit 1 arm 1 - -visit 2 arm 1 -
    -100 - -2 - -3 - -1 - -0 -
    -220 - -0 - -1 - -0 - -3 -
    + +
    #>   study_id dose_1_arm_1 dose_2_arm_1 visit_1_arm_1 visit_2_arm_1
    +#> 1      100            2            3             1             0
    +#> 2      220            0            1             0             3

    When widening more than one variable (eg, pmq1 - pmq4), it’s usually easiest to go even longer/taller (eg, ds_eav) before reversing direction and going wide:

    -
    pattern <- "^(\\w+?)_arm_(\\d)$"
    -
    -ds_eav <- ds_long %>% 
    -  dplyr::select(study_id, redcap_event_name, pmq1, pmq2, pmq3, pmq4) %>% 
    -  dplyr::mutate(
    -    event      = sub(pattern, "\\1", redcap_event_name),
    -    arm        = as.integer(sub(pattern, "\\2", redcap_event_name))
    -  ) %>% 
    -  dplyr::select(study_id, event, arm, pmq1, pmq2, pmq3, pmq4) %>% 
    -  tidyr::gather(key=key, value=value, pmq1, pmq2, pmq3, pmq4) %>% 
    -  dplyr::filter(!(event %in% c(
    -    "enrollment", "final_visit", "deadline_to_return", "deadline_to_opt_ou")
    -  )) %>% 
    -  dplyr::mutate( # Simulate correcting for mismatched names across arms:
    -    event = dplyr::recode(event, "first_dose"="dose_1", "first_visit"="visit_1"),
    -    key = paste0(event, "_", key)
    -  ) %>% 
    -  dplyr::select(-event)
    -
    -# Show the first 10 rows of the EAV table.
    -ds_eav %>% 
    -  head(10)
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -study id - -arm - -key - -value -
    -100 - -1 - -dose_1_pmq1 - -2 -
    -100 - -1 - -visit_1_pmq1 - -1 -
    -100 - -1 - -dose_2_pmq1 - -3 -
    -100 - -1 - -visit_2_pmq1 - -0 -
    -220 - -1 - -dose_1_pmq1 - -0 -
    -220 - -1 - -visit_1_pmq1 - -0 -
    -220 - -1 - -dose_2_pmq1 - -1 -
    -220 - -1 - -visit_2_pmq1 - -3 -
    -304 - -2 - -dose_1_pmq1 - -0 -
    -304 - -2 - -visit_1_pmq1 - -2 -
    -
    # Spread the EAV to wide.
    -ds_wide <- ds_eav %>% 
    -  tidyr::spread(key=key, value=value)
    -ds_wide
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -study id - -arm - -dose 1 pmq1 - -dose 1 pmq2 - -dose 1 pmq3 - -dose 1 pmq4 - -dose 2 pmq1 - -dose 2 pmq2 - -dose 2 pmq3 - -dose 2 pmq4 - -visit 1 pmq1 - -visit 1 pmq2 - -visit 1 pmq3 - -visit 1 pmq4 - -visit 2 pmq1 - -visit 2 pmq2 - -visit 2 pmq3 - -visit 2 pmq4 -
    -100 - -1 - -2 - -2 - -1 - -1 - -3 - -1 - -0 - -0 - -1 - -0 - -0 - -0 - -0 - -1 - -0 - -0 -
    -220 - -1 - -0 - -1 - -0 - -2 - -1 - -2 - -0 - -1 - -0 - -3 - -1 - -0 - -3 - -4 - -1 - -0 -
    -304 - -2 - -0 - -1 - -0 - -0 - -NA - -NA - -NA - -NA - -2 - -0 - -0 - -0 - -NA - -NA - -NA - -NA -
    + +
    #>    study_id arm          key value
    +#> 1       100   1  dose_1_pmq1     2
    +#> 2       100   1 visit_1_pmq1     1
    +#> 3       100   1  dose_2_pmq1     3
    +#> 4       100   1 visit_2_pmq1     0
    +#> 5       220   1  dose_1_pmq1     0
    +#> 6       220   1 visit_1_pmq1     0
    +#> 7       220   1  dose_2_pmq1     1
    +#> 8       220   1 visit_2_pmq1     3
    +#> 9       304   2  dose_1_pmq1     0
    +#> 10      304   2 visit_1_pmq1     2
    + +
    #>   study_id arm dose_1_pmq1 dose_1_pmq2 dose_1_pmq3 dose_1_pmq4 dose_2_pmq1
    +#> 1      100   1           2           2           1           1           3
    +#> 2      220   1           0           1           0           2           1
    +#> 3      304   2           0           1           0           0          NA
    +#>   dose_2_pmq2 dose_2_pmq3 dose_2_pmq4 visit_1_pmq1 visit_1_pmq2
    +#> 1           1           0           0            1            0
    +#> 2           2           0           1            0            3
    +#> 3          NA          NA          NA            2            0
    +#>   visit_1_pmq3 visit_1_pmq4 visit_2_pmq1 visit_2_pmq2 visit_2_pmq3
    +#> 1            0            0            0            1            0
    +#> 2            1            0            3            4            1
    +#> 3            0            0           NA           NA           NA
    +#>   visit_2_pmq4
    +#> 1            0
    +#> 2            0
    +#> 3           NA

    SSL Options

    The official cURL site discusses the process of using SSL to verify the server being connected to.

    Use the SSL cert file that come with the openssl package.

    -
    cert_location <- system.file("cacert.pem", package="openssl")
    -if( file.exists(cert_location) ) {
    -  config_options         <- list(cainfo=cert_location)
    -  ds_different_cert_file <- redcap_read_oneshot(
    -    redcap_uri     = uri,
    -    token          = token_simple,
    -    config_options = config_options
    -  )$data
    -}
    -
    #> 5 records and 24 columns were read from REDCap in 0.4 seconds.  The http status code was 200.
    + +
    #> 5 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.

    Force the connection to use SSL=3 (which is not preferred, and possibly insecure).

    -
    config_options <- list(sslversion=3)
    -ds_ssl_3 <- redcap_read_oneshot(
    -  redcap_uri     = uri,
    -  token          = token_simple,
    -  config_options = config_options
    -)$data
    -
    #> 5 records and 24 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    -
    config_options <- list(ssl.verifypeer=FALSE)
    -ds_no_ssl <- redcap_read_oneshot(
    -   redcap_uri     = uri,
    -   token          = token_simple,
    -   config_options = config_options
    -)$data
    -
    #> 5 records and 24 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    + +
    #> 5 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    + +
    #> 5 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.

    Session Information

    For the sake of documentation and reproducibility, the current report was rendered in the following environment. Click the line below to expand.

    -

    Environment

    -
    #> ─ Session info ──────────────────────────────────────────────────────────
    -#>  setting  value                       
    -#>  version  R version 3.4.4 (2018-03-15)
    -#>  os       Ubuntu 18.04 LTS            
    -#>  system   x86_64, linux-gnu           
    -#>  ui       RStudio                     
    -#>  language (EN)                        
    -#>  collate  en_US.UTF-8                 
    -#>  tz       America/Chicago             
    -#>  date     2018-07-11                  
    +
    +

    Environment

    +
    #> - Session info ----------------------------------------------------------
    +#>  setting  value                                      
    +#>  version  R version 3.5.1 Patched (2018-08-06 r75070)
    +#>  os       Windows >= 8 x64                           
    +#>  system   x86_64, mingw32                            
    +#>  ui       RStudio                                    
    +#>  language (EN)                                       
    +#>  collate  English_United States.1252                 
    +#>  tz       America/Chicago                            
    +#>  date     2018-08-10                                 
     #> 
    -#> ─ Packages ──────────────────────────────────────────────────────────────
    +#> - Packages --------------------------------------------------------------
     #>  package     * version     date       source                          
    -#>  assertthat    0.2.0       2017-04-11 cran (@0.2.0)                   
    -#>  backports     1.1.2       2017-12-13 cran (@1.1.2)                   
    -#>  bindr         0.1.1       2018-03-13 CRAN (R 3.4.3)                  
    -#>  bindrcpp    * 0.2.2       2018-03-29 CRAN (R 3.4.3)                  
    -#>  checkmate     1.8.6       2018-04-10 Github (mllg/checkmate@489319a) 
    -#>  clisymbols    1.2.0       2017-05-21 CRAN (R 3.4.3)                  
    -#>  codetools     0.2-15      2016-10-05 CRAN (R 3.4.3)                  
    -#>  colorspace    1.3-2       2016-12-14 CRAN (R 3.4.3)                  
    -#>  commonmark    1.5         2018-04-28 CRAN (R 3.4.4)                  
    -#>  crayon        1.3.4       2017-09-16 CRAN (R 3.4.3)                  
    -#>  curl          3.2         2018-03-28 CRAN (R 3.4.4)                  
    -#>  desc          1.2.0       2018-05-01 CRAN (R 3.4.4)                  
    -#>  devtools      1.13.6      2018-06-27 CRAN (R 3.4.4)                  
    -#>  digest        0.6.15      2018-01-28 CRAN (R 3.4.3)                  
    -#>  dplyr         0.7.6       2018-06-29 CRAN (R 3.4.4)                  
    -#>  evaluate      0.10.1      2017-06-24 CRAN (R 3.4.3)                  
    -#>  glue          1.2.0       2017-10-29 cran (@1.2.0)                   
    -#>  highr         0.7         2018-06-09 CRAN (R 3.4.4)                  
    -#>  hms           0.4.2.9000  2018-05-26 Github (tidyverse/hms@14e74ab)  
    -#>  htmltools     0.3.6       2017-04-28 CRAN (R 3.4.3)                  
    -#>  httr          1.3.1       2017-08-20 CRAN (R 3.4.3)                  
    -#>  kableExtra    0.9.0       2018-05-21 CRAN (R 3.4.4)                  
    -#>  knitr       * 1.20        2018-02-20 CRAN (R 3.4.3)                  
    -#>  magrittr    * 1.5         2014-11-22 cran (@1.5)                     
    -#>  memoise       1.1.0       2017-04-21 CRAN (R 3.4.3)                  
    -#>  munsell       0.5.0       2018-06-12 CRAN (R 3.4.4)                  
    -#>  pillar        1.2.3       2018-05-25 CRAN (R 3.4.4)                  
    -#>  pkgconfig     2.0.1       2017-03-21 cran (@2.0.1)                   
    -#>  plyr          1.8.4       2016-06-08 CRAN (R 3.4.3)                  
    -#>  purrr         0.2.5       2018-05-29 CRAN (R 3.4.4)                  
    -#>  R6            2.2.2       2017-06-17 CRAN (R 3.4.3)                  
    -#>  Rcpp          0.12.17     2018-05-18 CRAN (R 3.4.4)                  
    -#>  readr         1.2.0       2018-05-26 Github (tidyverse/readr@d6d622b)
    +#>  assertthat    0.2.0       2017-04-11 CRAN (R 3.5.0)                  
    +#>  backports     1.1.2       2017-12-13 CRAN (R 3.5.0)                  
    +#>  bindr         0.1.1       2018-03-13 CRAN (R 3.5.0)                  
    +#>  bindrcpp    * 0.2.2       2018-03-29 CRAN (R 3.5.0)                  
    +#>  checkmate     1.8.9-9000  2018-08-09 Github (mllg/checkmate@29a1fb9) 
    +#>  clisymbols    1.2.0       2017-05-21 CRAN (R 3.5.0)                  
    +#>  codetools     0.2-15      2016-10-05 CRAN (R 3.5.1)                  
    +#>  colorspace    1.3-2       2016-12-14 CRAN (R 3.5.0)                  
    +#>  commonmark    1.5         2018-04-28 CRAN (R 3.5.0)                  
    +#>  crayon        1.3.4       2017-09-16 CRAN (R 3.5.0)                  
    +#>  curl          3.2         2018-03-28 CRAN (R 3.5.0)                  
    +#>  desc          1.2.0       2018-05-01 CRAN (R 3.5.0)                  
    +#>  devtools      1.13.6      2018-06-27 CRAN (R 3.5.0)                  
    +#>  digest        0.6.15      2018-01-28 CRAN (R 3.5.0)                  
    +#>  dplyr         0.7.6       2018-06-29 CRAN (R 3.5.1)                  
    +#>  evaluate      0.11        2018-07-17 CRAN (R 3.5.1)                  
    +#>  git2r         0.23.0      2018-07-17 CRAN (R 3.5.1)                  
    +#>  glue          1.3.0       2018-07-17 CRAN (R 3.5.1)                  
    +#>  hms           0.4.2.9001  2018-08-09 Github (tidyverse/hms@979286f)  
    +#>  htmltools     0.3.6       2017-04-28 CRAN (R 3.5.0)                  
    +#>  httr          1.3.1       2017-08-20 CRAN (R 3.5.0)                  
    +#>  kableExtra    0.9.0       2018-05-21 CRAN (R 3.5.0)                  
    +#>  knitr       * 1.20        2018-02-20 CRAN (R 3.5.0)                  
    +#>  magrittr    * 1.5         2014-11-22 CRAN (R 3.5.0)                  
    +#>  memoise       1.1.0       2017-04-21 CRAN (R 3.5.0)                  
    +#>  munsell       0.5.0       2018-06-12 CRAN (R 3.5.0)                  
    +#>  packrat       0.4.9-3     2018-06-01 CRAN (R 3.5.0)                  
    +#>  pillar        1.3.0       2018-07-14 CRAN (R 3.5.1)                  
    +#>  pkgconfig     2.0.1       2017-03-21 CRAN (R 3.5.0)                  
    +#>  purrr         0.2.5       2018-05-29 CRAN (R 3.5.0)                  
    +#>  R6            2.2.2       2017-06-17 CRAN (R 3.5.0)                  
    +#>  Rcpp          0.12.18     2018-07-23 CRAN (R 3.5.1)                  
    +#>  readr         1.2.0       2018-08-09 Github (tidyverse/readr@4b2e93a)
     #>  REDCapR     * 0.9.10.9001 <NA>       local                           
    -#>  rlang         0.2.1       2018-05-30 CRAN (R 3.4.4)                  
    -#>  rmarkdown     1.10        2018-06-11 CRAN (R 3.4.4)                  
    -#>  roxygen2      6.0.1       2017-02-06 CRAN (R 3.4.4)                  
    -#>  rprojroot     1.3-2       2018-01-03 CRAN (R 3.4.3)                  
    -#>  rstudioapi    0.7         2017-09-07 CRAN (R 3.4.3)                  
    -#>  rvest         0.3.2       2016-06-17 CRAN (R 3.4.3)                  
    -#>  scales        0.5.0.9000  2018-03-29 Github (hadley/scales@d767915)  
    -#>  sessioninfo   1.0.0       2017-06-21 CRAN (R 3.4.3)                  
    -#>  stringi       1.2.3       2018-06-12 CRAN (R 3.4.4)                  
    -#>  stringr       1.3.1       2018-05-10 CRAN (R 3.4.4)                  
    -#>  testthat      2.0.0       2017-12-13 CRAN (R 3.4.3)                  
    -#>  tibble        1.4.2       2018-01-22 CRAN (R 3.4.3)                  
    -#>  tidyr         0.8.1       2018-05-18 CRAN (R 3.4.4)                  
    -#>  tidyselect    0.2.4       2018-02-26 CRAN (R 3.4.3)                  
    -#>  viridisLite   0.3.0       2018-02-01 CRAN (R 3.4.3)                  
    -#>  withr         2.1.2       2018-03-29 Github (jimhester/withr@79d7b0d)
    -#>  xml2          1.2.0       2018-01-24 CRAN (R 3.4.3)                  
    -#>  yaml          2.1.19      2018-05-01 CRAN (R 3.4.4)
    -

    -

    Report rendered by wibeasley at 2018-07-11, 11:04 -0500 in 5 seconds.

    +#> rlang 0.2.1 2018-05-30 CRAN (R 3.5.0) +#> rmarkdown 1.10 2018-06-11 CRAN (R 3.5.0) +#> roxygen2 6.1.0 2018-07-27 CRAN (R 3.5.1) +#> rprojroot 1.3-2 2018-01-03 CRAN (R 3.5.0) +#> rstudioapi 0.7 2017-09-07 CRAN (R 3.5.0) +#> rvest 0.3.2 2016-06-17 CRAN (R 3.5.0) +#> scales 1.0.0 2018-08-09 CRAN (R 3.5.1) +#> sessioninfo 1.0.0 2017-06-21 CRAN (R 3.5.0) +#> stringi 1.2.4 2018-07-20 CRAN (R 3.5.1) +#> stringr 1.3.1 2018-05-10 CRAN (R 3.5.0) +#> testthat 2.0.0 2017-12-13 CRAN (R 3.5.0) +#> tibble 1.4.2 2018-01-22 CRAN (R 3.5.0) +#> tidyr 0.8.1 2018-05-18 CRAN (R 3.5.0) +#> tidyselect 0.2.4 2018-02-26 CRAN (R 3.5.0) +#> viridisLite 0.3.0 2018-02-01 CRAN (R 3.5.0) +#> withr 2.1.2 2018-03-15 CRAN (R 3.5.0) +#> xml2 1.2.0 2018-01-24 CRAN (R 3.5.0) +#> yaml 2.2.0 2018-07-25 CRAN (R 3.5.1)
    +
    +

    Report rendered by Will at 2018-08-10, 17:43 -0500 in 2 seconds.

    From d34660a19408824e310e72dcc6efd0da88209f1a Mon Sep 17 00:00:00 2001 From: Will Beasley Date: Fri, 10 Aug 2018 17:50:43 -0500 Subject: [PATCH 3/6] 'UTF-8' in description file https://stackoverflow.com/questions/51694929/waring-about-utf-8-with-roxygen2 --- DESCRIPTION | 1 + 1 file changed, 1 insertion(+) diff --git a/DESCRIPTION b/DESCRIPTION index b5cb6049..79758a8a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -57,5 +57,6 @@ Remotes: License: GPL-2 LazyData: TRUE VignetteBuilder: knitr +Encoding: UTF-8 RoxygenNote: 6.1.0 Roxygen: list(markdown = TRUE) From 3ace787e89b3ab7a53a7f76ce2d10ccc6ba9c4ea Mon Sep 17 00:00:00 2001 From: Will Beasley Date: Fri, 10 Aug 2018 18:34:44 -0500 Subject: [PATCH 4/6] replace RODBC in security vignette closes #232 --- docs/articles/BasicREDCapROperations.html | 22 ++++----- docs/articles/SecurityDatabase.html | 38 +++++++-------- .../articles/advanced-redcapr-operations.html | 4 +- docs/reference/kernel_api.html | 2 +- documentation-peek.pdf | Bin 247610 -> 249160 bytes inst/doc/BasicREDCapROperations.html | 31 ++++++------ inst/doc/SecurityDatabase.Rmd | 44 +++++++++--------- inst/doc/SecurityDatabase.html | 38 +++++++-------- inst/doc/advanced-redcapr-operations.html | 7 ++- vignettes/SecurityDatabase.Rmd | 44 +++++++++--------- 10 files changed, 118 insertions(+), 112 deletions(-) diff --git a/docs/articles/BasicREDCapROperations.html b/docs/articles/BasicREDCapROperations.html index d23f0884..cd4c1b01 100644 --- a/docs/articles/BasicREDCapROperations.html +++ b/docs/articles/BasicREDCapROperations.html @@ -127,7 +127,7 @@

    ds_all_rows_all_fields <- redcap_read(redcap_uri=uri, token=token)$data

    The data dictionary describing 16 fields was read from REDCap in 0.4 seconds.  The http status code was 200.
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-08-10 17:44:07.
    +
    Starting to read 5 records  at 2018-08-10 17:58:25.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    5 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    @@ -182,9 +182,9 @@

    )$data
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    2 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 2 records  at 2018-08-10 17:44:09.
    +
    Starting to read 2 records  at 2018-08-10 17:58:27.
    Reading batch 1 of 1, with subjects 1 through 3 (ie, 2 unique subject records).
    -
    2 records and 24 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    +
    2 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    2 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 2 records  at 2018-08-10 17:44:10.
    +
    Starting to read 2 records  at 2018-08-10 17:58:28.
    Reading batch 1 of 1, with subjects 1 through 3 (ie, 2 unique subject records).
    2 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    @@ -227,7 +227,7 @@

    )$data
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-08-10 17:44:11.
    +
    Starting to read 5 records  at 2018-08-10 17:58:29.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-08-10 17:44:12.
    +
    Starting to read 5 records  at 2018-08-10 17:58:30.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    @@ -264,7 +264,7 @@

    )$data
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-08-10 17:44:13.
    +
    Starting to read 5 records  at 2018-08-10 17:58:31.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    @@ -292,7 +292,7 @@

    )$data
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    2 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 2 records  at 2018-08-10 17:44:14.
    +
    Starting to read 2 records  at 2018-08-10 17:58:33.
    Reading batch 1 of 1, with subjects 3 through 5 (ie, 2 unique subject records).
    2 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    @@ -336,7 +336,7 @@

    )
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-08-10 17:44:16.
    +
    Starting to read 5 records  at 2018-08-10 17:58:34.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    @@ -373,7 +373,7 @@

    [1] "" $elapsed_seconds -[1] 1.156543 +[1] 1.17251
    @@ -444,7 +444,7 @@

    withr 2.1.2 2018-03-15 CRAN (R 3.5.0) xml2 1.2.0 2018-01-24 CRAN (R 3.5.0) yaml 2.2.0 2018-07-25 CRAN (R 3.5.1) -

    Report rendered by Will at 2018-08-10, 17:44 -0500 in 11 seconds.

    +

    Report rendered by Will at 2018-08-10, 17:58 -0500 in 11 seconds.

    diff --git a/docs/articles/SecurityDatabase.html b/docs/articles/SecurityDatabase.html index 1fea59ed..1461e761 100644 --- a/docs/articles/SecurityDatabase.html +++ b/docs/articles/SecurityDatabase.html @@ -281,7 +281,7 @@

    # ---- load-packages ----------------------------------------------------------- library(magrittr) -requireNamespace("RODBC") +requireNamespace("odbc") requireNamespace("dplyr") requireNamespace("readr") requireNamespace("tibble") @@ -313,15 +313,15 @@

    # ---- load-data --------------------------------------------------------------- # Load the credentials from the first instance. -channel <- RODBC::odbcConnect("redcap-production") # odbcGetInfo(channel) -ds_prod <- RODBC::sqlQuery(channel, query=sql, stringsAsFactors=F) -RODBC::odbcClose(channel); rm(channel) +channel <- DBI::dbConnect(odbc::odbc(), dsn="redcap-production") +ds_prod <- DBI::dbGetQuery(channel, sql) +DBI::dbDisconnect(channel); rm(channel) # Load the credentials from the second instance. # Duplicate or remove this block, dependending on the number of instances. -channel <- RODBC::odbcConnect("redcap-dev") # odbcGetInfo(channel) -ds_dev <- RODBC::sqlQuery(channel, query=sql, stringsAsFactors=F) -RODBC::odbcClose(channel); rm(channel, sql) +channel <- DBI::dbConnect(odbc::odbc(), dsn="redcap-dev") +ds_dev <- DBI::dbGetQuery(channel, sql) +DBI::dbDisconnect(channel); rm(channel) # Assert these variables contain valid datasets (instead of a character error message), and # that at least some rows were returned. @@ -384,19 +384,19 @@

    # ---- upload-to-db-credential ------------------------------------------------------------ -#Upload to SQL Server through ODBC. -(start_time <- Sys.time()) +if( !require(OuhscMunge) ) + stop('The `OuhscMunge` package needs to be installed with `devtools::install_github("OuhscBbmc/OuhscMunge")`.') -db_table <- "redcap_private.tbl_credential" -channel <- RODBC::odbcConnect("auxiliary_security") #getSqlTypeInfo("Microsoft SQL Server") #;odbcGetInfo(channel) - -column_info <- RODBC::sqlColumns(channel, db_table) -var_types <- as.character(column_info$TYPE_NAME) -names(var_types) <- as.character(column_info$COLUMN_NAME) #var_types - -RODBC::sqlClear(channel, db_table) -RODBC::sqlSave(channel, ds_slim, db_table, append=TRUE, rownames=FALSE, fast=TRUE, varTypes=var_types) -RODBC::odbcClose(channel); rm(channel) +OuhscMunge::upload_sqls_odbc( + d = ds_slim, + schema_name = "redcap_private", + table_name = "tbl_credential", + dsn_name = "auxiliary_security", + create_table = FALSE, + clear_table = TRUE, + transaction = TRUE, + verbose = TRUE +) (elapsed_duration <- Sys.time() - start_time) #0.6026149 secs 2016-08-29. rm(db_table, column_info, var_types, start_time, elapsed_duration) diff --git a/docs/articles/advanced-redcapr-operations.html b/docs/articles/advanced-redcapr-operations.html index 9add1d1e..c166c010 100644 --- a/docs/articles/advanced-redcapr-operations.html +++ b/docs/articles/advanced-redcapr-operations.html @@ -230,7 +230,7 @@

    config_options = config_options )$data } -
    #> 5 records and 24 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    +
    #> 5 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.

    Force the connection to use SSL=3 (which is not preferred, and possibly insecure).

    config_options <- list(sslversion=3)
     ds_ssl_3 <- redcap_read_oneshot(
    @@ -316,7 +316,7 @@ 

    #> withr 2.1.2 2018-03-15 CRAN (R 3.5.0) #> xml2 1.2.0 2018-01-24 CRAN (R 3.5.0) #> yaml 2.2.0 2018-07-25 CRAN (R 3.5.1)

    -

    Report rendered by Will at 2018-08-10, 17:44 -0500 in 3 seconds.

    +

    Report rendered by Will at 2018-08-10, 17:58 -0500 in 3 seconds.

    diff --git a/docs/reference/kernel_api.html b/docs/reference/kernel_api.html index 70b2e1ff..7a8bc9bf 100644 --- a/docs/reference/kernel_api.html +++ b/docs/reference/kernel_api.html @@ -186,7 +186,7 @@

    Examp # Consume the results in a few different ways. kernel$result
    #> Response [https://bbmc.ouhsc.edu/redcap/api/] -#> Date: 2018-08-10 22:43 +#> Date: 2018-08-10 22:58 #> Status: 200 #> Content-Type: text/csv; charset=utf-8 #> Size: 557 B diff --git a/documentation-peek.pdf b/documentation-peek.pdf index 3f40a607dd0aa0d3f5fb5134bf0b8678a9b568bd..a3d6606ad2d1533830ce1cdf54e6e62944666194 100644 GIT binary patch delta 110595 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 m*kh+-a18Ygh0$gn- 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

    $XA_fl`+$()3m<;e-O-wU}LLr`#Mo`^(Sj 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 delta 109920 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

    u>s;6@v7a^}o7P9vG@B%D070Z?(yt3ooAd z9->uJB(@ZhFBU~h{f!W;r-|3=`k*oMLyz;hWe1+3{bS?u`TElMdpj+Z-qxlH5;^;q zt!kHZU5M`=4~bBQhBdz2IDP}kdhns8k@yvyB-Hr+Z}`>i>!=%OOmyB7jh9ca9tsKlteP35`KpqnID^W*TK z=y;;SfM5 zN|*%>CKhLHYbr%WDkpEpGq4B(H#_e?7!;_$P%vZRAiM@)D*P z1WN%xu&ji;GY}3C+;V{_1PX=b`=0?w5D%i!VEZ2fL;!!B95)J$1YtcN_6#qB2=^=t zL!;o}KqM3+1JLUrfu~r0fKx^usKimQVz3w}94*6#grUGu7(NV`n!qr~e<5HVpj$?s z2h53(;2!*sDGDtEVoub5m=??M01>kCTqqb6E`#O6pk$zEFlho)FgOad#DJLx@(&2t zlf?kPbZBPAH1C(8i{$z5m0fY}xX1HxEvRUp*m!+>xd z2YjVN0EV)xpamz;B`?p51ap)#Sc{ecK|KiOQ7|w^gCqGc;2y!T7(nF`0*I5B10odU zQWYqe(O_1D0dExv1M^@I^rOL?6b&v83G^s(0-g%2fSe+@Km`gGG?*b_K*vb%j?rgX zE0~v}&*ql~9}fvYt0)N^Da!G{G2q^vT?W^2mh&KBFfdyNvv@F11-J5S`W{6j01y6? zdZ3VCx(+)_lhIHt7S4ylo;@$n8r&=l_J5rZ(<6XlJ31mbjsQu5h%>^$q~JVXEAyl3`IVjx3-{y(h$|Evgni}EZWjO<{d4}4Fg3qbag^r@+dp=`Uy&YFaaa(z|1O*J zL%lr{xlr|*Zhr^cOB>rLh-RB^d9eH3%*P-5KRdHIlfIG6!HB{s#FcIUw$}=@|hZXmm9|&C zM31}o`!zUg_~!-8^IBA_W@)tsaWGydT5vZ=BynLbalcyD5TDrgXjokbJ)F5eLh$`jI_FXo9W&>x+e zf^gffa!@L-Jd6)eV{CrQPnSs%Z0Guj`up=i2McPoM%+n0Td>Zck^}9GMXto}7KgO; z_X~skTuaqmbfujils+}}@uOm(AIXW@VwEixZYc|#%fGccm0xtA$%brhYkE3H~SBJL4_e}3YHvA&s@|tw=#cw98(NG~{2nT7}=`S(1VMvdh?S8vSl5)qbaz2p=^i~lKOe>i3 zm!adhM8BlV?Y8fEqX8i-X>}WEqh)6 zu$fYCG?W(^mz3|LHpx|gt|NHvFy0(}W_TnlK{G)7j#hywAXw3~JKBf&iJ$`yKb`pR zF|pwp{op>qtVp{r$>iqcAN$Xz;G1EWZZGQ3waxA|1liwe7C<+WSo|kyGM)oy)DKyl2U1$BGanpn2;>^~giNQnW*0Z_Xa({m327@EV=? zEJSfZKZK zfuFR}+z61Kgk#Zs2oT>O;AgQGgex!{NHl@e?H>>_OwSD@UlIjMwHScAXfbXOEP;fb z3>ZAoPy~pGkRX%*LnY_|#1MZFk+RNJfZL1qOf}_8<42Wc-A1t zfCvBs-dB^Z(BC!+2gPCFz>hy|CSnkl04YU`fY#-+!T)YYUj92CjN)gD0BSD(8BdBj zyT!k(+wZ?70Q4#u1Opl{0!n)He%XI^3`p~lLqz`adF()q9wRW6BTS3{QKZpr2osSEmS6{mh~q7iW-xSN z^Vj)Nv?-8miPZcDp}PJMjp_Vsuj{FNPlTt<1a%*1Z@eE_YW{18!-uTH~NFLM4r+Ndgez#tx2i0q_@P%l)GoFR}>%Fd!cRazj`(HF0zwPLC znBNbK45H+Epeh(b)WX12II!1r=VT2%Z*6nU@s==i)!6!7Ql;`u=ac%9TFd!@F52q* zF4Tr{yVnO;XcP&m^?JhFLO-5pgm2v?DU=LNzGY<{Uv!mO0Dm7HCb^X|fL~#Ac#jle zE5xfr3;?CW~T*vYzuGPL4|++D%&9) zM^6a=?7s{Iwr?yQ37jHd3<;l`hS-e`SvNqLs0id&K0ctZMaUg32@kVs;RUh~X`hL| z+c77{5A$hCCdS-_CC1LJ(snHJ%|DJ$$a=Tyd|U4Jw^hOF+g7W`J!%76yI((Vk53&U zb5P+iCsf=;ciEG5roMk3CA9b3F+LQ`68V_OKNrH^BYL4eQFmu~%14X3)Q$M2odH)80O8`TKY+_K8KPFzSDd1CP=lTH>@{1$-tpxH`~ z@QBM1v%ZouR^l_7!$@Y)6gJFcqN;`;H_NQRhfJ6oDFjFEyg#?7b>hv(_XDc8@ivf8 zy#kZZ9=F-$QD_u?T-HEs{5gm?&&5UX4aaIou?PvN+h$AOg;oxks!jfYMmqx1CgVMn zHT@+c%L}hks=E(f<{Dr3#)rRsOMUyKbCO4Mge7WniihO`T3X7DfBQpicU!~52=3O| z`z?dB{7wcD@|-qK`99tkP~rNvDz7KayUipw&3a$vSe>`CokqV=On5GAe~I8SmfE@-9(1TdpZ-6p4(jJveu+;O1p zN1Eq?#dt}_8n0Y-AU>Z@{B)u&p&255>w9KOT2$D@Pc`sX#E477P5fH+Ek(g_eCvGm z<#N{rUM!bGXDUh&$MP*$m&hY*fWa;Du+vX0?z$JZ zG!wPC)N^+Vsja;`ittSHR@%Q_o1A;7#&$0{S?&|l&mRsM40ILD2BoJ}GezOg`kO7f zeg?CiT$EG1Y@|JfJbhp>U(8(FuQ7M~er3J*m~*7++P2RfJKd&B>dR$E3z!iWS>elh zZHz%uMlN;3k<};FqAGS{G5#qAKaUEvLgR@H{NwZv=UZ=IkmM=AKRie?wCSFX-nq}8 zNS~;wKB{WeA8a-$)PNR=dV5CW&ZF1y(-y7{61UttD8; z&||1>95b=VIN%-``62}BeG%%yA9n?V@U1FWER>6#;m%)@oLKsbT| z*%K7_3j-^+2r%x$!2&G`tbW44>bD{>I01p*9HPSm)|L@qNfiNJkpV07XSL_E>>aF_ zL1hsCb_{5lFcEwiAu?QGF&7K6BXAHUfdmI10%UejApZgfYn^DY>dyKHD}hS^N}?Gc zZ~|N!A`T>)GZOM(kbj|>{(rT|P+)Zrjsp(Oh5lUqpLVzdh+wWV{HqVdNir}1V$K9a zU#0)scg9zM#6$H};XiFsB>vbDg4T^|BI{Rc zDpHGvY#YD0?MfYy;cE3v^5xSsUHs_<=~OALfTf$n(dYOzfSCECq4Y@$*~ufXZy~ZF z2N}i}RW5ys8oIeimq34@c99BBRkD5=xxSCP`AQ#;KbSZSz`Td47^x~JeB2up4jRzt zjc1=(asN#*qdR?LrwC732*0NBD3-NULvTHQowRkd>SIXrhn}~z%irJ2XM2%UZQYKj zy7=+Xh~~%k5He5|!iVI%%@7-tUo!Ks@}aHUV0ZFc3ZtY3IntPBVQCMub<+*~mUBtZ zy4y+PL>au`wAyBM`n$S<9$!L&X2;eXI%^YLoWrs|NFYx-G=z=tNl+J6hB#lQb|Ga* zLh-1$hsdUdjq^|Ma~=y3a}oH*S9gND6sZpkj6XvbaJ4ii!}g8ST#=g>MMb{hmtPJ; zPV0Uezoq!$LIIt8#7YcPxK90R=MlD|FKxW#PTDkERF|AWN-I|~sciR0O1p)f%-Hm) zyCz9baBQ$&c28@P@UI*9A&!Bc`yTzcx(%U@xgXu-+2TvQD*R06P6s4oH%Y!Q?5=~p zcKK5`G5kwKo^=g7laRqo$wu9KUr*_1YRMOg*Vkq>_@B94s~pO{vKwQsRifq`a@uZm ziK16`icJQ)qhS&kG>#M*UZ2ZGJ<+UY|=sb27y-aWJDjPhU^LFBjUwfCKSpHMB zRHEpt;k9yo^ExLZxL77zEwY1>YH6h;Pb_*w_^HAEDz}vn74a&hla`&l9Ij1s`rs=C z+l!5b23o5EGZR$_;tM>s@*m$*rCuZ{5V~(bSR$o;DpahS7Zd5Yc6eRH zdEn~ch-x+3eEk3q(a;=I5(=`6^~{**TzBSkIc^Kr-Q#4n_ieu79#O*iWhgY>a82pQ z5ev*D{$AnFJ0YiEjqP~TvbTGgorEWJJWGXci5rla1SyslmUxdpWWRcBU#I(Q!6#zF zPxe?*Kvv|^hoxhbx}vat6Hn-`xEz`=61w{$HU7_DRB}J2!!sR;7cvjUyn9R@YI^wY zl#kq0)zv$cs7BS3^}AQ2O7xKIC+-8jyuh6L)eaIr8&!I73pmpkYlOCi(aLJA9ucOV zT?RAQ`bzdSb#$EO>nlO<9SWZXZWY0|sgs&BoX{&NKB$#Z;PcJ&e*V@OQ*FP7)`#;m zebjyNzv!D^RN@Cp-hXMJ$oiqcq(t)yCxAQx@z9;-&<#0@^WA=#%3s{GpxfSpF4t-w%uBGN|ZG`o{#b zA91wWv<%yG`0ul2mEHV{*6X!)8`%36wphLzHzKQ}EpSo5}C`w%KZUr>=FiDtED^?uhoYCwkIrv=6 z2`IYOr*TDj?}i|9%i-Ju&c{jMgR&J%{GRQqKI`2D<-!H?2 zFueZttjPl(oz|20E@-Nqn8}ZVa68uijbG8Xnfww|r(D*CWalBZlTGffXKcG)7^|(` zc}40$i~5hS4kfSc-;>nQbi8}-3zEpE6?g`E47``vH4Kt~_+{t!kJxUN=O~6-B}W8s z5S_~zQ`+k=WBESM~fcJaHx?B0wI3YNX$Vv;e`57I^D%nF9rq23U~1hJ(Qu zEra4ifvPr;FNb46#nA+W{Ve1X+BG2ppdRb2{GvmEYC$Mi;D1zhAV@gVGl9Z2I0n=) zq5cvYcgaWyblMPdf~OXQ|7=+J3`7TbpIM3k!l`sb zh<|B-Z0!GPfXFadI2itca0ezJ*Pi~5Xomy?GX20?d*MGVax^G<0s}Un?jUs5!9;=t zR|DMeVFcJ582+P&2}lEb;Ey28q+lHa4DjvX5U|Na2FeG~pwx`9_s<=kb%}!;29|#Q zi&8*&K_gB4qK!)Pim+SnF#LmQUsnn3c30gOs=nHevg>Ek}R4JoH;;*b<+P*Wz)J>~FjfvclfLJc!+pjO^I9_V(&*;|omS)m_=fmL&q=*OrZ* zhRt|gFpp9~&zZ|4lpANZYNG*@u{%4_29XbpYAyK4_3l1ofcvh9T$c$>X(k{7CsPjwO*g>vqAS5~Rt2)uD4{29_VJx;RvE0?h4 zg@-o?Gbb)^9aZ<;qYF`0%8qpz(L@saMt$zU#F~we_IO_V(^yU2WH02d6)>an^y8@XA##?g> zNd*?V7q3lRYFq%;4f|K2d3kv8#V@Oh3oC6@ny>F-Bu`BpFV$SqIFGM!hDM^}zY2z1 z*|4nMKT)$-P7{edcGd4PYJ*vQB^QgEub#gVa~uLNCc|NrEx}}t92_6b;~$L(@@u>A zR5~%I#}|=X{$%lM8G>H-t zYqw*}REwhQO^e-_I~mGx;PQ*Rlp?_%nzepd=bC-~P}+lveg zSIiz)9v07`y}7@0oY36;$@N8i=sEu#F|9FDYGb3NNS=AtyXNEgz1Jld4exZ!${hs* z${QES-`t|%%HHo`D$^x?^!kJpGvRv0-`u~mJ71gkLS@xgvem>%zm*Fbsip`0gV@B( zwYR_^9_YSAGWBLQqia^G(2;A^qaBr592&sttX1EKE$y}N7i=s|TbxevcxkvoUpBcw zX=+6X#J~vwFj6!8A*zB&(ZXt;a`VL6g*7FYEKUwWd9RxD(-*HG=eIKF4^=BQ%is}o zo7W1=IcwI=Ate&-L~yy>f0QMmPC3cm%Y}EKd~S0wbGCF_g{}$OGxJL{_GEWCQnrG% z?7jR1aiWCathp3JWY;Yt46}dcch^XANX%h)7H38EQ+l7MhELEAa#bP^irx zzTf_4ud=AFr6;K@nf`e)Cf>Ka;0ro^^Ub?SNSv_dRrM4}_W}#YxKP^3n^%S?{Byh~ z)nyDejng(2@UJbf@qRhq;4K*_aLEKv5Eiuy0iT@ zWqSL}T;0>+wXkuEPvxG+57h?(P_YlU1um&){S zVLrqQZf(5buNQRewYJJ!>kF0wdAD?{O}pVfC)c`{{YO`wiyqpSd_Reyk*z_*URFq? z<+*SX6JW)ZGxm6F=a&psrv7K?xBceX#;rPelzX#z-=%`|=^h!*&-v(zcpg3`4K3j7 z=NEHK5VRBVH|=P^!FGey#PXTLHlF5%?sB%ww6eA0+bR@TY1&uqwoJ31@zjtTi=A(@ zOX}5M>nWiS7ZA2sKPc8itsa_b+swyv+GniO9bASdMOP_1VXdTA}rB$3$N-+Bfa*-!z9wOf}TIv;n^H<+p?*@(=IuFV0F~*_ChT*V_qv8)js+?>F7c8 zF9Y-J`Rt8_>rP#xd4R7w69m)%oPAkcAo{sTjyfUi|jiU?4`@dsV0czgTY^yjkz zb<%%pIA=QgKTHXz!}$CA_X7k_NByULCQboD+`0ac7r<~3ITHfZL%`33)*u%G{($lX z7#x6masVX3l0bsvdBWUVh$0sPhXR%RAQ1v;BH$pEf&_&mp!5M`Jzz4xVu&JO;dP$i z~x0YPyo!6 z6R@2U1jLHy&j!Kk!~selMsPF3$w1CU>+DY;PnVGlWI~W|1fb+C_@5pAK0<#xsKF}M znXuzbl}%{3BIY0i_1@tAoQ;4|9|9m=Fppw6eMQAeFRA%M(`Vg|8fXQJI<&ZoQw=063Y9ph5wOckb&G7 z5`0(?Z(bnh`311Zbf&-f2a^M`J!eb~34?2)~R{z@7uvL(^|f{hImrN$$a3QQsYg>t&z~`UdElU7S>)lM=vSIP%N&A>#~06 ze3ndIDYhPp%>d;MC3kyf9-x2>^M@Y*6l4dwo$vV6XP4*tXcHXnC&?sSb)GxFjC z=Dy||cQsM{Jq_f(S|oJ|33I;Rp3rP;@jEI!aX|{-(Z@XHY5sbjiPG-3-66GVPs5Iu zU>g7gnRxfu;6jp;35JhKyPsdqdc?%#kGEvm94#tm#%pXoLTG--)YJ)R?4i7fzJiis z62$Ziy|f@4#`W!HIX-w`F`1aTF301iH!x(gs$-jj=CrFetezBK;r?+hqutB(CSNt5 zt>tWhwF^r$m7^ZREG%C>v?%(yq)lQdYALQotWOTz>|rF_8<Yi5h&iU9*Hr@Poht`#y{+&!9+^}^R& zKjN8J9;3z&wW_?(7rOa>eLwiwx<$V9;zvmv=XA1fAM-`M`G@u23=S8hYy3tKkcfe8 ztogmgk7^BE1%l{Du71H@>V*S$c5->opJv-~>73AAr6}Fr{Hz@y9K~t7`e2(5&+nGT zCNY3hA+n^RT-OXuqKO*1cPjW};+Z8tAEUl=LGcQZb;s3JkEdV6$r^~{%$0hH4Hh0wsT+R2 zzuF709HONa{$18CbIE*nVUKKAL)?8acn9Vlg?qWakIA&Edk3&^Mld?8RJ6mHiEl~_GOM=JQ_rd)NqIyt!`ZEzK79o zIuP<2VGG?_UzaAYOXe4}2IBSm%w!gE-CT!0+}5_4-KKiJ;w=TQBBcC(WZtI^UE8}J zv{4v2Q}E(zA+{m!r(4zb0|xV)qyF>47UF=9BJXRYO7yWe&v%FC;vU*Vl5U8jpz6f~ z#VfCD^l$%8qv}^ZF3YAj;3l5*e6g38`fxBG+*(NG&)KPw1kvtP&KA7zejX<87V*V| znb)R385B>RG?@@bw*naLgLPVQ|tE>#0Y~Q1NrD#bn#1&m1 zp*q;~`*85cLXygZP0c)zUehrBMIpzH+J#@O>z{LUcZ&~i6Rty{(9 zm0?;`Ya>oE0@I~c7dA@LADFf?7Pm?E!eQ5-Xd(aO)lYn< zZ)zJ!sx5gbf8alhYXZ6)8smdur(d*n>bd0hk8i+i+dh2>I-KR)NXihze_QSy|Ge@u zR9mZ*(1YR`WD#6Cv7zfJ5c=LK`lP@@@pvvpe)#HXvZ+Qhl$XPdO8D2>vXV3J&^$T5 zgC+3O8ykiz$rVS&LNZ^s?Ub8CQ)lR#Q5|}nd%B$&ciID7;=b>*}7$lOQviu174F zV^beZFJ}dO6wR{~F5Ba`g7+f2SCf|q@nSqK;oNr%;rB)(-tH6o|1fzFyCXS+mNwlN zaEy7Plknuu2fZ1np1pL=H=djd+4c6}>;$2`hZ}Rb{weq* z-rUoTWi9+V@}j={*GlHzT`t?UWqdu6ecy~xpl!8BhurZJ14w-US_&1|QQU4UX7U-bmawKCmk6HL4 zR#2S^BQ49(#q|cdC?Da=aT|3x<{W;0;vrQzj=@-z1^A&neJWQD&GSVZ{f^+oV;d)= z$T!Do=tfoYf^6vTD9JO7@FnCzsJ?44!R-46gsP}B-0s+pQ;%)g#(Q@bu6pT`jP;cc z;MSva$CJOImA`PomU##(k$(`3F4oVe^HVMQO%e~W`dyZYMbm~Y#%VJklFG(F_a z@tpJblxIWY^I~0sEMeYe<ziJaFbt!Co`?WoYA3Qqx?0r0X`X+lSx1>w^V(3pZ**6KX!QI&xh`r&x zCh_J(tzs5alO_3#RCCcZon>?jw$7{Fv2`k}=6m$xNwG+;f$tv?=u53a8x@Ta%_KQE zTkwC4fkOmRzXg+;GiD`Mt_cloE4(0AVMPO)isqA>G{|NJ>h#??TV{&77He|AO`1t2P(cT2I{f=Xs|>fvp-&(=tf=ayh=L z`9@lY@RQJxD}9U$=Tg#s{%g~iBF{2i4thpv_7+d?lbzNbbJT{kY`gQ^k;73}g}Ebv zU#?lIY8CC2(sP?aiQr#ZJl-s)~#TO;N~8W7QozFXGND(!56ILeV5DV zAF15mbaTh3HxKI&3;L)aqD^!ad1I(xS27Z!KA{pw=IG}*%Ii`vz%nT5Xd$%!vd291 zz63NM7nArw7TwQ4VXh?Q+eOe~1hoT6xp{{mo|mwZ$T-938m)M`>_Xz*_GCaQQahTb z*BiZkv5L%r80P9{|+c<{U%vWPi>IXK@7JEX6Z=JKk- zsi}3#POLoEDz4>8KMx&cpOJ?`1(LeO9=n4Zqbk`PlOB#r>2U%?i{uv%G<9NGiR91EK!7(i3F6H^=$kZy7LtFf!!d z0)RjMiGRFYc%B6SK(qS4(p&DogaCkK@ON6v^GCvH0M-oAe)(*pfA$>!`u%4+!n3&n zFeN-^sz7%=V4sJl$2w3s|udR>84p{O3nT7HEY4Ya)Z^C~y`B(nh zg69U@-hAvF;1}V4y8N)5d_blQMi0XV>xBPl@d)FPrbu}>)h zUNuP}=fr1s$Ybo&cfJn8R$YUKi@ED;>>`Yatl7S1c(QhrmD6yr;-lT0#upyPwG!N^OzaY`Hm9xn6c&?x3t0=}o2v%MX7 z_{BLvVH~o70+_tF$D8NtIclB5>qH*OG-HN-&CXEKDnvJ|RatR(Ad;)hSWJ9^sHmE- zdg>oCdgeMu)wLY?uMW)7UN1G&w? z&pR0YO02h8D?@wAa@g%Q=1`nk9Q{pb-mGawB4)Kw_& zW4jLMu7v_y!Lg~%z@zSzygm#MR<@0I^*BNqp_LTMBN;LH(5abP@LHj_e)9Z+f~};P z^vqHnI>uTcifl-!6z1^VHD<>>RmTH!2zyY`ctK0bx^jbk^I1d1=b?P*<**d|6;U~+ z2MhVQpz-z!2CkMocv8+|b6Wb(4ws)BP#GMbv}!kErXp1O8urV(8OoW-FBun=7twOTogjY9M;lft{?@6c4ePrmI5T6@MKCY;L-6 zq>$erTYXE)uI{6LXZ=cYoQtKEAHAcE%BrZqq&j|8NCR^ zBv5L;=7mAA)W}Cb^C-%&dGPjEVPFOJSK(d5tB3No1)Fa(G)|(nll8a zVVET?t|J$BO|};%Td+G{nRi!*Q$apAO_(~pNuSVUUt&mfd*p&0GR0)cP&8#Il3#%F zOm^|4CLS11D`!D>2~!(-lJZFuiGAG6u-iI1Y*_mY8kR!pc8W+xoNp=y#9n3=D;;@= ziQ^sk|BNaFG3cC$Ac;`e9@5ljv|=Ng4E?}E%4*VQKFr8pSt;k^#q#*#_l9IPc~P;* zee?R$kadO`ehB@(gfNrRa{r9EZ~1%ww#)aG^%%Qsc@n1}RM9@LA}vC^w2$~N1I=W~ zzJ0tW9Z_$cE!f9+t(y-yG(1rvWL>I%NJmoBPM9lTR+?Wo$2`V=xf_-o?iVq zL}=`i4VXupaz*IgspMekeR_|ibDc=!t{N_!N?uWSkj`BiWY_MkiD^=)ZD&GUc&i)% z4nHlVy%#E~#5`Kb4}1}@XJ6yv*y-wI1*_BOtM4T?COM^>+|PK;H)xLkvK<-)kqRI@L3rztUJf`}9p<+b6A zeC1!l1loDvQcxk1MhkfRIO>SxeVHNn=7$jCplVlwCrvm^7st0rhIa(Hrqv+9D915@ zu<}GdBK27R^yu@C_1WJw3>u7_@7A+?Z|UX41aKE9e`t=N8SE>v#uns}e#C|xJ`rYu z5-A}GrK4kyB7LWQ8d^S#R+%H^3ER;8MXOxAU;`8a{tkWb;rW4fx)gVHsJH=J_e+0F zz>ToHTuyEdL|MqU+??ALj{&3%E$39;HesvSW#(0LgD1bk2>jQu56Eiu$EGD8$?Wb- z8L1u>aTxaD|I(KBMGZ%TGUL<7wTf3y z@SDspl>ZK`{!L=Z%l*d&!p{3!M){8ji?sXJe)}R*9y$@3RnV61C$H$w6I{FF*B)?ae(|W z`T3~lHCSL54mNNXm>M|7DF~2VJo^)$^u*Zf5m`lG;lB%Bt`TItm zZIiG-8w6nW0LMY`a{X=A_>EkFeO5yN-I*mGaC8gabF{n@7hct_Lkrn^bu(30r~!Fy0FYbLOM-1?&>GjNI7Hamvfqk2h_@?eJ(i zCLVQ=ZQ;6%6WA%~YlA>1$C`~fJYr`UZu#29`(&)4@koVO!wXKDdhpBkFhccarmzFH zy%F<9Z~O^wxq9*6#+;k@I}!6lV2{uaOIsLXryR+(HYRbx);k&tL8ie}J8eq%RRo^g zM3i>&OAGSgSO)%hqTv#!U~x$lnw0j+tq(B;*CugysQx&XAP3QN-$JIrMNVg752gOB zW2VQF*vg{UlB;u4)~JDBt5p!+)5`Dn`Fwk7sB>baLVo~_E*g6gb0>S(J6^MVQdn25 zgHtytnjGz5xVty_RS6R&BmQWVDqeT7-=9eAW@G_k$O?sB$lKo2TSru^3Qx55H<0qiU2uvTMZynSKI8BW*MNgoF z|JYaL$VEg+Z)m(h)wV2=BeZLQ7;;@nL4!w=1hgh!27F+U%fv*4J;bNf8A^IkYYUI< zXo)@i1!U9WOwa>4mlQL?$cqdUhnAM4Jh>jfbBoQw4Gw{_wqrg<`0KOs1(sWi&mxn} zN?-pdEGdU9Rx?kPn9v<7c6PJe^U+gWJk0TElh&UPr`Fy>^M>Ik=|(&_o2rEl9_jGc zVEvKuDU2q;mR%*lF5A6`;v7v+^3|PZQwNJ$SYru|tJPfP43+<0c`s4oH#Bv`5L#^> zjAljdc^(gtw=>q-CWKMgvLbd^P3p}PcLW~8aqZA&Mm%rlCN4!!l!8d9PLEe-@zWIE zI--jt8++L)72|A*n7)My)0|8PNNQ+fk+kNnrtnf-i)?syDIC0BaB0Qkl(RC9D~%I% zAq(HFQMo)x=MOeECsO;!LGA3{i1x@t53F(sNYlQ6re3?ZZxeVueB*0&X5Q5~u~Gfb z)}0!=O>o)&Z3wpJO5x|D!rVR{8!!B^bhFFHKFB8+x6|@uA2d5DVko zYORu$2eq9*+h_w*5Z8D4qp)A@jV)0i2+dP6n*Z^@1$=RTI<|l<9C&8_RUi)_q4ok>Qp^z*OJ4=KjyHYV`&{m&GRgY`eW;Ou$OO{-H&tqmr ziQVyBSp;^RiDGg;EkKVsJ>oW0o`(x#-Ttl;s#{-tcKcqmwc&Yn7(Vzq|D0w4#Hyk> zr(c&31#m+}v1tu6f&;>v8W+7tefqROc7FH!scFiZQd#iPJ=_)%iOaQnpvt&fmIg^9 zeMTMIp9N79B(7h+-1v0)BMZ8{4(S)okTmpcy*bs|vwM5SgI0Vt(!wPlXE>rA--j_# ze|^09;k(~(2}X!Aff*BWVw$IQ8k|m|K%#QI>InCSXbv0g&Nir7^Rk=^FK?Zc3d5em z>z(^WU6od973kILeE#~#5YYOQUW%_Vs$h8%6`p8fp|T3%V#0?S(?lv-{`Qn+b&Yu~ zI}Y$I)jbhXdZUF-ycyCW$6NF9&C)~G2R7`tdDeZXUj`S}HK}v-KUP&oPgbbl3LGV> z=&;Vv7tpn&yB&l(+#s%+>9@L=Gq|VP1$Lg8U)h`<4e`7^ak1Rl*xlKq z5;!~7JKoH?`MjBpn$-mb)2-1iYJ1I>70{eHT-=lEdl-P8j6~ zcTOp@$4X-zE`MK zP>M~jIOr3<4&j`9#zb3LM%(w+>Hrc=kPh31{DD@8;hkaLC38lF#+;z2&S$36Ux{e~ z+mo0b-E;}GetGIYL9WRNr2fPqr6BKkxv8YDj#BE(7>*b0Xwt04-55e>S3Q%tB6_wE z=7&NC4atO(g1c09~bFGj0?(+71tRMm0@g$E(=tz5I=)kupAei6~D_jJb7!eOT792}# zr=i6XpG%*E+R}<~R+bNGsfr`RB|GM{#EzDR+{uxOSS;5sKN54s=;?j_^65)GSLV`d zn%nu^m2Wx_j-~rQ5k%Z!h#KsektD=Z?u|5w4mfw_g9`V{0-6i==n*#WW8cpS44E#Lsfw?B-F@$8WEhHexVXoteshpH$ph{|Z`s z)lp5WNX6!i7}8EfQ1HbZ#rc6;=11^yH|2T%Yh-wO3v(KkI7&-@lx&k9ZhrT>Eu#dc zBJ+%5Zry$$kX2(HU-H(M$-~t)SQpC4J*2R9t@V{w#v$cz+8>wcey+>P?k1<`+fq%;MI8 z)9`6+Lh@|JRSP#Oz(ui8Q+UNyq@EjPAs?JLHyOY!$P6o)`3N~;zw{ncM;L8m;h?f4 z%S>m}d?5*FWJ@+l9L#mFB__Ada#_&kn~n%daK6=1@bNLPz3S9i2rr;D1FI5@m0x(+ zi)Y2#i^imI91;&?Bp}o>We%f+e)Z^N^DOYt06|XbOr!U;)0tqCJNmoqAHy6XXj*9} zBjB&5Q3YTfmzvR*CUx;l_mhdvlG`hJn^2Nd3}9A^HM3xVaS#G6`Vs<3CIm7tZSFn7 zbn)*OQ+a;U(-0&ti7E){FcsU8Lx6ZVixgL*^YZr@q^E5~<33eHRn=`uT{TW=WArI* zfkfzPk#n`vb?-)Awej29GT<?}WT*Rbu=)LIDM>3)(?4Hy3B~us3t!h)Z4UH#o zth8ZNPJ|p7vNqjwFb;hIp=hMc53l#cuZ;SMHVhZ8lCtU3T~EI2QoRzbX_Xy6>Z3_t z7QBH-bg}_SVZtZ|DIXph4xSD=^!(i34yfU%vZvdR56^y5en_)YyxFT8h0Lr$UK_XU=I<73*Bdf-QD{Z5>auaXE7VI0y_KVx{odlsR zI0W8}hj@#aW+?QA*>5HvVAv?kH9YVnH}cJ@C+CR*nUUfe{5eWeV748@NY}--$DM2~ zTHmFy{gQnjH#j|KOB)i}&Qy1dwZF4i-_)eBXxOZ0UsF}4?rdc9$ivsQ8Jm@UVhKuY zrsg2^S_^FC^#pHC+hg`8T7r4D0D=vRaZ`~iA#rdx0KwUR#7RI_HHpgn=6{s_0ii#z z?{iiEub+PuZ9sbY*Y@uu7x?^7i{KxsEWT&$7}$}34NO*qfXfZ|?zw>wjT1;*xwrtn z5P$*Y<^;q6kYAM`gO3)3i2+3x zFBgEqc}6$^ZG`6%{xda!4M?x~_)>GFAjtp|BPm$C5c4@%W&j&}hsS&7lX3wq3yx=< z9q?!T7sJ#Zy!ZXtV))Dt0+W_tf-!Wk;duZ+Djxu-0vA*KV_Ttw@_8GYn+HHJ72|

    |#oPaa)zhY1T=JSs>2gmQ-xt_&z|0`q#AQ1n(JK#(NP`AJ7L~(VfFhCps z_gM(pfl2`v&<*;pivmcaCE91D+B4Mj|1xY|u>NO&I{_FD_*l9AG91V|uW)0wA81u) z1@UQ+px)4E902}oySnXB3!E=k=q1|tm>C<9RS<<{+w&y>a8~an2ICZuM4=Ek31iTV zgjv77*8X~TznFROc~tV7PAOQ*(9B?6`xSXCse~pG# z+A~ zaoam@u+SL|&V{D|dh}|vG?hAz?Yj4GO&qkUEvJLau0=U&n1uG!V~=0j!6AgyUE5p)yJyb>&L$-&XxH4mbw>$=Uu z5YK_O;1LhuC>X5zwybzm+wbIL8&>wF7|YJZEZW{nS1w7+1dm#f^V8liQO7VGaz(P3 zrCa(O;fcc$NJ?q1dEIGSwPs*zbW*9>WWNuZ_No{Um9|~e{dflMuo` zp+=Q8T`_J6G#3Zi-b!O)LjGFm4fixDWMq>e9zRzZ1!;;5)Rs*|^F$;1ucI7=Ns24k zHkt55SAkZ(S}4W_TZ7WLLadEE`|$sO@gGBf;doueO*4^bSCCa`rwa(={& zr7nM9skx>se|`AKga-fi7qy@2=bO)m$5@}VDpxfWtn!qKBBZ?)ptFkCW{+3(5c$8* z*PIM5V|t$j%F#S#dtB9=@H@3H%A}wwE(%xVPq=|xbiY#V*CEdIguV}tHSVWDC_p(H zx+PU?-quj_Mkx80|2JF1*Zh+`?-wb^Xr zUz|*8K+T26JB8$jTd_?zo%Z?9&!IDhw zOmdHOP83$>cZc)q;ToM(bnXwFHOI+TcCejUyr+3aIvrV$_k-SQ$tO&vO^#P|j?AP! zAS$vhU}0+~$gyF?dCE+oM$c?XRtXWBqpG_Kx$PU>w^#>81{cE&>lo88iBg?HD^dxO z-DMmqI*r7yZq$P@SbU!t0t_hMb!X_pKYj1FAl?#77dh5e6Vra56R;Zg5iQ&Y`Vi3& zwEuPkyzoM7G}v8XtMyPl_9g{+D;-%K6bYLpxCt?d7mqq^9C^_SC6h}?zz8??dW@|3 zLQHF_mLMpJa4PE!wY$wPdAnRWE05gVi)@9cI>Iy+6GY!GucW=WI^paCZNoDCNDbAD z*P=ut?_^vX*;Fp+IltqGud})yNtVhPzPTl>uj|z8&`;yVkybxjANWoVUK*o7Gp~f4V5csLvRmo5(!LYc}WCOCaN@2 zudf4ff(?tW_0#doA(4Y171k@c;7ZWKNl?XZ_AcU9eZ%{!!_JkTBa^K&vWZ*H0#h!c zjD28KD17MN9xpMAZ1AnervcxQO`~w4g4cAhK#H!e=oo zMs8?>Oddc*a5R#aOyd%_@I8fBgf9_5C$6EvVOSm!n2g`d&sf2fx>i1AN;@FGyVIvf zAFs2WOvHANyxvpuo1{ePlRk6eGsvgHNbvk^KAc-vfQt_7n4c_s$+gb#=g4Cnu2lbY z>bye)yzV9s)imE9x@(rV3EPeLSJQz%qK@v3+?=i1PWi9m1-oYrFg1HIHH2Hu;MY*I zJyahRbs*_$%LE4!U0?Y3kj;WF&{ngw=Cw%OI3hCqUQ@3PwDMzRr{ai+I;xS>YHok; zzrNf{_ac007LhsveXB%Hu8_td+#!RuV<+gm(d%)g5ApIk^MQ#B3m$Bb(0aVA;QM3x zQ8Jbu1kacmU~UIy8#5>J+En`W%gSg@InRzn!6>TUI*;T5E2(_A578X!KOqks?0>;D zIRKUZe+GJN&w(B^2RlGqfP*AYi38>qq<_W)s8hLG*Z@1}6)O(_Q2b2!%x(aRT(AHm zo{Jp}Q}=tJ+5{UGfa?R`M6o{$&x&|%AdCV;>vdRQ{#L+~`J03Ie~Z|d{~3w=*?}Io z(*F!}02}K+BbU^wct~8NXEeiK{z~w2AHlP-{`p%z!w!Ep1%N>07vSOihywn!^bb@* zsxdAk%71z$nV>khpVjt2Hv)qMP+ok(1BMN8U#uFDQ^8t5*DwJHg>&(FUf9Uo&Z&!1 zBpBU%yH~Y^394C#2kssT%zWeIueR=+K7Gt-AlenbT#GZ6?0V5i}RXg8TVm>Ha0R47L=p`ek{uP z^#dX}O_Y|r`d}$9Zu#qjV!_4xzRzt|ac^GQ-^L2ORAi)p8MQUP!4@xW=JnWU_3EKl zoMLxq2-%Z9T!>ialX{C8V}WYpoj0h7*?zw3hoUvE?^@R2j5BcRt-<25O}qDj6KHNiP#*T z84fEl%a62FvfAq>zFwsYe3aikM&cpq{b`(j&|#!2e`ymx)^UVSXCkLcQ@`Ruq<6 z&_jlzVE-eFuJq$v8=X3?3%`dvyY5QN69IXy3sYT(s1wn00IBZ2+r{^_8g^co9qw4l zcO`EcQ^Mkjy=pPG=NczJ&^idXDz>b?!w#?u2xB55Uq)MR4iM~+{;}i~ymai&I9XzU z7;_5~W}3eBel!@a@&@$vynkC0gz@EOM{Furj&e^=9!fh;Ymy1}lewr#|NN?6z`H~x zeai0v$R#kbDWQZLNX;9`vG!ua7a5R@<&vgs(G=r?Na&xS~@@E)bJae!2KHmO{QN|}Lf z3Ndhmrc&51ssAFsts;Fwy}Vc^88tFL1hK)KZ-Y=f^&~FlK+4Vkv@DXV`6HNHlwN zP&m5|(a2CS8O$NR6()nBgE*dbT&b@c>9f8jM`*K-3zScH zk2^aidr}+&@6QHgskh`y#ogwFU@r>Z`kU`w;E+Ir0`s;SO9+N~8eQ_^>OQWsPuC&G z+~Z0sxv@4;^XW{r#zE5dSh&b0qHGQL`5l67Vm>h7&wyRTr^C!|ImrfKJVoO3a8N*n z-pT8NsjZXa#Z7$-fW*ND zWP$(nb_I*#1IB*9N%#A;`+xpGU>eNx3Q#uq!+ZpG`D^=+U(f)|I-o_&!4A+EpOKG1 zJ`RA8IRO|WA8-v00J8+l7}+5ILw+O~AO*?{e{e~Fm=W*@umckR=b1#DKsf*)6#%su zz=HN%S?HGl%eG*F(eL?40GuQ@CtwT&2nhgj8n_VvGYwFa*#TfRD=Q#kd`30_XZ|f9 zcnL@ao-q&r*a#390IC3>AOdXuh6?`;Y(N8dE|8|;(gMwS4i2tADuMG>EU*bMkL8&X ziOK$ZFw?(v_Bp_B>9Ap+$Mx_&*B~%{TPFYipM%-jexD8gd6EtvpbC%~BK=dd08RtX z4tyT~ssui0Aq5kgJ-ZH`ImT#?7yuTD3A{Fl^0x#4AYnXP4*+$gJa`$XQQT|oajV;X zh3vd050kZZ@O)QZYp(phMh#Iyj6+|o5SPkQ;P!Gk2JU8pHG=bs9Pb6hOA+CAdlzV1 z*G7|aQ2X7+)$HZ+n+kT}D9P65)hb?7KlHwGDhogC@L9W>yFF`-t+S?CIotyyhhMIb z+z2#kVttQPA(tRz=D7GRoX`9aM=rN9)NA1|{Z>OUhR!>ym+%_yN48h{zb^IIOmT5H zOG!)W#+2M{9~+oKS|v;hi;5<~@*$?zSB9T>M8CEMJ*bl0LA!L1l;8I#S>H_@T2~yo z|I+Z34$|F@d8PY9M*hv5d$Pf3wb0bg)Q&(XF7@$ z#CmDpf%d>rn2I+I7jX@I3AV+tF*tTT=^sMKKWWZoPZg$%X`JmN&)POY*x#s&ioX5< zvm`|e8D4S5b)DhWwvjUl=X0}#}Z5J6gVt*x1CN3cH>>&0L1P?x`5cV|?kt6c-F zF%@5zSzaFwGA`ctr`DYvRma2n?7d1Pqjj?0`lLc4ItgiOVb(b*tj_G8O-QDqhIf?i zg=(e8`A+BBW_d=h$hBZxv-Z;8{o^=7p`^KFsdQ9Y9?iJ-s_OmEb}IiOk~<2zeg3_o zpW!uhnPMEU2h+JK1aytZ9~~g_m?o7LS6fCgN%&oPL2hLb29XN!uRL+bTn9fRxLODH zC}`K`^Ww{h}x!X`1de`IEanU$;PgTyvL|Iq^5xGwQjUQA>>uk-GK}ZSu56 z>o^9Uiru-c*LGQ%ys6pF{EB_`Pjt&zi?OwKir*k?rw2#`CEnaT(qN+DQvc%9+sPy( zkP$593ZCZ`dqDjDNsjTZkcvV_BBF%BmDRuZ?E(k^+4rlzIJAMdy5`}G_jLx`z#RQx zM&L<39g#0(sLC%91q+2I)kP`a`+Lccr z>15cX!=H7Ca6C%ziRB@ZWGy5w;4qX@(s#ef4;Kn$6~+3$=TYEO2!#)mmN#H!mklF^ zVSg%T>6g;;(j$ZWsAjXJ7|`%_CrEI!W_1tzBGmQX>DS>qQ^%8?27*vl{DeX5GH-ru*o>Bx{RLKDK_mK?nN_t-5<)A# zE-$x;il_kFcIhMW0BU&7A--b=`Sod-G|_)2^k zSu3b{Hn_!9I(iECgNg%>#%d~F6fO*Mr^YtuLY9?Z%zmtxVWDi%NYgV?(rS65AM=vyt6Bt5j+L#K zq!gv86(=z-?jt`bPR7@(3r$e3NnrT3=Q(r3^lTL!k?HZ=0?~02InjX}!gjU*k-?H> zlQpeuu6fvts)`K+o^jG&O-ra))&;O|EFhR_nqHaOWY5YaH}8_nrv*bo7CSNi1sr~8 zx=-KwMx>b4IKFop@06bX=u0eE{Ry&h*L}k*0ZFbZOjN7ljdHKh86pCzuHmp`Anr+e%mhAC>@q4L;)QzFY+#J@O)trV*c$@aU;Ouy82O+_NH0mB=mkPoZrCu-Nlh9(|bgLmg#?O}p~piwf#CU|6bY(0&esFNiJd!t9v zR4l5!zXMm4e+g89!G(YHQXGG175}c6;@|`byB@u?2(&F{yI+qUvF6$j2M0edkMpZn$XZ`A zX+>+j7OH&4oqOnXiXwLV-d20jX?5`V)jnPK2(PV4c9~hG1y`n0b@m^s)G|plYN&#e znr>tIr5Dy4-McOh6!Cj^&K2V+64Hp^N-rCJEe#UHArVY}9sS%%@?hWg!gPGG-i}!^ z2y5L_AHIh2hv<~pgXQ~ zp2VbleXMD}fdl5c`|&m1YlOz1zC3XtQ_Nbepw=D!E@2)$`c^^|VIfi4(-k_D@rg*) z)s=7K4HI!6+7^O@e^H0m&RmWoBUfMsql)Z3(OJLYYO|GV#Tt{FL|XGd&75r4;Ed1b zoeGbC&oY)%J~vLm^MW1UKR+?r%p=KW{>RDmyLjb`NUXVoCHFV=GU;V=BiCkSAdU#1 zuN*{*moQf0L4`3KFS-Xt6IYI>zfLU$CBVgvzAsS=C&9W;0mU)RNH3|^#lUSuXrjI} z#eXRryE+lID1@oWW5^dwg zP#s0F&emmCE1c9Iy$X4GeJLX1?AQ^^`~5nFm?)WG!;!wp5`m&#_LdTh2M{%p^;F`A)?0jt-OpCMN_# znb2OO*BUPv{wPj44>J-HWTK-)SXnq9Z&)W?PSn=?CXM1OJOq2?gve*?5@xzy?r?S@ z@e}?AxBDf{7%lxi36abD5?~bVUZ)Rp^?1pyJ=D5$(7=b~ajG8MWvM{DtLZm4R0&0u zsg|0R^Ynz2sqPp}W$%~k_xM3}=U&X<&lcqU@eK^XqltUg zoSYvkgu^cT+&#QChKj3%ybX;h?vubIZXLt<@YE7Cj_R#>Nts1kbw>9&l%dUy1o>?P z*dE~&-j}a>IU}}Agm*BRyZjx9kKp3hacisY@b$+Qx68UM4)7Sc&&qv zjwrKZG%tH->?2EFo7Yt8P@82llAn&2gz(lS@TFGv7nqS#Z$mOIZ-e41^68yPF^oF1 z{Bc7>TdoT!Y3`dKBHg)MNy4b&LP!k?PN^(|AqG*Bp#DPV-ZdU$2Q_`x6;8MafMDQ; z8A%JuMM8ih81MvyV8#JUpERz#fF~Ui31f z_n2`q6*}ibh&_I+VJnue(+b6M2n3e+N)*_6MQxL6yvR6B(J=sY&LEhx98YCw91xw1 zyU!NA$~3%_TKt+TD-1!A3Yqfl9+*2`M8q(U$g9wpcY;h4Sv?ZI$rzM;Sjal@eMH@t zH@6He9c%nug_pQYUA6cU%OqiGb*OLPNAyn$`V2-V(rc|iPw%1W3)@wjw6T%KxtO+$ zl$KeC@g`F~*gO!_b`nxTvC6YpDkOh-ZBP?}EiTf$)eNUCj=A z29>%-Qe3n-6vIu((r@a;)Z^(a|Bl(GC>-Xe+&&{A;|r+*m4rDCn#N}F9W$vaWP^y8 zFh~gpT-vmyFU=O2jC&`smD5}i-TfOO(Hn78@XC8(|p!_|dNZm~i(xGC`)L zIXUy*IiI;I+!l%&Da;w6sJOP$=JWgbk=kmM(qqOST#8V-;R2#?ILi2~@iMq#=vz}T zLl={qqcJ3NAc7VDSER)g6NPv>8@UqGMrO8?c@1RM7Yb>ez8oCE$)wGWGz*klP9J#v zPy;>M_Zt-tJ;(C0@vyZzYM1gPySrBxE>$6&#^^{gCQp*!YIv&dYT|YZUafrN)9EvN z&8S{jnRMhIqbNnPSk_OF>h{@3jYR8YI5KQk&)35Q9;7ucliAL6ge6gF-~W`=+HF!I zp~Ke7fAJyjgpUvS_ipQP9%~)I!X2ucpk-Drs{>V@`;%z+mls~}WefO#^rpB#Mx0BY#$<)eMgN3TILLpO+ElR9amK?#IN=U9tO0u{S zIxjxkU(Ta1(ZPFjk@cGQ7p7O>&;A65uy!lu_rnf8@_Qv@N4H&2lW{M6KlWJem0;Xr zJ&=IHz{Z$6X2-s7i@klCzi;|kzit$EyFy0gIIQGlAyS^v6omqD%C zn{6NBg%o_Kga&_nlT8@L_n305R+RDk(1x@-n7Oc>LzFu9$Y_m@51D?1`wxW86B*g- zFWHXOHz(@eTw%{S_lNcUY8>J#vr}=;pPq3A>0iOUp*P=<*j331eEnD#%{X!M$TRcu(G*2j)LJ5v7xYGCy{fR8X>x5CdLuaW0`?zIoM>{pCLvc zK%27==E5*SRRzVbkV{YV;e`){6To~}W>PlqU#B}D5jkJ`zhlFH3k`wE(!Ztje`2_Q zNa;Daod|0-<)rOCJR#3USlaB0Axn!mi^|K@@QW@mRzK`Oj_X1D@l_ki-^^BBHo z`!^>K-=C>^&x5G(!2WP>V6+7f96;m+Fu?v#=*0y{vj76?-$JkT1vOv}`!hlHUqUag zXP^mChx~g!j$siAICv45ZVn8y_H-~Z4#GNj^DNQIHK z175*kSm)>BW6m>*cQrMvVwKOTE^?j=!FM$c?F-SBYN{**)CcAos_o^3yUUx`gYfGo z)o=(cPEhYb)M&A=g9z4&BFh>ab6-taaJ?h91MTUY9Zja$i(32FFs3ykM4^;cs8ExG zdt+alvV3)`cWRw@ii}USpwjX_V6a2dwTFC1U>)Oq>?lqUXIdiGZ~6GdS>NVp`n{` zG67vWgRk>ZZC6&)2g)2vYBDCoD?Uu|SSqj)Pp z0Chv#{!9Sv6eX>=g5MI1;uV}wB4-VB-zO5lB^-#dDMn4EIt=LckR!N1TAQrOOm!oY zg>QC-x!U`ZJYJFo*S_*@YeqHo4FMDHQBrp5lnVCc)DU8s2I`?(J=VXP^n!Y zmdNL4J6)GJ&NYvdz$a>@72-wt<1Hds8#Es&q7HM7u`7Z=!Qv6&T!{6Mw}VZU@G_+N zU2BPexejyGoxHA;v96Q_lil!49h_NX)>&muRW@tgW$dft)xz;EpCbzJuV`-O>8ZIw z^6{YydDPX7TkEH41Lzv^?%Q~r)#!y1WPcWaJQmAl1^t{49SNQkF1L@bKN6%^Cxdni zcU)FY1^Fi5^AL=}_qr*OCa}Hn&-;pphJ+oayc%IB{@r^(y?}YjKz>SyIw2v`yf=kU zXR=lKcgsvCh2KAtUFd6>J(HSi`mvQ)eVIKxwr^TCC*%LlGD$0se6CEp_}9TkzNeQ<+w z!@^f3*+WroF=wK|o?ftYlqOmxVjT}rJR|<#Ag$JYbm~F;{|2OY zjazkFm@r=_nl$47qwB20vTC=jPj`2N(%s$N-QCjN`2f-=Al)I|4I&-V-6<^~CEfTf zeBXDUv-dvVUwYkZv7QBbanF0sF@7UZhvuPZRsDS`5+9!W3Xs*}%mqgjUw-A3V zK)o*f5eKF1p%ZfbRhuyD=<1QN0?>ALfXn(?5dh(}iN8-tCI0%z*ViTc_j`cJDj;9~ z*QdUIyRV~5U{w2GFZ>4+`pRGB{UeH^UHvaj5`cgqg!&ub&Y1Z8goN-&WhTHb#P+tegt~;y$>2@>6G0Ll2lt;1Da_1 zzufN!pM@PaBFt*lqC=taVFf%Dt6Ez5Kvi~C%}3>P)faV8UUKJ6Yuf2!)rY#5W{Ct%}-s3)*o$2l=5vuXJDAl}wzQcoEf;$vvN*S$~WCf;M&gvzo*z z-dP7T9FxNQtuBT{peUy_918>5wwV}Tv%(ad4mL&2CpIz2_O1T9n>@X3ZMT`$dqKF+ zc?fT4Ig3{5BRahzRjU^PO^>5nJAyfc4S^Yo8X0ntLrfgXWgXV$Qf0Pb5fHd2qNd9L zrX>Mc1jE|fZ^0eaeyx5t&Gk3?x2JI40=QMY*p9M+r3%$HVIBH)k{ur#+|LugGxkJc zSC%vtr<)#2lys#Re~52)#H` zJY#tNoAA?6!s17o+LxVG!f8+>mK8vaR|&?H7=F9R zBEO?h2TI*vy{TAVPEgUdaYeiW#I{#7Xwc;vlV zlqYQjCM{n=(}>$Th~p^0Fk9J}T)VI~{Jsa`wI?-W$qNbK)L{XXy8Cb_kco^^S0r zl%m;N;hkcVkKu3vDE+k)k89`Hm_6Qeyz944zwZy%$HE3$L1^Tmxg22_A08PZ@9(}! zq*Q&@w~QX(|M*!cxa^i?uhM}?~d<2 zi;?Nh%tv*dc)-)HlB$SAnw*O()M28HL$W_?3u|?GWK+ z<@Rwiog!vhu7LIjI?9@uSL%a`OxZk5PFk59)^X%kVY{vTSdc${f4K;Tk!BgNMP!m0 zsMFNJ56^pqS!HdMRQ3d@|EiieC@xj!Uxs7#_BA^^U_Vcl;-vAh=`|C0@DLn*ejm1M z3Q694l7q*VQ@)mY)6Q8$cM#SL%51W$+F@rTJVdE3R2%@R7Kj{i~!woeSPU&WdQVembc}mFv`wW$1)Cw%aM& zEG_2nH8v=1ZR*=*ToE_t_qcGEhQm8rR~LERtHo@MZII;;|m ze1*ayd4ETW|I|DSZ)xF+Fp|%egigwAc6vg$?{Hw4jnILwI_KhY5ni$4XXVViAdy*# zp(&0N|LWSEw%c9jwr5!m@{A?CBYu{SJReb%L=a%LuAf=FKj(9NhvWp1(K}(S+Gv7Kuq6i9Wg~|Ij?v6J89FOk_ zd(Y!blQ?YLc{#zLG85!HdBVX;%b6^s#Rs55zR9L)=@yv!J%X4FuSTW4p7zd2r{Gw$ zM0vk!TouaDSp4i|>=n@to*-8I;P(|pea09UbaQtE;k{!doHk&OaH7n+g7;&GC`f5v z(bRQx?rz122r>ZX^mHunB(qWbo;-&UVRq?h%)|4(cAZ|P2rcrZ^!*r3^PW@0V`{GO z`Xpsr!a3PV6RA3$m=inRxmqilsd5D(SwwS{vDo+YG1f=C{*Zhe*oyAOxW^A;`*1q;Ez9&?qAY4>s8*xtmB*W z$I7Vjh{=Y%@$JJPzIZX<@2g|(xN{Cko*aIF%`z2=noNzU|Kc>>-v0wF3nsTVeTu;A z;QbrTma>qhpwnMd+K~%amkrmcxUk&Lk06za@Wen=3}3H^-yP@%7(ePid^6MV4t8n> z1+y|7>X$R0hczTwP2PoYvT*eto9OQ~t8NyCk?oY*HDL*rr-h`w>#6<_F0MSje&S$M zkud+ob1y~>E-7#8QQvD9HKlXtUVKvnkFBR_R0gBOfYkFU;Jl2l%_$}MkxREVE(v6D z%2OZ6-^>Xo(H2e>kSx5IQv3LIY(9XF6Q#ksdQDi6=WwnRZX1xi3Wl&e5stzOwp<6>VaT?0twgR2X{FUF8&Q0g_a1nA$9ic)F)Ql>qba({}-JKgOZW=^yDRUa2OXT-qtRNj3 z8Z!xI+_03D3{bH@MM+;iNAjQvAhO0lMf`lA5R<4WVx>jgQG%_UvciJaXU`VgP<^@< zmbv$qJb~A^78Y45mD*U}`ee_dT~W|Uqw?XwyVDn+V+%&u5`Ug!;pA3wv{2)G((Bgg z1@>G*H1U7V9EmbdM1KWevH#&g{(Ik&J9Vo7oKfWeP-OyPlz&v2f8#CL|HfOg|AV*u z-z`k8*Vs#vR}du&5H$I!$>aojm#?tP*W(B~U?v3;0$w|mL66jlI;Y5~fsNooq=40y z3Gkx+iSgzIXiV&{Sy=$ui5;LM@o@1ZqCbg10BP25{NxD!7F_}wP{7&x8h{DB4Gn=cAj1T1Ni2Ot`Um#>>J6m==SV#N z%?o6Op94Hg4lZ6GjFJTea4cuQlY`SzBn;p0_1Gj+c{*A@p}Wdf#m1$eZcUQWt?aB3A z6ZD9_Yw646Q#134$3ze)wC7^BV6Zh{22E^Lp8idcV$0812XG>Ov*;VmZA{YiFRZ|2RqQPz7TDHpmo`~gLUQ-sLDVh*T<>^;Rs}k5Ij9k-_ zm0(62lPm-z;lVPii$pxcqi-M=FSAh|wVcfE7f9a_9u3eWic^e`>={)tw2EM);f(T^t+^OD=x(L&C%#@@n!IIQ^;9V*7Dip#3Bm6Z1B6@2r1` zgpb#&2lQ1;X3Jw6FLV??%X+Wb_B5*-{nuo+{~QsR3oFQkKw57+7w>C3O(Se-R+L!S zq07uC@8dq?4QI`F?NH6e=HHV-VQNh)tRtip^&%QcdnjOnYh+1Pcwn$(&s-)W>V z0zk;Nrz_F8z&*(->0C4-gGHz7B#(&OdhLxRN6?QIkDf5}a#16q)$U!l0qze<8~7rj z5jroFi{wZu6Rha@#lHl}CvXvun;g=`!$X4=e-=MvmZ%B8lP&UF4B=!`?uiQ{4Mo)2 zzeFdup!W)E&NJEE^jHyBw#gMMv`aaON3lf1ME=PRWv-LU5q&&Z{!J7Pd=`t@Kq8%^L-j#(1*%PY!Zd=}IhDf%D+aG(oJRbxBGwSJ+ zE{@Np%L(f-B(L6$Fj$JY69Ac-LU6YE(}s+DxFOl};$cLEOBZvH6GD*|#xj3n5l#WK z>gPD2H#1M{GrH_I(b<%JK`B@kf}ZB#Iww#_|D+qE&q`6BZ->0>yrZO?_X3mqTaz)| z67>v=>oD`raqg6(OV_fC7Y2Id3bL5NwQ+R&f1No`endnh4aB&PYVJR z%Q4?q7oEzZhF~c7WK!uqnZ4kaFC0Bsaw)a9`7P)`b}L8RiA)GE&+btBXU6JZ*{^As z?uZDlMD!T!rRU=ruU9C3hx74F%fFR*Y6Z(MhDbc;KdH(hs4DT}f5yP$383v(*0~oB z|6;z37)#zBaef9t6xX(`nf~Uhshj@=t-FWc1?qahRQmtgT=QQ3;YI+ag#Ya1aldx* zumKj~UswLe@c;mmj~*Bj^D^nMxq-Ap*4K2SzcGW{fT4gTQB{~F5j9IBk^J{7i|ZAe zfc45U`1jok*!+OlA4C8H%?h;YUYmZYY z0&m;8i91hCL{O)ZW|6gHg(u}*mf3G5#~f3)-9y(KODZqZxT}(BuuqZR%kORu*>4tV z?|R-Bah#uG9}OVFi@M--fB7|G)#Y(9U~Gv&OeY5?#Xw>70l$|bP{B}JlZD51S0yQ= z2k{=-GC7MK&Ds57vAKNpK16D%W?%F;rc!OgqabSWS^)In$+p1C05qYb{QaVe#EG+y z^ewv?6Ro9UCT>{HkD{0AZ&?yA*)-)RQb~+z4@zH?DDfy60%PSl2@aqLbkfK$Y-5xq z66f!m$17&ewaz$kRp5CTBu(vW8{5aL)IBYW3rm-)(Z{ElYxY%l2}jOr&aU&Su3FF~ z_i2r)KkkDh+bXJZy+^10(b3v(MdY(@*r1u^wfS>Me5~F$6u`^L6gsq7-+fGT9}%Z+ zMI+|5LFHq12_2uQp4b?}MNbjBS5JCpAipU)H9U;^aC!M{FwUt^?Pog}%3H9FA(BF_ z%KCDeyJmDdtD_2SsZ$T8XxX~=^U@YWeRY1nJT#p^H;73w%7+xEWDJiA#M#9@xd}R# zkD8h_2!WIcsgPR5<9Lp`>tbId))gG)2`bp?5|#|k888(azLr(2FRY)R-tF8UHcnoA z#&*{6NVJ;8S&Moow+(IhBGAUeIjXB^w9j23M|mc`2;M$~HD*&8T#z@>$TWCOv}t+rJ-v+?_k zd&&Wgxe+VbiXnc+oo|N98k@(8Z#Hm3t?x z*3EC#%dMJ?=6!t3IrpooBAcqUi_6d7S*)gTgT^~P;P1B4MmS3&pA|SY^SIlNY;loR z)MwtcSKlx4#yoMm<~-cCe8%yW{dKGryJ^c9+Mh+$CyI99p!qX~ixpSGeq8D4mO3NP zJlk3yv-Hmg0z3T9B=^~>gL-?kq^XGNtioG|aQNB%i zhwB$em^*ImLH3>?X_Ib2JZD%K0Vqh4_xWcyUpnU7(!5Ms<{=Tp>$rwM#Z_;j@d;NR z9O`0aNRJ{>%#abYQA?qD@bx4s-((c+5*mV#BcqzI1S2_XOLZVyV?0E9%aBYa$AjL0 z%_;u=9s9es%t!RjhV+H!XCL#R+qM^+j4xDof)p5%Z<~pkqX^#eufcryE-Y?1VFk^* zt8;#K5c4Q#wm4w%oV4-L@=*;-BI(42%NPt&$`7yB6dNo;N0~(*+GJ`K26GTZ zq*!kf^rj^xHw; z*bYUy)tQDQDFY1xU9r!ZqF)mOJxeFtRt`NxRi@xQ_Td^EhIlYy!+T9u#r&%@2jc5& zQn>?~+%nB%5)ln$gd1q_84p7A5c6HoNJsIzc|CFbw*(?GHU|*BHp~`ohH(TcmWJ%M zf^GAsxfV-lPM(sPPKbR8>sqZ22a4Yu73y;$?Uq+uH_rl3sh`rI;3AX<-uu(D5WR_` zhQ~5Uf(fQ2J0}-TX11n;SlLH%Z&uz@!07)iIx)o&NN0t|D@VVB!4mB zF#}^wP#s*=BI4us(pK#L`zc|iBz3pSzPX>qwBR-}fVQNebmCs~P3(gsijF-rHD5dx z_xL0OT5>d5Pb7lBl~<~Nv1fVfTIIu|calhVBXzJER_(7gxx6H@1B-R2mEThrPYiEq zZ)(cR(c0${tEUdjq+*G9=e02SKs-aTmYtTCOP2S^8*gwLMOe4AIu^-&o-(kHaTfTS7*TD9j~+x{nqM$tSyb0?cc_Od)61b%M*ai zl}xoWQ$Ua6Gb#0|g@3;`CZ)I8ipIAJHU`JOow2&kW;cDYW+WC5p8R321Miq^dY5nk z_?3?;%I|>xKl^nu8Th~U>%YZ1YL;$J9$kt5;L_D z2K;X(CV=Y&%z>P#OEBO-M&BRmFVySG>o_Zw8usl#fRu@ls2qU)A7r6;QS8Lw4wh7XIB;x8Kn(L2S_t0| z0QX*WboDwV2WNaLJp+4;UoUPEJT{EsXnCA6#A}pT{W_AMl$3F^v4IqkM5${ti=zYl zzry0}yU)pApEupBemOthf3(uBf#{UeB{;YC=rZ=H9(^Ly`}S#6EVt(La^9~b*@r5_ zfnKt+ioPt?qb1pF+;D&Aq2>_;GU~W^Mvv~k*2w}33`p_&vX^d0vvU3un>2C#E-Eg9 zOJIPL?<@0&QuAuP5Y{r(@COs3I*zTB&9-XB@;=`kx6?U40(j`4oY~HoCq~(jsK`(8cHn^JYL6 ze>-#SKZW;{&Brd1!}!LK(SlabC3@No=D>wjerZezola#?I$szB!Fcpt74c8 z<3j&w?O1}o;p;fEs0IN`4CvNlT$0?b^os-eq5#w^l;CK&y7L>7HYM=p?kzKj2t0+) zd{~CLxXCn!;4tx-A@@ zg3xP;Lfq0}VX{&s(iuLW89;gX9fHU0`4~Fb($y(-Bo*(-x=i>&&I^AIIDh8?Mc> zD6aSvThf6Y0hV2z6x*;iC{G1ZxS&1aI}y%|SYfm0;Dxn2Yb`W4@Rg%BqDwJ*lVcn5R@ zjVIs^@fgBgNE_pAe{X_CbViDf6jQKi=X$QQU4-izEZ2*!sU%`#^$ed-H;u!TKXye@ zPR?3|MG3FS?8N~wEoEqIXjxUN6XVBCrOI9k-rbL?9lopl5SR}iNmfQ?vc}FzUcB0| zAPMFrv?vG;@=-L`^n!}-A}@sWDx@ct@rtG@>??L`W7JaD=U^{tj%Yq5bel+l!q{QB zQ!!;xuPcZe>1dAcn*S4IJG}Uewy1K zLw+iwRo#q;-&mjywI>LrG^~2#oMb7kQo#A4&3!%!Bu_<6H)39k#y0U}hjbxiFt`m{ zbNwxNVuiE(8Ndan^`mE0vVWDXDa^liE*y(7K0>9Dvx(G8;)H5EYCo`TC$9akNeL9J0em9KaM<0oUevkp09^Ke=NHcXv zlWMRS2(nbLaVzI+u!_T^jAa#`+N=M7Bg3SeKWHSUU0JU=?6(Ggt;=+h|^`%rMyVhn@-k2md`uhUXK^J(`ZDjsAeaK;*pL>Xj1-VN8Rzg zu(7*e^7$_mY+5vM-cxMeygLbYc~WAZVFAOR11-d)BdgEyR6KVowf;EpyI5fkSQUeU zQjbV8@y6s(2_$rrEb6k{%|D1`*D4NFgrM1rddNOM>E^@NIf4=MLBkh%*%f$FYfbK6 z=+S6$U|PB?RPHF|+ZvF0Iz3GPD9(itaa@NfXJkw++wAmBR){v&XrLP$-%i>*S}@TSCeLCrLGGo(XGz3rK5xh!Yx-Yy^(o78ZB&G}=;(8^WmDuSXm z|2r>hy$^@hxs6ENA6d9$#;FWZ5vq8Pf+zijbAC)X?GUL2)HLg6Gkf@$c5k~o3KT>6 z{HWJiMa=jJ-ea$IcAZ>DRGZDFR*NuJA_TJWT#S>mnsDA_mpK#8vptAnv-W6?YiVX> z8BgU5JXY4)xEp$xuzAdDQvD!boMV1%zC*WeHDLuYE|%~FmOJoXq`{JwV5~SbiwZmj z1JrLDU#yXEC+h7fll6hbQ*8fQzQ{NZWB-F8C`hZTc%MQP5us3pz@LKLO+}W)Wg4{n zUd8ta%F2;r*oiDB@r$#s+0El#12dUqfUbbc$hI3f_%=M|<(EzBLm2@Ud^Hj!I=D!h zydYRiaLO`lG?9>SQ4u(O@U5P@pagfc_}1Btw|R#Zrve<^lH1mAIFKm8NQ5K6vM;Tp zi`tN{#D~}&DND7d6oVS& z^V*Z$k|Nvrh+ayf%+fR0a?$)ijN&Dd5&~g7+cQvCPsF5OMwa?m%rLnck8kAQa_J_x z2LCLoifXBGbT9tBiJTBe5igd#`*jqhvJ<+{&00v8PvH~%Hhf+aJj^)*t^>#o(SP^T z;==UERnHGgw3yON5TkaHh@l$~lcLjt^XIczj~L3tuchfZI)yL6<%B<9a*I@ov}X62 zQwD$BX7IPBu8dni&%B(rkdpZRct-CKq`!I;s2u-}=1hwTvxIEvqAW?A$CAqzbJ>X;3;Y%EP*Y_dc3~wuN(C1=)vc33Yoa4Aa3=jfot?PE@~s&Kt_u zA**lj-}2!<=LdtNM3A0m)A%#i!Ap_$Dgwtt%yu)2WyDuxvI}Eu~ZDAKqYNpTQ{_H|A;9mfG z2EU{DMcsVg);RNEVIRHuaKf?}AU?#%O_Bum( z9d-oZ0M~%o3Bd6Dd%VE`q*DNJs=qlHsj*n#W;pDxi4edT=bzRRJCME%WNrZg7O5WC z;N~z~KmatLE)xYN7^&yj;FPc&Kyo(QtBM*MSouHFdB_g&UFs$kPBu9>AqUv`#vZmVRO;5a4xN zW0Ez2^$zC)6kF|VfOCCY_K8Kp+_?}XJ}Qil;`?B-C5PVuagKOI?H?08i&2~VKe@Yi zo~`W?poZH$@%gl7j5!KUMrK*Z?hn}7ku7=%fE=OR7T=I?X_cM#^e;U$J~((D?R-}v zl8imWm?FQK<51kTUDxsf;g=l6RmZq}WkMYBwCqz>Ht?V}SkgAP6XZhqqGH>2&;DJT z*YbU0$-pw>fD~NBerFIG0T)7+&w;!lvyl#$i{T|Q-didUeFN8T&P!6&n4e@E?T^Ol z1#vx}%to_k4QDluUAb26Z=%$j`BQi}9IP;o8LI1--4)c;1B=5fK|8_K*644Mw4hC> zefBou^%pN)?$`|4FK}WuElc3ZsUgs2?kF8rzQ)sS3uwbxt1Zx~&%71X3;b}z@Et5* zBF*I?i_6}}?U}ARB{g*28{``o=Nau;)yS1dFDoU-%gCOu1m9vOtmJ zjlBJB2i32>aJ+kb3et-jsUn(2S8&3@vc$)0)skC`vxZ~aqKRi)+xZcz-tcZOr~2qZ zwj9sH5#^X7i;ZqgK|4y12FNt>kY3B8Q&DlapL zlM@}J+)gBX7Ww3{Cnb>#92+t?aR>7sL||ol^Kpx2T&R`W^g+dPM_10u)FFt)zjyaA zTb?G{E8quq?e8Cd%MStZxG%)!f6S{wKe#f=#lLJ26x2y~ zEb#_H&V-`8WetQ#9HPRW^4?zCH3wUf*2-5S)8b*&M^j&jbyV1TMn=@ZCwLe#F|N!l zgKu*vo!k}0qh5aJ6VnOwrlNN8vfW4mUqwbGgVgE|my&%4413kO+7(N-p)m)23bCj= zTYSraCt>~2G`V_jFK&x+LCGp|QnbE_NyhH}sJHW_XXzk1#Epe>Wiei2dV!H|+%ayr$_r*re~*NemFKFyA^A#Wh;P9173RAADNKOqlN3w)FuOq$Jiay+b`x z`x1-n+0hDm)+^c8MlBH`m@aWv)gR&{T@hTmc-lGiLEuk_&2@Z+L--MIpd$>X-A?Y^ zS^2LR) zZ$IZV5~j)Mdi4wCC$GJ1S7UoLpEMq9`w~LoM}KNE)2^)?*^i|d7kOzLhNI^;(soU2 z2LbPW`@?ygr!N9D7m&F(Zo2mg#=U|LeHZV8PE)-=D`wlxlFf`q9pNJ*%}R}Rv``iU zrqg=;4e=Luc$*|CI(uKi+u5@?t(*77-}h?kFL63(O-rQZ3_&l<>7M5?#%V$Zt+N#; zx0!i{Rz>6cZ7(lNcBjAw)@Z!2Y}ekJ-J$fexkwX}3#69pOESdpf)bK7xEDJMKpgIt z!_V5F;2Cijx{Tj2r~0bFb=c&`ThsURz|x!RNsFn$W`mns!cwayR)SB6OQU zZPLaRu5LQL?n-oJ;+erVUne2TsLy_8{bvu*7_HFuT>8(Ug>K_eCGvw`s zguh`*EQX5yrxWteD1ZY19Q|=M0l%+bM*#oZ@DBV|KZgcq2KFQ*Q}8zsz@0$g5nZYx zF*ru*mJhH;05Btb4A%dMeFVM=xQGSA0icQ4SW|woB;|GED)Qj>@0zh)b+d%aZVe3cw=Kry|jSqr8HfA-MnH9_suPImwa`AtR!26bPRZ!J*WQqoZxHd^3dt z!#LfHvreTZ`~LYpTU@Pvmqs!kN3vWzdiQluFHq09I#IbiDSdVti^HA69wSS{({t>| z-oP01#fSwFGUl$Dy-nUK_hWbwe3iCCwl;@hKpg|MRYkdlzc&2kSsMs7hH#NMYfG04 zo?L~Do1ck^x4uzMhkuTHF=k&W(#>`>0X}2TztO1{R5}`7x<}xlHxN$UtXu9ueMO~gVK}e^qZNhTu}kW!J+|KX>Qz} zA2#r3-MfV`#tAIMqgrI5*(v5PBlo^!Ly}nM6lv6AFSYpEv-Nc9bV7q}^3A3C^-x@P zPLY-nU?TEG&(ZPuQx+AJk*&kH&7{LJlsy?*_idXuCM!Y?7d1hu?wR!vviU_HsK|N{ zBdyvpK8mD+L5c6S>b?!a#C9(b>6LLdF)n?7pp0o<;4>O1fp?!Gff6UMBPNTlJsBR= zOq(ptxWkIcPkH#I0n9%~``}=(a;=;;r2zu#SHmY4_EpLp)RcaTdX8h}*d5Y4o86;G zZ_!-4c14VVdkH1bSCXiZ@7eok8qJmlbmj}1$?SUN!O=2>iop!C4EdkA1y+fID#%c* zQ#HXhLfy*Vg)eZ<3yjopInDmC_~3Wi+V3GQa+)~g@;$^rW%@!>t;imtlt>a4;%QuWO;<)ryLYHd@N!Y47C)mt9@cJIpN;? zd2Vs2(fPm%59-^bWNA124cRuo`J9kBJAJDtLynLWW+g#1`H0d|m$g7N<-*gICBcK8 zBiP+YjFW8D{gr-yP?|8n0RF~+C?Tc zhuV(IFrc59jRloZgY7+P*#ca}h-+I*#nkxY^9Fj9jGjaRTFX*mO|_?wNzDq^=nu&U z69OGiS4Og3B1a{K%0rNAf+e;xZR3wki^evlD^kb-l|~wUzApxY61No)>!I)9GRq$o zWDiC@m=1~EZ0(Ir;v(8puCig)88*# zbz@3mJ<64g-@%{WFoIo%1nq||I$wyZpyGW%jhd5eH_yW7fGqvV>d>MSF-cUIk!|mv6sqg&nldm}jN+gps?K)3mBmp@XYQOBXI zepl1HL#duZ*yA+}w>#__e((wKx)MfT5&89hR0XWSrvF+N00+%KI>3J`3s_%+8X3v{ zOV_{wKoeh81ROvDBk*TxISx2s>Zvv)OsX>`IPYK4i(G)Ci2F58@$cwGZXk?;n~gJZ z>yudOJux67aBu{sAbFYeSb%nc1<*D8b9&`^B|iS+9u`WKrvYXyKn&t*K@#=X-vKie zUFrZ0Fj@H%tOx@{LUM4iC8`c%rcSVcBfUDI0Bq!|W&#Enx^S@q5J=!s851}X>0g)r zy!^j=n>YY!B#=u9+=%g?WyR~G95D%igE}Xmk^E1I1qUnOX2t=>|L17@PqZ~Ya8d_8 z=O4D?KjtPD7M|CjYkH7drVLKtVojbcNl1Am*E$EB#u4mU|MK{peS%9;fUFu56k`-I z5)!iKMeXl^iNjg4H#>EP>8{B(8K-W~6N)(vZUXzp4WMePeJB5oC|#oHDP1SxQ=hTa z{01aJoV#9KpaG^mbia7byzf-V!*?h@Y(WG;>{hI*n=!}jXRIl+tl!r!Ba!yju+mt8f|)dA8lOCg!ME(&9bXb+b-{5t7?Ke>ZO$7s*FCa zUu`_5ex3}E(1M_e2lnzIrm^1*w8Or^8Ces?SA`D_@+DP)+IwO5PD2b-{ZQC8<1V%Fr(EDHh zuy+eJ#g5vb)x-)crN+*uUn!~sc(g4d!(=PGk#-V~g^Af28i)DO<{UcM1>OX3aBGLo zbw~X3_HvOg=PSY5IJQQsw_wZ%XLFWSb1O=P3fz_Em zL39qwLg@u)Bk5FCWU(pjkAjc4wre|Q%Y4QVzn9#&H8o2M3vtVvzGby# zf6i_Pja_iq$a?v2U?>c3Vvr=hsC;?T`^X~jsXLVcVdo`_DnSLe?QO#h*(wc8#V+OE z2wvZq(5+Uf9Ul~Hk*N2|=vQb>N)o8CTL>Kycm#+TFEx?y6gY-*Jx+Kj@{I2>3^1FJ zK66qHyvt1g8z(0iOD(cSGcj646RSigAn98her8r4jLbcg0w*vf+o&^m941RA z{6voBiKkqaozKETGv|F>8)iwOz34A9^Vou`)uIc5P#Kwx$bM)cWcw*bu8$Obn~O$< zUuG*1R|y~QnHKMhRRnRZwSyL(jW|6kPPrs=2nxba{Swa-w)eb+WYd^~nZb=t0Ptk;SRElA*S{~`hFCtOI{cEU7VOcNx z@e1)YKR-y+o_COqdvw zEy1Qq+0ERcyL#x}`&I=BNB4|p3sRkm1_^}GUnM0KYnlC6_xOlRbXn&`+DTzkZktH@ z%i%IRXI2a6ZHN%F_sAGV0*o*#`=@cn*|w~_*h2wUbQYFWlCWcL%m*8*3rbzs7oA-W z{t{y&;r=eTyu_rfC3Ttu+pyn{WZi459Aj}y{IR+;DIFvqTz4MO`)Vy|a_bST&_Qve zOJR`ng=5yAG|OycDqEZp9o|ituojgk;977rtj}PkE%J7`(Aj$va4$TrbDRAE;CXP_Y@pjAw><$mB5mX%PYbKGkO!I z(7lde#p&c$PnSftfNJIoSgE{FS{Eo(c0XU1cY)d`&6QO=Q;k}XyJcqCfU&I1$dnD) zIo%Ul0XH3+u%J-o26|o8^sH~m#NI8mxbo}{6?;VLHzmwxd|9dY%_sXXTM#qL45yjx z-_-Xxwze;H*Y835tV6o)KPl4-l!=AC6d}pzJI#Oa5nih#i7AZo(%uEcHavk!Sy#2f z6hp=JOAstqel~n^N4dqn^9E;(C0#i`j6v%$#^wMM<(YfWV3SCT)vbo4YZDecvM%o~ zyg#)m!0%(?83W6gDN!+xpj+<%S+LNc2J<04qPVhVmn=+9je`)^(cCV4*--;Ipc7wY z%ujRW(niz0g>rqUipm0DG7N(d9>TY6=EV#?u0njUb@r|$inxxctm3CZ))QX#t7fsj(q?5s3dI%h38kPF<~s4Kfu zw!*&r5`{v4peO34nQ2U*nFCHdZbg( z9qyS_1@B=FYh^NXaPT7~4P&uj=b57)h@7pABOArHq#9H@N6#R;K%teRE=X>@`S}3$ zN<&i3E_vM=`lD)bZrMJ|%)50~KGM6A-{?P_aNA^0o%F69${`+DC3uB#mM~BC*&{@D zldl9&=#AChMVxq@?W zWa!gATFzBx$Bz0xsx1I){dY-_3SSM5hy7QL@bA4CfSgjpMEl=07Z9`hr{-e&yY^!H zqc$Z=Rb~b!crB)ciT*ZE@V;hVv;S+Nz|O+W#Lbns`RUI@fi^Xr87RI!u7V4ZumV^Z z9w48ajRlxF09j;g+`NEw0tf+PedS{RZ;T9a>f-BHDJ}dd+32uYUoqGKmHVHX?R823 zq?BxfAY)P+1ikl24!eeC}stbKjGIyvxH?>(UGeXS!CKQ;c-d;0h6FkoZ? zxbt6KBHUc;uf-rO2yfMKR_3b1*tAEE2P1v#?$p+YMg>O;ZV6)vR>ay&IYF0*-}~6} z@z!s`0D~X7NV0A zU3!9w_4?6qy)oBD0sE;I{UdnQ8)Fy?9VMDkVV#6pc9tUM2ZlZSGsd%DX6{i$K2IjWgzYBxBr8rkNvxlvIFzbFw{(l) zMWrelr>2A&f_@T>OIw{8;U5&0HpBQvjGQNXTNt(Sw{Q5pFmEWNbybDdXExUwukV8Y z42f1BkRjoJpCt?$2*w;O15tL#Lgv3O^hTn#*{o@EtWdIQ_`GcYpy#=mNO3dV?C^pv zX(blHu~J%r@5HYgMASf|^v<8YgghqRqooW9wh9KK3nV27&v9)|aA*P+lgG;;j>N&1 zPSt%p@5+WeJJ;pt3x(mFCOW5R6~vhf1&B{fn)7};hv7ndPdMs_$oOiup|DyDu;@sz zyF+Q?jL)|a%lj3AE&E#5E<8!kZSF}esCh7+#p6mTEvA)VP{EW=i!)a_xYIVen{FbX zNg&qAf*i*`^#9^-q2kZLJ68{F%jR))Z>edD?%dcbJJx+N+wB&3g$RBp!jSh=!Hjh;w@AWTn8pU zU*16@7m)d7WD6orPRx`@(YJ0*YfP^ylc7IZDJ3oKw1kOik4 z^lM7ex2)$U2-{(#+hM3J+iomMw#?RRiy>e8b3~bMnA!7gl-%DMH7+h-WUq5rPwz54 zpS}-Ka=uM^aakqg4GM26ECfiqCj#GOmCU^c4XXof!w>#*0^}R%a>c47^^|xrqwm&=0{u~PVwP7mYZH}-L!+xl-f9ZHN0^Y^%8a6TbL;tUS#$kT1F-M9`|O*j zn7yBE^86Nm>|sy~q@AQ~Ain?PL5p}$tLNo!oWVmZtt}#pMHPD?MUxr^ixG-GoUnGH z9$u)&9iXXa6Yr4nUejV7K4$LvjTcA@db8I)q)<)K(#6s#ynr^w97C-4Lpb75`_Ke4 z9EbjN#v5b>RPnJj#-#-F8QrtvHir#-Z*pxrM2N@O(Utb@$j=tk(}9jDVvUuoyld`A za0>Y5X{wiSHeig@t|`;x#JYzYQF8`SxTM4@NuttMfn^;z5{UXOi4c)gI?%xI&L6oGM9&M@dRVhq-=G1!7M2&?)vL zEcT_4DRKQ;jY$F&4a+qQYuieU3=*w`S>P#)G=%=Dm-OTM>g^@@DV$iZb#q+cm-FOs z0jUV8P8{jdL0dXlSgvUc3aZ&#WrCfjt+M?Vz1wun=8&fH4z!D5eu=U5hpgUF z)9d-!5234>;v?7(q8925=w@t0=5ak$(=UWIJrNd0Z5Kq|F67LRLzSU*`r}^uW^4qX zEOPPi9DKIBepaTlNkODZgQVW+UAOP4+GJ0!{-p@sLgI% z&DpDQ_Ujqh|D)@w3!9faxXD*3o@)J)LxdXh!b8Tz39;)mv#&xVLJn!x%-jvLp1WEoA7XcgMA2~U| z&Im_2|HDG@!QC}%{E$Dc{Ks9x|GV`F9Y5c12P^+wvkfge*uEHuB8hVy8{Tx4<}M@- zNZ$biExLFF1?eC_;te3WpaTm6?ovRd+X5mk*NTf_wEYb-_Z#CZfNixvw+_g_F56dm{c=J_@4pz&G7yf z+5>+Yu!Ik8;T8D(I9lERK8^^DPZ$i`?f3IwhAMx#gu=%UFmN!bY!D#<|6Ky&Uvb;r zeR<)TfDmCAzB2GN*d+h5m;%fX2%`aT`%j1kbY3Mv1v;dFf&U%&%OIk^Mh3By^8<+W zpZpr|Zv=-_Y*0g|bs>TPz<;~h%ZoQbP{KDGpy-wOg@Zy=JSxO{iw3zyb|GsT!j!TP zglw+d43)fW6frcxUn^jZasFB^5PRSqiJU;FK!b_l zsTX!K)%)otZb}lS526`iGK+qe7)&J|F~Kl4e@&aE1TD3E^THE%*0k0BQZW>{x#8+@ zJQIbRDBiz({UDk^*7^I_slgdZvA2P+YGDSOwVQEm_WOtaL}Jsb3$>P0U4i}W z$5LUehuN&B_+lm}Zg`KTpCz*d(9(32T@LM*Iy6F&UxvRP`o`pGv$SPJKb&Jf%*TiO zTwucSQVjjerh10TX|T9rboD_;KMgAK1v)xI8u_`Ihm_o?A$ymDLjEb9A{!fFq;jp# z`{#PSYWm-R=q``o=y#R1-ek?5UvGxaf((&4rTp#knybSLYS#6hBn)DaS3lBx8ymv- zRpCPx^xGqIqbtuk@v<2qDjj~(H4}j%I~ninK|e3<*^ZKJC=YJ+k(Fr3RH7?iMUKU} z5Fi9_FU2p6eQ4@Lt3jMGa#EC9zQ5(Ky{tV-pWMe@H7dDmGS7(`9|N*nK@54&ZTk{Kfqz9P!siX zmM1flV(FVGwn!9fUF|SYpI7J{7#!$n2jM@bz|Pzvv(&Vq&{ux6iLtZ{Z;;z)t_S>J z&^fv=Z^x8db6tI@`-)$h%chevNk{r#MLFKSMP>g)hLWevq=415jZwaos+S=VZ4+%g zkrI}khbZIIGh5w%189fbU zBvXi5PKAT?Lkp1~DOx$Ns|+5sCEW%GkwJA2(AZTLKF@<~eC(QSgdl4|7c^TFUO_1J zmaMgz6T&!Vh5Lc`kyH9r8&=gII6RcvWfI?CLpZCofRG33BYDy1$xKIXi)VT=3$lJ~ znkg7TVmYs8-y;`?XNVm%^6b%y76q%0XkdZD5vO}3mip5(>TuPk%=B^uD9IXAEul}) z!bbHDwHWpHxR@*Ed%^3c#nM?N%8UVeP4sgLP_RXD`8-qFks!Lm7Ea?#?d4CwH-f2k#N6=LE-klI>il^D!fT z=|9WfB7HTq`J)W=J>Hw=W5$uGn^#*+DCu7*RyMz;f6wiYapPp+bICEv#K;{)dbTia zLP0O%e<^8aFFvo2=QMpub*>(Co>^2t(K+s|v6_+c(25$uSk7hen%s5bb_Bl;dNC^l zit-sGCib`Saz-S`VVYl_3B_-+3YxTBXF@3}W->+dV0+|SCcWOLiujX2&89!v(JX-= zJo3eAGJ5$*@Bn6z{Dju)2Q~UdKL{w14!d7WPCxgtyua`$!?U+ubfGT&lRI(+ipBzh z7vHcgO6)Mn0#6RzbElg1GRz~MXy{r_1C}nsxZW=s@Os`GiBCsEl9j|Ci5j{+3%XL~4)S^;Hl?J(o>~ipA%}SXa?W&)vk0`Qn7@ zvP#X`<6CqIR@Y_XX|uIdRqIzpsZKcvTSFEQ>(Un4bvH>@rs^P%A2&ZYW}pjG@0bEL zw=QMA4;gEdC15%07;TIC$oi4xJ74&{OG@lu>lisc2;{UdA!uqyrV!^?Er?&DV<8T# zcw8Rf8-*=ACqw2qr@fV?(`BRgF!v#-;+S)){s4uQA~AoXK}k%kiLU6uqQ}HG_J%Y& z)6bXJ`)_026d!J^i(%@z_9Z|iXKnmYP|F}XleJB_;rvLt+SMcZHb}yVI1c?hOjhYZ zfv$VcRqksRMwN_3^{lLJJWlzx=lqRKmbSutGC-Of+&jPPemx<>G4!rxGqjRscX%^s#?g!tIocJnj#(Y=t>r2U1R0zav1rSs+q4)GZR7GDQ;fU zcjc+6qao-h66quoO;?T^_^B$|=eBlX2rNc(CJ*8uH)MH?MDk++V6kyg#8mA3!5DCQ zXzZvCTLP9;YLUReN_HiSd)W>@wUiyKU%OG|B1<{MvF2=!yx-N01@L`LtEpVJuVMKZte{DPROE3T>wSco&-+58lP|f~e;p z@})AkU_li5AycVV@5NZZ)aW&l#B*Igc03%J`-&B-u95TVO*hIiG;vJOx!?V>{%w%u zf~w@(k$u%y#Uch9kHNc=4_i%`7btX~-!kks)U>zVtjM7slY0EMixJ&*aL``kd_Vdj zk!CD;OC{98k>-H&*x#D~Tx<16#YAF{@a3@k#colMhLh-P;__e3`JK~B7!so5uBP{x zt#N9w>|+Zvho%Oh2;@Ip^lbl$bon8Fs<7_R%l`sh2wa85$@?!z7xKGP9{*ia5uomR zm#7T?`I`%bttbK452Up$oYY+7hXVd28P?-fmjxJG&K-94a0Lr z!iTl%gK%N{u|WGg_}|N2iFZ*Mgb#xJS9J!XQ2EaQngD>|A-n>x7-c|e_InchA7lT7 zk9Ucie+8ny7oZ3LHa(qSy1Omgw2QVM+6quMx=&SAHXrZ;?Q-k<)vXK3IHvQL~5$Bh3rFe8Szq*U2@LuqxN(L6vE>w^Z|J5U*i-_CvkD zVz2^-;0p}bK?`GJ1#dAuwMwKWkB0H5mF^CBw3g*c7%kREh@Cfrt3u)@&m23rfRm6%-Er`c90|OAz2H&KGJP-JTbb%Dujm!3|?bWC$mkNv8@@sfcfs5=#8q*HLc5 zEo+1Ixxk^pnO*baIoY?z@424gge+9ERzt%!uBtkxR+ZM?o$Q2v_|ewisdKi5YY>C` z?c^g_Rr`HVwmpT}Rh$ z$pIubL7#n5aJDuOXuVrr!sX<;CjWH&~@rTBt3T6tm@)*XH9CpwL0y?2YvD4kEMZI(y|J`=JQs2vQnp!!!yi zwXVBl&%V@$lf3khJ@(O+ll^dp1uat`{%G$toH*=+!ZbrZn{hpD_`;9(?c0+kFL`61 zwQQQ}Akx{~^{j9yt0<|O;{7E4rF)G7nzqNe$SdB(DAeIawx|T*qyPxs)}2p)=0Qa+e<%IM%kbAmdN>$|h} z`QJY$F7B$yF{V&K>9KDAG;48BaxZa=^pfuo)w*az zikFBua&L|`o2E0=eg+F^?2BATj5OH=CCZqGYNlqHQ`f#xAlgLGnD*HP z`&eS$BTzYxtC2gylt+fS`jnk$*^IwffUktYQS7p$mS7tq&ozn;@%Z$|!tS8e>LM!b7%f`l)DHkUq4$4y>Ki*Uhddf}BpNo=!%2$?t@;J-I**ly?%`bKORLO4ERpQOeEVR$=yNk(lTyKZk?@rz$N36#g1o1P;1>R~= z)K$Vufiuos71LU+s9%%^plQt#Ns74N(tSb{P{a!kaH!bB9`@MoVk+blk2#*coH$Xe z6i*>K|3;h_rab~~J@v7hr@l$Z?9(b&J(6!a% zsX5;po@K}7Y8(xVaS`DU7$PWh$K8>feUUP4R++&cV#vCnj^Z<@BqFgphN~447Xqrd z0siboKN@_Z>uCo*=r?^aS%h%EGC(%-wW=I+mMO&AEU>nD-GCe1(uh(P$< zU9t`@mmrX12WOyQPwhbzyzs64k378pm5BinalF9W|AVU%0IGcyf02OQHGxCo7Xpg8 zQt)@6e~icmt@w}99IqOb5R{g{stH5zRm^lB8h>xRQ;ut6HbCG|mcp!Ckhr;gaLA%k zlO*DRPuH3zCbqoV+92$IA3=0@3}mOQn61a#yQk>F8!fNZX#96AE${)TVkpFr?=T(RiM}R(9BAl zQ8D~bcVRWBcu>ySn0V})v^XYw%hDSd`M^{2yS(kHQwN{}b@@KTd%cDC4V#LkU(Kj{ zHs$`)(b#sHrs|1zi#P^&QoD_M&BD#MUk%7_(7O{z6{g#sG{5tkk;^@h>Zo3#y=l-@z7Xl(ZhQ&00aPVt|tZIy;>W9gx}JgXPY?el!9KRA@oI&&huB8XM!Pv z>{BeIP3^5O(kDxw3KG7VowCYsJqkkdLDr^xlqmIq?HY= zvEd?ui_B14P&&8Fb8NaPywQG02QwibnyNY7?;}S4VVdvTL1nm}SslXZytGBpo}%}H zwT)pnVL_`Of;C%R7YOr7#VEFlo;&v|V*{Dx@#?Jmi_(p*eUIwc%TOGWb}>x+a|`#E zKUr#mOHrZ4mY)7@-V7mfSvy@Wcs!xS>z_-XY?gU5R|R38w>5d+&LfYIwPQVUf?5Zk zSgwhjmwp*fti7*x6p2xn^(v;@bOqH6K{HT|Xn4AB>o9_$%+RpNx6Sv_>~garnYd^Y zX{&uAHnIG^>j%b&ezGQ0Va~1>pMpyTU|ovuDK`o)v#Yk-wYr&6V~VG=gt zgn}PdY#_XGKcEdnjmk+k?SEa9QKWUIgC_2D>}tWU-l&8!^5p|_{U8knrCAwa-+owx zeoVU=&oo`rPqq&%k+^++IN$nLB6M}f3v@zSFjzdbRG+jI+Oh!AfohH2Tzya#;<+BO zE}Y}GPQ@h+OM(SOq%j5jWR$kp2MO`z^9&R7kE;;-6R^iqUc^2gn`eSfpE54_sx_jlp2&Ljr@4CddO8M zGGXP{5QkHpFI>@LDe~Cg8){^kdQWb>b)LW8m=3Tf6<#mBe-dM@_MQL64_=*RkA-8)*w}@Y(l86#FX74%uF#m zh4&wKL&#QzsC;DQyQJ<}Di5t9h_jaeOp6udAH4dy=Brqk5_n|u07-u~7gd5a_?BL< zaePPe&9^rDOjKL-BOZ)MPADRSv)ZFrv`Oyz+UT0&7i-df#ZhW08$)PQ5v}O-#b|88 z0^WJ8w3Zh#6}%mutPF064oN?wro(+bb> zop-22Q$^m(4he{UK;s!jX5EsI8?8_FagA91JcA@&Zc1mY+|1mTX&Cy}E+fFqwtj*8 zB>bvr-hP;+H!LyH?#(&U*Orz=!jCnuw^`I5-u=o9oJ-6r9CKmpDf7CqHfrz)#t-Vf zQb*|dy6@IblMrN~OO!I4N}Uq;Hq^ZRKGGh?j7)p6X^vq5Z3!)yMV+e&RVi7yP)}rrEsZ+dJ4sE zr^jgKUPQ`@_kee|k$`%cJ?$?#3C!ob(#zs#8mu8oACmW_RSg@oEEId+n+2~SPYL$h z<1g-iYZR{mBV)z+a=g(;y^0@(*`MrpnS3RCDj_seeCX$7j9g>kvb4hqDGgNZTGIEy#X9Csg z_1E~_?Y{^QtrI4(#-ZT&r~7h^W8$KLh70O!qMfQE-k%o&m`bnS-0LUjm&bRx#uS}x zPh`rN<;}?1vyvIpXGSbqZq2?3e1u(Zcxc|)88yQE>sQ}YvA+Jp$u+4Fwny|f0_4;O zxN-N_UKnb;-wE*%nUtpV`Vn1o^J&uSyI=dHCPXbrMr zgDqAm7v|1C$9}HizEuegQCB5g{oYyItAHM{$MYJgsT0k`!&{RT+hjbHSm#}je>azv z!Y`y-t@-_bAytG@D?#)mz-|j1w*Qw>0pjt3S~x`h1y|fv#rGE3u6rNy=K;z`_t9`4$iu0jvyA2?X=OSrfRq8#U=^0x3-4G2lmommF^$KSdM-t%UgN-SHNC<%F-~S~=;Ma+;fb?J<;L;tof`Od= zFb&>aLxz`9e>bEKkrM(&IcF@fQ;;adC$KV5aE#T z?mf}q1xdK7U*Z{v_OA)Q#3=A~C4ZRSg~2dQUl0-CfyZlq0Ox0%765)`|0#U|IUCY@ z4lVA~nll*^QKRtst$FvSHFTy-BAa;)%I;Y=*IyEnISXA=NqVcv<2@MS#*R#Slk9+O z-?O2uZzrElC*SIQ|3HsjaZ)JvWaj8*JR_`<@WrHvdd0n#P2E0%0Nv_MTgIMvGdT|8_7-PJ7|<<(P5&a&S+1)0b#|6@uA?=1=*3H%PYC{G?&l<|9lWy9W{q+XbKW2bP^_XtNU< zV?P%8(QtE=fBGfqy3$JX zxo905!CAA=Jll3~;ybgflS0-;OWTi&8cK$NG-3AP)8%&PpT|+^->_ud=EUUg?gCjN zXQfJ1uPfh*`0xPwZ}9>}#=+xMQp~&%$eX6OM@NHM9GfSH$;e-so=}bD@f<%}e){1P zNmOtxG*XK9Dqw|UD)|_V;6W1Jy*?~9Tb~IPO+ibo=qyng%$Zh0tK(8sXGCE_ch9n( zZ#|z9^mLD&q@XzI6@H)+=rOICac<)c*4Ufbq1Aq_Xg(jWAh7SahL)-{ua^-H`97@=esI?+vXXua-A!bi-bWv+?!E8;+d_{eH_}kLU zY}Of0Rwj%Y3$qtVhdb5W1BEO&8i5Cz793=`0z?f_w3m4IL{WDxIg#G2njylx#$^sY zvD!&h*I2P|_;a2`=Fi2_V*wHBQ44a1&FF_bN2@<{#M&*wil@n0?OrXSQje)-MX!~- zaDZBkG)u-VXN^Fb%y4DMr*IL{c4RfQr}E-ebYvHLu}uM%cw_tI9@Xs(Ce@dxD_evpvHK64MP)AOKcV1Y7_Y?hqTmcV1fBGl1Wnv?#3m&X z5~}}@yymOJWZB436Or15l@5!fDHERQWCi|{4n`9Tirq%r&TO)f+#gm$>N-Dzx~A5VM*Z2J^%S&Ju9z=$14SLRwfILt&<#W ze8~qFpHijj&&&{BYW3e=tDIw|^#U0K5ZyD02tZ#z$% zoan|xLc-c!Hub$G!ni-|q?v2?*CUK2XhTp>hC?M2o1i($^KiL@yu*F+)?OOUQH3`O z$a>8rbDo>Y^0B)pdWmsdT*mBSN(f%9LNx->kOsdk4NHkWIZJ=4+x7-YjXlY|_rG#f zwg_J$b}wyqOk8+9#zvpy!tq#(40Kqb!HaR%ifc)m>DcG?iN6$Qy@z^edR%h9fh0=0|7^YLU209V;HhRPauA{ETCRZG64U$Q$@`&B z7Y~o>b$jkfTbtXMvg@Am^{j`J4p;Y5Ts!0&l}%ubK7Uzq0dyTxG&Wb-bORr$@2cm@h1`sw{&s-J*Q&B;7liBQGQ zoV7|KLRk%(rz+wvKI-%cqEI!i$Q66KS<)F!CsV{(7)HfYhP+*{rOSa@A0{POvcoJ_ zNsT^}$iB7{y8PO@L8*a3r~U4wvSu19hxY6APT%j|=pQ;j+xxije_$aTi~n{n|Z8iJWE!4qACe`}E9oB=)rSbJx#8v#B5JPFWh3>C2NL4d-|l{1JD zxSXV^Mua59#S3fo1~G%~KB5300ZF%z%iRQ6E8k!uTz|^`zuP&IX z*`%KSBI*$DyB|f6(X(%N-kCjS;@jTFaJ%ljb15We6JM|AfIM`mTH{mCo-y?bx)900 zoiQdwH0f=4bmhLIAjOm5ht^^lJkrFI$anI>jhoh@Daq+kiqvCLgO}isc>}By$ ze;%D>jD)Ce9tD3@Ch$kC&n#C@9{CdlN$U2#9vO4Bd9BWNt7`9|k&M>E!ddfF>?eV+pL_XJ49DHdiVFTi-A57KqZ|E3bOlCHLl_C@jCA z$m1WK_un#&w~Qqy4y%xc@3O4>dPU#;o&N&MM& zisUm;-(!<5&J^qtX65kravjj5C>F<=Kcz)GArtkcO5}-^CQ^tR>V~TE<-PPx4vJ{= z=xmKv0$p+@eP)_cEtSPd3MW(de3-rXU6Y?L5ie1?d}2B*?i-^C;vvDj$M?3_X{@q` zshk1H(8&QRvY^=@O*=-snSLv{{-9K<|pZHElW%rF-4TDd>RCc4mKIb^aVE$ezM6GJ2+ znN*>Z^{*ZT)~VUppFWsOHd0wsExjG`-wkZl4YDucPUy6!^>(#)H$sEDA zv$30`{uI#(^fJ&yHmJkPXLlY+T286h^?vI@lH}7f{imyoHT|GyneUoZ^aSR&yK@sk zc7|laR-KXW-o{c^BFZQhlOxcmT^7d=7p5q;JekLTqe0jDK#irMX1<&oATn7t=7m+6 zp)#qT1u?*v8pkMhvsqJ%G{zo~B|G%eTryT_biqL$%$1k4=nYNeNeP+YpiCE%$7>xc z9wx+;#w)*aIdYs>f3U`|3{?zIflJ9J#s!|o57mGP5K=}mQ`wnHt#J5r4=8Nowp-{R zx%}NHk^!QS&zH%?&jh%`$|LFi zGrnk@C`(gH?*76yO{LR%GWw9d?uGaCqp{bL&vaFqdOG8p8Y_3JB8;g-@-;fmM=AGq z^HQ_3Rh79$C6=B(h0fHIZU^z+klvp!u!If00KFjkL;ouw0pIlCb$ze^U<>yKoaYN- zAUaqgupz;7YRF(%Krb4~Pi~A_!0ck;JeF;3^gbILiY%fV&L} z77zoR@E5&7LU6Xt~tgn;N_ z%ich7{*RG=oHl_I=A{@)QG1rF$J4Z{if&m2JQ%M0j* z>wG{WK%L=_G#exczz)EjM8Xirolu~VY(Z;5{xrMla6Dpw@2I9(MfLij^j1Mq5oi1< z{Wj0by85Ys#QYSx8~?Gn+C*mh;_S}OrP2MSXoVKdZE&k}9-5Nz;QSz=02wa~O;c=OBcc407V_xv^VuyLdt zeQCzam{KaXu?xN0-~Ap^tF!cCKD*V_;^uVe90De!QJhAdD+_dV7*EY}KXyB}qIib! z@pb5F6b=%J0?rdJgYT3gl{2pG&VG*mn~Q@ZRLcd1#u@KWeErW7Sd4YV*zt!s9m~WlTE1~ z)a6Zr7nVaZ-;%I`No9@s=cvW_uz>OIFS)96OLQ_|*R<~UkJ)OUbd&Y=4VxlG@dxsI z-WRtT*B2QI=X=kUDOPyta4$~P&Y=KRp+vX-P@1HvOT)zu?A3^Y&*c3MWy)_z;Gx}5 zp7XGp(mIFLXNG!|`8jS)vd~b(I&t2>-Jz!b3S6hzA3Heq)p(Sb&oN&au0;4g7!QlV zK=E097-S-g8S0RTttz6t-AG1_TZg{;=q19U4gR^4(F;?k*(vHxugv?s)0Y%RBSGWk z8fS{e;)$jx`8up{IIU4O8sM=zfY}$d%MSiPFR1N4kRJ8`78JklDrmD zx?#!6o4~YTsa^DNOQ+O!P|LxDm)pzzR`Id2u8XOd=o;2K=^I_=9#w9rc!obiPot0I z3w5W33~Mo#%rGSrmiZ^E_KXCqSmyWWY%?5&uyzHp^O6F7`LLxxE%o304DdLc5v}F6 zbzvv6wBruvmEkI?b8{Y<+yH439mivtXs{mM1PmO~{dkYJQ9t_enTYuX$BvfxWGG5O zi>jR@I=vxDi9kGppV221?$T~9S=~Is0K-FZG5gXoC7u` zA|$U3>z|Vx?@LfvKszhgZP3MF8e^MZS*ut1#$3WFno{~QH-ntWGk^9(hLg`>O%nPR z(A*F7VdXbJ7*|ccLh|t_p?yckU-;8VHCM_ADNIx*&mmo9hvigR2NgZb7e|i0m((Eh z;&U4kVT#UNebIAPOPfqGuX;#(bpEoPl8ju z=4`F!SX@a7co&8=_tm_*G6b4$o=bo9Pt81Zj|;Rov(VaP<>zz?`MxgZ$r?jVcT|kc zO!4&f`xNOWD$O>I1@r}obT$PlexE1#1*rrLrQOwcp`UJ})L`x{v3m?s4D}l6R@x)h z!wfBM)7relmG7X}&X?yHYu9_dDdok*6&h_l#e7B=_-+ioY3R6l9ZyqZ8PPQk78rLV z$u7Y38t9FlVpuQG@Oi(p;u0{OCy5Yx-hMTYSUgKIuPb`9ICyP17l5nM{3L6oF9^;1 zf=IBk{i*G2!|2{XS6~>RaUt?g#9?5RVWT7oJYmYmmynw`ojLP5Q zV0>2a!w_L6O%cL`je%Z7u&Jev;uAye%#a`xaqoO8N{w{HzVMg&0NG@p*Co|+elLWi z#ZQ-wEeI(K(1ebcIv3P?mA`JkTjo#Bw7$tzFzB+C-bBlHZ?VVJR%ygxsz$u`;4NpB z>9O- zoZ-Zg7G}zq%_Lec{*p&q4N)RwLX_W~D6%z7i(2%_&LW_B$$=hI80XKUUjc1&hnX$B z11Fci5V*Bz^6zX?R7wO^q*E97*EWOSlR2^?+7R_J%06~9r+jT{n3v2VsV7BkRfC*b zo$tQtGW6ATtwm*gGH>@rX>r1q(~VG8maWsyix!V>>N+n^ZaGdST=0t>2Rq1dnn^?X zXEO|*`<&cJ?rot&BDevJ-=dxJuL>0gox{ZK^u zyf~K;SwvO#)~5YXxPjzQrq*k+WaX5hCy~+$bsgutxFqfKV~JxylRL zuota^I<5>DXuJMr;sHvBMJ6{JX$1WnF6?JuHa`bDIhn{+ar@2B+uJ%1KXK(%fl^Sj z_gW6{dKfu0abKR~8&EBfn|c>C3C<||0Se~ zc#`f49sjXHLGH5owCEtOfv+qK*dmCCn_;g!MpPT7G?-gE)Wt1T;99@9nQy3 z2LZ|jLP>ba@CHPH=_nS!P}w1XOoKxo^pFa$cLQ2eI7|`-3L^YK_KhGA(E|ablR^Lw z|69a~3>%3B(Gx9Z%7V)< z+w>M37liQqf9Rw8mmp7aa5tk~C+<34Yq)DWk>n_ zJQ(3fg9^uLG@Tp=M-PM(kWDqh!aYWI4^yXrAKP*$(Dao-7OG*iX16tu5i+f`e12T* z4ENZx=KMj|S8Jhx)NfB9c}fH9O)C}E(0RheFD@dPe(44fs(8v1{;(c?*4@E~I3a`1 z&0U20Xt74uQV%@B;F`3cCnyIj9k}O?znaOmDy>3IllzzkoI&7%rH*j+xsM&aty6Ep zD7?{wnDf_(h%fPGuOsnzWd#Z`aGD;!$Q9ht-)knUkKq~1Cp*8rx;g!t<|M*=3l(M% zBEl4=J$NrOF68|z@XF^ZLfF9O}SLx z&Dt?OG@2#Zw^jGVtw&}2kkZcUCnfZ0q}H@4Vs{wc+d!iPru?ign8*s9XNDgV$DIv@ ztvHS7JhCv~2Ubz%F1kOBDSqF!S$2D$WMF16GNJ?p|1IU^>CMcuJWvYkM+Pkw4|_x+(tj1Dqc!o(9kt0M2SFx zm2zq8{J|qyyQywDXZD`e_e&J)2^ytEDSOk?Kiqbu*NIWfr8e)OKNP4~t=NCW*%sLI zxa9+?QuF-RX}llGH=m!sbECx(l!rnIEJPgyG%V`bldpH|7f%Ah2h7ZkSRXpsKN9qp zkR-@iZah!EdB5c8M}u(|C36^XW@*bxSjZweRBsRXqz$tQ3{6%RKCgZ{8r+_%4MKooArtv> zd{%heO2?t+J91SgA}Q3k5ZwnprIMIJpiVSB@Evr1h5m}+*EYY@306WLRQOy?Gik>sSdkJ(~%7JP;PR*5XPAWlzFGm?g{1snE?rAM>}sPe(S5$fd^(hbcmY`nEsO- zUPgXaOP()%S!gqO56_m#{a95r#%3fleJ$+-hd-bY5#SBbQ3(cgMr`P%Oxrea`zEH# ztadRGVxB#XSEvy?ZZeHT*c0V2itseFj17Nn*0M`RVzNKyVSB!PBBqT=1MR^RUz;No zZRK%ZIovDz<#Dln&lv|aT(OJ8xcrIar&YtYMmWNkBc;M`DJFx7`l1S;V{hL!{E>UV za&68mE2o~2J*ORvm^D_`Dv zk;0v{NhXEVNu+{zMOxs+fD{lS2;bkLV>Kop}K>Ec_b5-?aOGP66Hp!UOCW@F{;&>%*rU4H6;oa0$ZVGl6~S zRr>F+kGr)%S{(3wc!0t$d@cLmH?@Dw4FN2%yn=V{a~DPe+y`b#4QxOBKwsQH-r;XG z>AOcc5~2tJn=vmeArnMMcxPMw>%Iv8vcMEz-v&0Su}pwh|2=|wH}d=I!<;ez1MwfB zBW!>o;SV+gu9N(eAmIg+2mezLhriz+Atk_kSVqR(t^YbS`{z(Rhy+O(U^z&BKmLx^ zM}dD`9vEIcV8#9a!%ZMSMFCzKM}!FfrX&oWI;{&}K@Z*z7*XfAm|sGx#ZNu=rp-EW zj2(SvvBPZHqr74r6kXls-qJFEjLrUvV}mQl{y2{{kpWtu%(9D-e&cuM=nd(>-D2{X zh?Gy-uwBDnyOQFMVq6QC$mTd1Tpk@t4##1Cne{;8*6MQiX6Xl-FXCF03nC(RE%d-H zmHk;9!cy){y2a~H24kzKM&Pr^TE~V~E!Vp17g@^_3H5XXDvPXA!a20QTG!!2&xyl# z`Vul(>a@L@Rwu}@#cIhPB%2QSSiBWjj1Hw0Oh@M=AM7WrNF2wWSfU+MJ?@oO@zM`= zxV%q5aBGLc4fJS@DU_U4ky(F-75&qqFv*dDDZ7YT>uaiz2EC^t`}<5G2mOnj(>#xJ7B zP=)(lg)`}2&@b$zMy*M@H{y?dDkMs#y(V0|eR5p!Da61(lCjZNuJ=U$#XRcK?z10j z#i3$R;#b+4=}cYpZ0x5601u1}GDtUrQ9 zia?JaG73&=KhFF?b@SwPfA4w$`TDAZ;Ug;(#ur_$cT3Q=3w|9sR@n#kRxXJ1Xj?Cvd-`;^`22RxPr8*H)jdKN9 z-VDq(HrLRncSTt}*FgKk^Aq|rtB7dQ?$U_i@c5pZnKmw0>5Fjt#~jYBU)k0tW1<90 zNk7biJ{!3S4+#kTvPHq+9LDaldP>`OF~ad`y-+oO{L@AcBbu{@=$H4C)0rM~j%AKj zD=!l&5!4Wr0?^yLYAlm)WNj`u#Z(sQb`~O$;Dryb+{AZA(VFaInpA1hNvO_6(2-Bbex2p3)}{p_ zX4=B;_s}o84L<`l6Oc~vFL|gJlRC+kBYuS63#w;a&l7PY4_IN*6luiDu4pJ$AGKhK z-i+V8zp*I)1z$G#8i(Uu!l%=dpVen|4JbxWGA{Bjg!4JVq}A7AcZHzV^mGW&^@fNF z`x!f~m?qL36p@cSsH=FxtMr7Y%;$lbR(`lu!tsl$&Lq8f5yNt5Y_^9cj^0Jy)e6)sDz78YliMO6pgM)5yTgjXHBVw^J zgZ1gY++AV9(t(F@-gxqE(?qoiqKh%}^DNV|IZ+Ktam5J%qj@0cq_SI1EVy$69mjW^ znJ+fm|BEd{@IrS~sh*c)5=-YVkxo1rk5m3&$Td$4v%~X#0S}5n)JnLiYV`GV{LPGinN+@*O3G(6THO`HBB;|PI+73$0){*?;y1 z1h%Sw9I*jDy^2%e-=NfQp&*1Gjw*noHemPr{qwg669Sx%;h+Dy0YhL?(;yXUc&?f- zADtlJp%etNO85X10R+f*69hbz0)I&$xr17OMREY7OaYHz;{l!k#Et;QLb#8UpG%NV zKmfu8OPKqOvtWPBH**9j!{Q4-=md~Ek0pc--a`fei4dSJ5jO|a-ht2&xJwfe!heV3 zk~-|sN#I9&L0}T#%>;e|u_(aN9s21VK@2+a|S8xNY3d#Vn!z$nYfxQ6L9RTfc z;b|s(|LtH5%gqOoz=PNz!1V+_A}N5h0RJ<67jox>{oniF{WJWpZ3CbR1n!#H0NV`# zBK(Ptgs1|5g#_RWEQ|PpX=1u(zihji*c7-~Q%2m% zw?Y%A6~+lUi9>@rx_9Cpm$z!^cC?lA^9Fdv(=RjjyS0W?6y{wq1r&u(2zx!izE646 z>(D9tHqS@lewpb|nRBZCDrfGqu#&j6-EPWPe*2S%=q8I)H8+Lv8{AHR4No;n8T(RjkBfE}tD|eNqui^Mv(2_q7B?R$uN|3H0Tu(=9$bu`> zLq*A#96v|UEf#9BzNC1p5~6_j^ZN$$WB8Q}=W5CT;Y}NdoNir63vYBjuhz>7OaXDi zGFKIq@&^%VzY3Z?xdzVgPDDs2&jL&ObF|lW;yy(@39N<4E&Ux9Td!iKY=LKK#l+$Ep9%GDS(k%xEr)<*R+^dZ>X zov|u~*U6+U;1WSM=!lqSE6G1`aWT8*k)hP$to(F7 zi=DaTM{{JzJ|s?G&*%;O;(13OobZKYeDI~pQHm=PrgkA_%=i+(l);{EX?6Lt^KzaO9ThbkXuXTR5^ZX;%rwIqfD$?Lsr+!a_ z@I}o%JY9{tP|-#^%J<(OR#O`bf>m;!B?iozq-18D!8^(4F~z#InL3f>0(9o18#*4s zs7c~q*G4xLFird9 zYyk%m+y1@w8n+Y=o?!+Orw6kW!Ehox?e(s)<`p$Q{ysSs_FdsaYotUACANLpw>{QN z^KhB@Y_|5$L@!(~I(f1s9mVYyp%(ILO}$`yEArE$CYh`6*@+QH+`BccINdvjeS?)c zuo5>(Y~5>g&Qqh`eg_;>iZE0K$|)M_-mI{U`6@?Nd!(~>Q!1Q(3WSa1W3A&|6H53y z=e1qz4{x-+wsKn*z0&bRb|-LSBV*aPfB4?Le(i3Q?&g55WUm!$M$XW~z$?Pz&gM$H z4Nhp~N4?E2y$KEut8_FsM6-8k@|Ra4wX&_6ef!?o_3#+?Y1|e3wfZeyF1=8G(bg%P z;_mZkhPx$WR4`S%+*!@XKaOuv{l25C_=Lw~vlpt#Wu|2%hyH?LwbkZY0exKJ(0vgP zFRvL(iz94r+W0o)0}?11a%~dq3V<9!B1#a0QGxz%q%bglpuYAO{{Kwg=V%LH&-?tV zeo-;hbOk2yGmrqiYX33_0Ot7D)GeAS9IS?vt$?V3AF{|Rbr5yra0Nsh`DzgS?iK~v zSA!5eq}OYRFjAonLV_+PjG|FP9>0c=0Ekiv5ea1YYY1y9f{_HdRSwZWPIp6ah{Ry1 zc@*rRfW3DBj(FtSTYe$-D`*@1pq-1 z2Yc@SM+Lsh2cTCdBEU?+Ylt~AuNQ)YY<&w(jr_TaC*0nF=G0<`=i782y=H-MYX z4nVP92`B{mLA%~>A)3gLD)3oHJw8ayQ6Btc%Tf@m^` zNrF%HQ7a7a!322aAPmqZgaDX=7?@{alBo9sbZ1&Yey9RH=e&d9Qi9Jh#6gM$3{3Nq z;NTunRGx#V1ahkaECr0KL4Q{7Ay`ykS#(Z@`rcm@tTQginyW#Jmzx1I->V_&0O%GD zu-w2p2P}%fatwtnfm$p90Dl3n6J|h_%FKp|RCou-BWfTT^kCOW0(}1u)*`6y`oWGD zcyR#IJAm;4XziEzb8?hjkrcHkOEo}n$-&DK5r6;zL!n}Tgio+i1TRarIOxypk9=MW zIA(5vU{i|1#0B60Mnf2I4zS_CTNZ$+dxmpCm+D1JjFhW`XfT1S0q_|K47^W4fnxw- zzzGVS18mCy&d#(zd(Q7LAV=l`g^`N@O`<@2FZ2c0gW}?;B=mHtNIGJqOFcxB25j63 zffW)2qV&S7|4GF2pLhCD!>8Y0CPeNbxhzzn{X8J2Uv~) z8_=Q)OCawwK+KR#^&qq4|0pla@Q3ou3`o?F;%?j(u4J}FkBR5)&M*bFe1TDCTL9(U;?6!;658&x0=8h1b&Q4t^upp|1&8n zw+05@W}(DCw<^hd(DUpE&@&iVVkqrjSRa*SB7CO(b2ohY9>{Z{Rgpim7h3&?7Nt{x zW{4RBYB3K^$O2hF7i(^i1|%T~R?{fhVU$f#UQ2=;Zbmsv8|WTH2QjdB2Lq}R2gx}i zD4_sUU=_%I_WBHlsY?rBRHz+Lf-jcDKn(k1QV}prQKxs+L+H*T&q)hl^xRoQP_O-f zOZ^$BB4D5jiHM(zf4dv>pZWn1i-H$t09hJf$OwSn55P)5_Y#10)E2MMxs}f16;~@@ z0Ynj83IHAr2jlLKJw;IO48ez2J;0tnTEI|gYz1Kv*b|3RM%p}OgHi#B7?;S|5LSfYad_dTRK7lpCS5yB; zAJPumJbH~vfVvU z8W;3PLDWq04$%E^4RCHL@GvlrFI)o#60rcV%@-DWq}c$V)$av@UmAkol7e>*Ac&v6 zNRR+&Z!jS^vTqDbPG5ULuUemgxG?at0*DI~BLqez_&EwdhR*duHwc=#{qfHrLjey7 zgBj+JASh^Z6!0X9!!uIKK8PA4N<28|Q5a;#2@8Rr!Eg}90h$sD@>5LcOl&YAkRV_5 zowJAs^o^SF{tPu#L%fbK2o7EF=E$gikd)$u=15yPmtY7BI`G7c69XCxFOfW&_SB83zh z1tYL@1ZWR$64U^<7RZ8&|45M|VEY0o2HyQ3UB*BS&bAuk<^M5aE8we zI86uS3Vy)``!uIB5I$6*02IV7r0^Vs9tw6U&i@e+m$>)`EF{1q26RdI`4!|Zp54R$ zT*1kMsOKV*=T~rAA?h9sFcNS9Kj+1fFXtfX;CZ#u7qAip55gB9ln~+S^aaQS1RX9? z?Y#tvfFNJYK`vG2EkN2K$d*-zBRX8Pn)fS6h(w2rRVS@M&^^%M;>ar|PzvPxFX)uj zjYjAc=;Y@cLw~)O5ra!qFPoq%;G&W*{*M}|Z-@Sr9F9_u-rvNcLP$b;bT+6k0E^Gh zM5h3`kl_6m$s#%*!u)%RKw0xNT~H9gj~_oqRm8QtgK&OO;((i!IRJMvh1>l7XP8r8 zj>$^ju}Dz(dgZR!?;Ene4ELUJ8jV;}zQM>gQ;odh*}Kl~j*jL~P*hw0LTX%>ruS86 zsl5X(Mng~SgOX<)=B2$;x2^9#ewdw(rqIz%%Ix9*nVUl^M{I9zv*|+F-)mV4DA1XS zqvdAQ5z4Z$4K=YN9-{R&o;(ZQ^T(XLjRxNkWHcnS=DN;z|8r>UX+_c9bc**MNAau) z{L9f*N=nwcczV!Q?b9mJV1~?mR3g~7pV!wy2ha;}-j<`+ntpab!g|3L%Qti*f{q#u z!$hLS>;!Yp!js^H?tbc}{ppr1i#i$Y%Os!k?JPjdi2>5 zwDMo|#T?@;$FEXOMjK;-$LbzJu9TsHzC002uxfN;bc97NeQ(2zUlX>UFXV*a!!!lX z>jNru&fD?ni4Wt8Bhz1e3(edcM+B@`1)!g(|8msQBD6$=MxIvv*2Y&5H{!!~qht?E z+RLHz+^F@q&CEX9ea=2+45>yI7n+?e#9S%OE0ZjP(7S29iRTp_eXQ|EJI zH*_ff9IA#un?6l&$T*cvIi=!0v0~XrM9-Yc`<~_&o%-H8MQm(0#bg8qdFzPz-D0|p zSf_q}m?8{?%GVi62AhbLce2S?>Qf%M)bwp}s&~eW73+2I z)9A$L?@U(E7uH}w3nc-PGI@ukH=RUbS z(89Pw1(`b4F^S+mWjK*GKtwtj7*eJVj5r*5Wqg~x`+P%j?#b7souu#bYP#rVCo5lG z3C+*XX`?YY1~ST_iEvRJ9!`g1xAuQn-kW`VBu#wS)Tid?Dezt!Epo4JV*Ir1L_QCZ zn{aYD0YTo0?tU;f?p*UQf`69lyF4c1+2tt<^BLTe!=Ft?$Nk&0B3=%^y1LO_maxaK z3JnXeNe{gw6htg$KWks1U!$F>sJ=!)$XAF^xtF`A)XhuK+m zK3yX8T{pO%UTSG$2xBh3Zo@1#@{;U%lX8fnj22Z08HVHZ*aBJB;)i;($-=Eq55COs zjh|Sswl~~Va=juVb2@DIlU!U-A1R+m`Kmmbu|bR2bm52A@1K06{P!|GGMK21j?m#l zCQ2g{5F>T`pW`b%_hGLJoNOHKgs0(ULX#Pvh^beLSvB z&sXk!vuj&gce=xL1ur&cJHL6=Az07~E3xq5hnEP-Z-zK5<`3FbMFRwC7Z6VgVmmv@ zbdFk@)-;0-{CaP}p}#M2=BzR`x3d5CneHDkTSmWW*H%q#m5*4yBsA7bUpC1S!tzBE zVuY}E`f;o=bZn>J*09B?u~S8U^*!tWO|?Ns=0)etYrDLOoHhnsB<{W+cG)>J^dQ%C z{j%&4bJZpbSk#*=)|_lBLuIn$alrKI1l-B^2ZGjX zIO`QctWPpi6la4|%K5#vIpxSZrR~DS7f-yG6Ib&R@{Xjeur?jNrQ`}ZmM;_CeaJD8 zo$@$0-6w=|f&>>~U*cD5#_^cc;~T}8v4gDdM?YSIRhFg)b9=Ba4bcM%I8#?OVlX1b zeDD0S=WD^2u)=4gD?qR}H5}8v-wqqG^y9nkd#uw}gE_OpLu^)!LK9A?2L4D9JNnHQv zM=bi5kI)F<1#5f+y7g|Xqg*SqfVP*ty& zB)>%HXMqp68Ll#ma&x!6yPfcj)wr##mLPv;l0E)K;AG3z+@=?#IN$gZbPkW1I}$Zm z1&bsxZ>Z9T9_@^G?s1ha*BDug0_@w+vi1p?nb&-&R|=Z-n?|S2Ep+fQ>k}I1I!scX zHJI=qz}`rqx7=V4s~@?K61+QvSD>ReOr(^10|1aTPr!5WE*Av&e_Z= zD~pqMO^(qlf>_}JFT;c+gk!HxC;!9Em_42oP6t{H;ShROD_?V)F?or{HA-qHiT8&2 zo86p~{Ke%E)&txLJKQ01=4f44ZphSZ;UR?DD%|IU3jrKZK*x z?Ob_{LVgf3g`4F(^TNC`xF8lgw6`2%itm}{*L83M^Gjp_zTwu~nPq@|R+j2{C#0Utb}ge7 zajpMXXB6{XN%6Z6C_Y;4= z)R$-Dv@e?aaroCb;*B&9-xJ2zRyy?Syoj5evu2gH5Aw5(IKBw?wv$fiw@H27s{3)b z=~{|!EhU5kJKtTgu1@P%Rf?0n)XZvZ>9Y5frK)?D z=+##^AI-O&?n9PRUHE1B9-Clm@iv5^B`+^?0M_Q%P&niHBYNf2SmV8T#Iw&jx6;ZG zttabMGM^b|y!DDO{VFbtN=3AF=4T1W9&+1rP5+JvJd4%6=CF+LB6 zdl@q~w;3&TZ$G4ZbjbKbee-=e<0D)$ykN8s!6L79`mLvKa{wd@gu zMfzJmgj%CQBh zSa%SS@6*CoXWD18eLl)CmHRqDO`7rDEyS$3kC>5kqk~Nk#2x2R*>GCOeZpDN`+G|t z**nsTd6*46;{4V~`5JjX=@W+!oJ@Y|a8~m7H>nxDvdqK7d-a7vaqy*6)!GNAs*yAu zNykQaX2o9}-+dtJWPF&;<(nE;y%F2){|-a$ts*|t`pw^nR}yU7jaY|6v%Pz~;huts zD%CjUD{o4x-)^f|F!j(Dy)JtAw1&m}7#v!Eob`hMK>>Zrjw_>TLO@3iMx z!Gb~y)T_)32zvU-8w-;dW+eco;VR`3}NSf+iR?;SHWWYLg zJbpoU&RJQRJ}0rGj%H#$Bl1wGD$;E8>(@b*p4EiA7EiR|`Dq7!V(l4EzA?5C!z1+X ztX{0%Z9R_u#VfSFe>3?;S4V%HDrNp_OvL5xLlspK!uf*F@qxjY$Oa4K)?1V56n2X| z?|S2-lSIxvwfXA$HrFmnQ%jSY@ouIY9cE_c7oV5aQb|&jTJzqciN98exNop%H?DOV z>utSOdYhx+$L37r`bj8em|4e@pZ8Wn^C*Q;$6CreQTcn`_kVIVJP1s>w8fG}{oo)U z!Jo``N9I~pHLUYS^EZ=%LKES=On|~q^iGM{gyf(^$E|h|6fajcA)5_P2` z{Mu+s^u%`uyL~%#p`RKzQ}+bZ^`dM#fV(@)HS?s{8Qhq3ky(ufLCW*HK9L*8d; zhFi#!QEF!~V@aTgm&clW2R}CUu<~WVp)29h*V0JU;#ZcX{l(uqEzNgj+Z^BY{eFYk zw()se$|woTWo5lwFsPU3i;pKDy2zuIrOhIZGyhnFiuM%@7~98<1nV6 zZ5-~OuMMkZb;I`w;&0mh#Z0_RtRz0Mji|sIp2oJk_afUudqUxq-PSA>`>OLisqnxn z<~Z2G`*4dmL!8+PX0#^8cH8uEfQl^_A}l&l>D)^SEKWy zI~oI5@OZ|9*yYkj>^L#Obdw-yA==#mgD1Gg{${5#MoRfxlij(6J*s|!-4CM&q%9HQ zC4P$TT|$G4RQD=Ybgy5dwTWxY_zd%jkWTw(t}HW|dApg+gyp@2RT^2WYT^D%0YQDn zsy80b+jyvc>zc=4h=ecid$dG5)MO=v`w`;?8gr%WE?%$NtUQG^Yr;aaq~d=LG?Wal z5uKp@#u@J0TJx-NHRlm0GkG!Q&8m$bOr>Y0ThH7lEq4wU*(J-C9n#a%i*k#X*B&a=uz7aLmV$M+@dk^aI{D4deb3Xyl(8c}JaDZJL11GWnDK~~HCj`k5Z<=xJDOVp}#_28h8iXmyPEQg17!#OgVBF-r zOjK@DbT|I78@VQhOp4h{4}AvNZba96MFnf_;z z&sxYS37)VGJu`gv%{PY-m#t%C@9|wH{m*7omdF4%QesX=4Q|qBEJcrNIm_DWh!FSG zHFAD@wo5d%&0i>AA6=;;GKC2dj5Q5;$!BZY=18O&wARs`JPTBn`69`JscF$Y+_&;$ za9@(gxu`QoIgQV2O`C1lJ2^<^N+54WMdSVkPHw$jam*{MlFXcyc#o&vfhL1#SF1l4 z`|gQ+p-I1vnJv|UADa5&`EOiFam+vj=id1HJl1or?JH2H#{qy5(1;F zJ&r>BY{;)PzaG`C6Fq(MRm$0=Ea3Jo`-kwhsHK^NcU`70WJ(zxEf`WkMGokc?;<*p ztZI9i7~kla7Ni(&b4J3Q+U z^5d+KvOD?06~<~lbEU59dpk;;%+>mWLA6F9=8AID8)2+`0sh4yN=XMVrB!DA(b-;g zg|V#J3+*8P4LIPb-4_UB$g1nHT>o80bygWx76Ew1U`L#UHAOt*V?Ey3!>~v zjw%f{ZhEnRul(DGbZf!v{lYjgTP1j^($B{qmNBo~k>_S7C8*YXJcNPGq{c}jN^RdY zv>Zf8;8C-#CwO0o){DS$ZF9W!>rUFPj}zm{gI9>3hF?EnII(#K>2h2?QyF|*crupDB*=TSrS-acXzi03t zIoq)jt20#_Cu}a`I?R#0&5&afpo{a$ozt|+Src=d<#_68+>p@VVk`x{)8k`<7T|p4Nj=~@t;F3042{macNQT}q6!rUA`n$BJZ@+M8QdDLOt?_h2kU-czfYC zIYCj%qP&u~0^&Myrfv^*Rk7ITIo2WO`v@8*YzybNjJGP;%8D)_%1CWY9qHk%w|Qrq zHy{3Y3I38kZ^#6Me@DLkmA{Hbz5|R$(>S#n7@Y z*FK^jj}qr5(xzlva>7;TMScwtBT5X?I!yTQB3&x5X~V2k@YrOMu?SNl8@YO!JKmV? zc|?4&Ta@K4^dd2_;HE3=;j>Tg9f~idxYYf$%XIdLXyga~TiS>AFI6#vJf^V&rKnUQ z4};cl1$z%)ZjB3+F;F$lIeh&3vQc7%*W>z;@V$Uv>Cbv?7&rW{7A>^j8V*8~;(oe% zN}Mtp9uZOAb^NLGnR*`LX1$LRN##Jm^Gee?IHqlf`f$C4@OvZQR!c~9+CyDfH*d;t zW>2=EPKvEWMNpulD9_$#eznf6o8OcrDs5crf=fKB9Fl`i$Fsb`R-Xlm-@@YDNhCki zwdHCXmLAph*p2@%KGAhO=tj*Y01I#KYdKz;v#>~}EHko7(3cS}lUKbM!O?3L_11^| zQjXzbiHo1GBztiitZSGHjxm}OAej=r zDlQ6>wR(r$BU1LvTrnBz@gq_Q!l#;4YwX22zs#z&6{)xyIr?*#rIG?a9dJ$P(qvR? zg{1@~w@S8ZrXL(7-|NcmXa0%aCwCl~`X;+Syy-4A>yuXMT8V8Y0`~E zv4=B;*E8poyRa&iI5>N5{vxwR44!QrgXE)uw|AX;of?FyfMeWf9;TI`{oDk9L`tm0!Ll~ zw+L|W^)|4q@749v~s_97{ZpV$opqwCrQg0oO+eK*G)gj1eZUinoP z@5(`}EmFRC{3b@P6*nyXjw*TokV&5-dU+3#!&)9C^eTeOc^pynqR}JmakyDyBKL=| zJbvbFE5FpBu$>(uUw_l1&Dy$`lNp`dx2?SOgMu=Zz>$%3Me;RP$cPk4d5Gd$Zk#UgR;b8;Ah@JBc!SN>hstO*h-?jB=hz7Ul~XXP2%&UuvoO!CeX8 z;#nezdiF(vTj8k{nVP+&6f;TjWSkMqWP81SnW0SBLiu}loIxmJ?o}IYpZ+BSNNu`KYG9V-N#iriN!sRkgce9 zh^5GdcR8zt^4)T#rbbwx?1+R6n>*>+h^ZlGuW0ApHC`!Ku%4!vv}i%{SUiAQ|h&< zDxcVsje8R0>7_3q&Tyw{#K_#+XTDLy{Sr4pa~|)PsyX37 zES{Kyn>@Qy8@uw1Lho^53ag<@{&3~ASF)>GGnc^cp60jSH=@SSXiL#e4_CP7Tsbkb zE;$qq=9=&=$6v!{=J>Fn^b^thh#`7bzC`4Z@04or*a6A#}6``dK2JyEQmY;5YQPlX#3EPxXq2MEX>NxHof?fRDF7(o)MD#mZY&0pBF4tXN`DFpOYGG7%BE$H=$1)Hq-kkk4~0nH#X}D4QKWTkt#jJWZ1V-qtlU zDXQV(9o^aO+2<22e;+!5n2dfk@_^vMcQSJ&`FF*y32n2G+cjn1o42)8hrey$Zgc|> z?I!B4H8*jg8!58O%qpKP8&d~6J$8!QV-&w6vCyrp9bWyU_YsM#*V1dv66T_kTbl5y z#b*_DNpbzo(QHZ1`NdyRdfOJ zT*p^J(>(-6=yE>1l^#Ga;@o)l;wY!pm*%yuL1!=yxlY_$TDg6!9oeoMg##&4YTw#M z=+pUgUKPNUu}n10z-vJ^H~qAYp4In8?`s~170*V{#jzGZj~ zTWh*@@p*j4_v5xr$>QUjtIb0^D{6G{4Q2f%p@mDC`BRU)TS%K3ZtXDy1h>*mvk!}& z=tO?P!S7*KBeZ|E@H#Sh{5j97m#~|^4U!jUl0QA_oUtITnxT`GoqZ}6MR*9Kctis? zka(ma+~?&H2_35ELRITk<5&sf09dPMCq&0<`u-_NA_j0f#EWwP#Z z3G*dfs(5isE5|xRvTwEd2GtWT_Afu`a(>a_Z1+ zKSZo41r+nVayN@C!R$0g>|=Rf_sCh@PENn}v;3!Fu&EZvV5n9qrnGF0yvrp0la9%5 zFVR1OX~32}j?uAmqgk8Y>ORe2e{a*Y{QI3lg1N*D?M&X8c+yWx#P33|E~i-HQ)c38 zjc(X2WOH5S4toQY<4##^ue$t(OER?>p(OblIo}}Z3Qk);zN(>&gGh=z?i}2w-VWXG zXtPut<9uiOKD8^_!g-LxdX^wOE0X-DOderT!>iq1Mh(u>1-8d>cbPC9)lAwBh%KXY zOT9aO3T?cabF9ViEc23r$kvuxtkEf^?Rtk4@4%nT{}RCsNRoHm=;b& z>$UM!;{Py@*ro7n6Gc!B_Q!pD{n7kG!_Ae-wXb=bM(G_&GU`UUNdomR+F-+)t2Ni0WR zjl0b)>@+>|Hr{oUAg9NOF>FnvM0Buv>gfhq;)fh8x`HEPIy*x|(nADEMFy^{%B$^k z#bPXer!V!v@HaS9Y>wtzsZSzqB$F~KlfQ^+zs*%?Yd&>J_=R=@-S2^wbKi%tju^sa5TP$L!!J>3fX$e2(O@@khO?sx@5ix-gr&&hV z!P30HRI7TaSDW!S6NFc~(z<$3>I3PA0b%I^v_Y3(u!pi6C4{_PHx@oKEYOo;U(PZg zcz&o-S-5E5ja98;@3s&9ai2(D@S4OA+))j_)t;C66%F3^2jHXgrsjr^`Q|BiW$g-B zygtI$Ks=KtEVC$;XfSwNx%&fPgK5Yc}>yRLJp8i zidaHe%hBdzZ6^KKL}n+A?;Q)fax%|$)XIp7Kb!U4CjT&ZXw2>!)jt=}gqmb=hY zNqSCpo}jM;UA3^4glqHUTa&XY#VReD?a_ z)M1Hf8?!9eEKlu1#LGFQG8t_~%kLcrqjQq2;F#vEvFePRR$<>CmiZ&0o zGHCCsP#@ON$`0Ee1Gu`cez0vP^*+3w3q6TQR)k@bDofu6;WQGfrdxHGFTKn8*hfgO zAanKU0j{d1nw$2pCC-*2BJL_>5yy!O#$LbI<(hiZyTJ)gl*Cm++y2*@O=}&pC{z@0 zwHpqr6=}1D*1~DD_gy2^u0Tip9NDx!ji=h`7A$jDY$OSha3x=p{;_KDYD4t-JKg)) z2DMKT$GfZ;EfLyzialCSYJ{oE>AwY;Ijb7h>^$6eOb`$E$j{=h9o{|oiUVI<3+<0` zeev<@@MRrC)B3wZ*>$46jWvYswZEl4%R;>>dlCA_oSWVyT#`1j&zWbhk_B8<9`jfC zQq0M?wiqVN{-rUS@iIlOEZvjZ=M_)JnF|>X;JGknHraiIp10331N_*baPGc^_8MD! zLqAoN7K;%1Pp``7moK>!eS|_?q5Qj~r#V+`TP zvhErj#|u}QL{RbykbQ3dCU4pkM?#bP_T9aX=c@SL<^(+kj$^}bEDgkeu7^A~_6qr# zeI!xp%)2*A8M;`W@`z1bciTY^^ZE+*fGM8hn!4WenW5P4(cOz=PqTA$p9k&Ib-N1_ zcjpF2`%bqMw9rd`;}J8=d+B~}U^N6OU;`%W%s;1MZlE9dL7e6p0eM34S!6-Cu{2PPoL69BtKH+^t zi1ns}yW8le+`Sl|dq1_mI+DVo@TtAsp5V3FC?p8#rX%XRBv>m16R~n`y6cLS;Mo-& z7eC<(Vl<4tQAfTkIhxkqz@b3=^tz+w!hB~YcD*26V!#SxLc;wt_*(eujU44yv@y@r zUau7A+|gf=rmd5#kQ4gJQU7f5;1?Yi{G)-uubVV`Tg~~e;koO&X6ufZZqjL5Ot6rB zAt;k0;1kY75Dl3@eYu_v(+L~w#c_Hz+{VE?cmVZqm-(Sa{_$Z$H8bXjn)?ylcp{Lf zRrYe0^t~r(cT>hzv4(2y^eh-{P(hYei zGv#li$J8pTmSdzyO?uWQlwT+7Xx%2P_-qpQzABHK7xD4iQN_LO9KUt1XwDG7_;#KG z_PW{u8$$d3FJ@X(MN1R1)8kOYYFICpUxmPn`FmK6to=B&Cc!TpL|(?C(f4u--@3i0 z5x0~V^t+r^-{@AbKfB3P^c*h5LB!&E>~ViqVfIY{wJ5d(nLgq62u7g{HjO2$}1VJwKuAA2^J97$Q(ZPlj9om(z$1j#9Fg?L(3lT&m({`%+=uc38R515StNyuT_4B#ewo$^ zbKMxCDKb3LE9a(SDv;wob?KswiR|YgewgUx)v$8Z#oWoLJ{njr>>`axwsNcwef4N2 zVd1&jgoRpBq51vG<6f2$IWkwtOJ@5OH2qdh6c0tm7=l>Bx2fKyPi zejr*v=F4RIdVNh?&a$O~p8jTxt12%S#2DIi#6nF(uT>bMtG#1|S8mKNNoaQ#@v)Ct z{;cJ_A@pqU)1&;A);$3{$_XM8m_L)~@Hjkxa05Z#`|C18MgXCZb;m$#rA;j}nr{oS zm|Tf?T1NdH(Vt0UB&)NtDwma-nF%SCGrtJMGr=url5Vy3Ve^cwT(K6#tKc&#`t z3x%6sM5JIFR@~9t;O_P<2E4fHwddm>)iIyx+3;!V z?&Mwbk$U0EG>w@Yp%V2P^G;Fp=?u}boOF?tFe1@P1ajDUrD&dMF+Qp6n5mFB;p#p= z=Xd7lsNIxMqFLuqR%u9I7t-st>aaeMu&_CI==CB@k9d7T`NP?@TZN(#re>@BM+WX% zSH*IXGIoqQm?gg`=fy~j%D*reV<@TcF#PY-V_Wju=Kt&YRD&!hyUx_=cSm?A#f9(~vErvN}W_EesqEx7b*ze~7HKdZ+^oIbHR9qa zi&*Ts{z7A4DZMO3Mm3&mTPhS0HC@uxI*BO9eKFLYB}hALK>9OqjdZE%d6+0kvVN_u z+A^#%BmG0equXf;&6X=4)=Q&2Hiq>pQV(pp@5u3oRhwuS?~hV+7l$jRO0cBp@IT#W zEPM7+SE(1HYkK`K;LfVyp5|v{er6`oCR3j&+U1% zP3v{`l1j5*YmfhSw#FxUd?LCwCHAO#%y%Hgh5@3E*7y5;aU0X4(vDqvjGU9!m&3uU zOgA(oLnXIZAWmVedKt!s&STjP`H$#aUwo!s7j<+k)-?B-c6gz{m`E~1>J%@SC$}Ar zSfY@6s=mx3&YM^jMwKszIfBhNq0wRdt;8#Ego!$!sxmW zDcOFI>wDJij>Qpuyz06qDfy%!Ku~-oF3nE}pHT-^u6<;pEmhB-@Z;7N(_||(hiK_r zJ#^UWSDQq+>XMYiQbgElebq5I|N$7{p>E04u+1fs5$cq(GVXw&!u0GmQP^n3@c5Tlgg zGRqO0DJ>iAoMp7%;R&^b@;{$AJ}{`s!n&q8l!M0kLLj*Q3RZ@V_T++(*4=Cc>idk{ zBX#a#GRYbxg>!g1qu(W{IB9el#N(V1U1hcw@%x)Ad{BMHrriUL6b#M}>|(v4B=@t7 za3my#@T;2V;C9Yab~S8|+2`KuXK|B~gtsx{__jB4CPkBT@Hm>ajyz5jSbos|@~yI8 zV0>To%B@)hk>C=e?MfRfrv#kdjIQyn=M(jC-;75aecn>hLCUA;8#tilRo$3%(W$o+>Khpu%GwS5(3; zuS^OMGjwWKPUQoGo88aS$I$`Y$N%NqU} zz^a2Y(S=meMZqa!AhV|$WcHjw$j%X1|G`=VKrPjS3+RKF(M193%byG&q^}8-6S+qM zog_a4D*1O}k685*DO5`i9b`8BUyW*OU1&H23nT%8G$5qdG{9HMy8)%Drqu^0r6BoA zAo$gpH=x-NB;^#yNd%9mk(P!~egM%2nh-`d7(%&GSw+i+P~Hn!1eHDQyzE^(otVWy zQaYOvRPm4G?nY2>mM#+C49b8kH-Zw7gGAc1l=^?w_+$k2JQLm+%7YX&gOZ*D*8MFC zNKSoVgieVpFoe=0n~kA@XCfJcW{FIo_|zg7C_8^QdnR6(AygivBGEIY0eX;MhbL&{ zV(;(l$1Dbr2LG3vWfQ0`D#^;!6v|6}4#k7_mxOQ_GSl>&0*J*!4&ZkF+lClY)eI`F zaE=IoC#ddX@5gKgvT*?&NZ3202g!ry_yEEeA5<4GkgJWlikw6CTBP3VU_#EBL8&$V-Hr(CoCT3{oC9VN zkm!0=PxRt?0bu}=h*9N#V;6{kgAX8O0L2Eyp89{9y^u@P=+l(2#t zqOSKZuB#VU1?%(j%!}*h#Z|@Tyu9Gzx_@!iu{|#@IlpGuo!=T?T+1)6IQHk|))&{B ziz~6idAYL#)Bwd!lLJ%_btQ8=zdAVrKYRWK9UrOT3&lZZIzlf~129OG{M>pVaY|2F zNzukzj|Y|Oii9{pnJ`iRilF|z2E5R$rbTYslNQ2Jsf|4Ua zr7NIM8!spuctVBLbAw7;P$CQ5pqj}0UQo)^+aTML+8wY369u)H&L}HApu|W)cc6}Y z?oa|Gp(XeOw1bZna)Vw%J-|jby8~%2x-G}RM4q}s1^&4wM;duR(?I76z97P)YNeKA zkRX{o0Y^e-ydr}jVI6zu{Tr!B8qHw1-xs!gT_(Rx4fZ($T%k`O==Z32T%hXq_h6;hMJ%Q7*1~= zC@a#<2P%RNju9#F0sVnA{{bJU7xJ|;lm^M;2PKCBlvSjcFZ2r5xfdd>u0vR`K_)Uv z=c!xn479NGh}~uVcmQOD#F@H5BSOf}EyU(0O@|-FqV}lXbS*){8dv7l$=kc%ojDqQ8mxw!$pY%pt(5S z^`b-)l$@Uqdr=F;5)d#n04jj|<^inmE&zH7WVFHF? z_9DDr&;Uk-#b8MGK)}8=7|E#iu&e;HAZz#Unt)lj_}|Kb0cOt+U2s zu=3&Yk?6Qqi(RGO(Ob2$Yig9Qx>QQj~}QxX2Pb6}+(8U%jA2 z`ZId}Dn_v`2EXVW)xqB_iX*cFE`&rLhCtN>|3}?06!ZT(0$?akB}D!-3&zb~&B9OhY~nxGg`w<)S`XcVQV2<0c)~x@z);d$EQS8=5cCf)4==bu z_4u!c$RB^W!A5y2Tv7yCd<)8pd~^#+4oArc&WHXFA1F^0{Sy}d>Ho|f0iyM}DN&96 z)e*oz$Ot+Y`_eTao+yC6_;0g7JOIY{KgGayq5!Y)ztrMLpD-xpB_N$B0KGsxMKXnh z`N$y#N7};3yeIU`QbZlv4SBGyq4*_`hBa2O1Xr zU#|u|zyPSi1sCA%FBV0SODHZxK`;^(xtO#5F`y_)3jjX?avONq`D*v?x+tl^ZxTVU zNTK-?DgUaBk_@b-&aIE?;qSVl$m$R%fFec7`6q_{RTqrhbM#9P?Efl8$^B=t{i|3K znU7j15l28VsQ!0gfVKdz_opx@9WkoAzuFT=#)g715E%h})B!k+w-Hbw(!ZXg+!iK$ z9*T&CLEryW1x{;v3(5};2L&YmV|kRj!Y-EOf7KC1Cf|WlPyrrb|6_3g_yUQ9Qec5z zPzR0*--pr?{mU}}x;bAMl%F2uMzH@eG9VQOF;zGcO0M)jc0^5`u!|pm|FDB{S^(Ad zpRtVcSs1`lJ!9Zs9t#}y7%(7$awGuW^xt}@QTAum^M9rhLCFeMJ|t04asUha-#RE^ z|0f8Gpai^#!NmXeTCk|;3y1O}OQN83RR84w<+3nooT zy8;Ag!Y3LEyYgS*QGNvg)c(UJ%B=uy(|<~VyZ{0Vj4E0pln?)-SFoPF4dus14R{zh zZ|}MxNDJ?4t{8LFX83?MZl;rZ+QxSu%Qh33%x7OLPT9SE74y9r zdFO_!VW0mOCbi6kC*y}Nm`Yj=x}(Lq8ov5!aq?KUM{w|u{HQq9cW}3!BFc3&=D`y- zZp59xOq3*-b3@i$5uD zdy2KI$JYkdDx7S<&e*IE$>f^z9 zAd(-92T1<+UMo(o869kOcr4M{vzn{+!f^J-#i`E)n@H#R6l@~g9+i)oqLs*Nw26=i z|K{rmfM1(vqvZ}i)5^!Kt7QOO*?90Wk{fO4u^s;Ndhyg+9Y#twyxTZdwL+_^arp08 zl~Fbb>0d4t_kW}r-oI3Q`VKG8iJDEdZH5Xyut_)qSEkG)TN&m4}V7L+b~2D zll9EyNj5mUD(T=Fk^k^&acec9CfywN*oAV&!;Tvrr~ghI>bQz-`gglS4ce2VG1*?g zylrS&gyFTz#eKs^&lPd#ub^t~f33LnV{?%i6&vonLXrM?e2{-$DQ>yH`_vk73f%g) zJYcb{IZ5U48>=}vF3tr;2mb!`;^elg*;gFydSk9|Ja;rO8{fUO6ra`DaMgaAq&tH7@ndi=cwoXvl-oY22{s#XHXvaTYE7DCU!hc;WUOvL* z<=h3>03rSGdU5yapKo6;zJ5~`hNCx%gFCB@!Hf2{|J?qb{ddD-G3=!$Zxrc{Xu+5L ztv_XH*nQo;rpR5N{%c84 z^qzg?Qaow7Ooa0F&-ofLC%!IGL^Hf(U+ZKTZr*Ph2sYleuU#}R*nRC15d2VtyP6bj z4gBY-94-u0PH7Vc~IHQCj%{;S=@`p2XuX zeAv8P4COr0e(9v+vdO$)S%%Uw0_)1qM#Rd|YEv?f)|d%#w9d5Fiq`Z0A(-u15sj9- zw3Q`t;8@ZhKrE**J{e3pL@ttDB<$<9JE7&x z+)-1Os2#s^p+&#O&=NPZ<_)5q4cyHJSe2to?r5FCO=}u(Ss5)LS`%q*%y~Il?bUn9 z(}?C4WaUb@DjWo5kv_P2v)i}zUnG6QJ)k0lRRuPjcY^dcmib!OWiV9FX_Q0FNW|C?dG&iG~SDm#-LQ1&RZw^^^;s~#&(M_Vu%7#lY z9cP%^_#9}~ag#dXRvUvs(IfwNIsh|lKzUX4h-jMSA&2T!_2W z?I-C^q%WjkXg#~Ck^sr=ZC2gxe9i~MtzgS16SUiR!ozR`{l0Q* zS6RCT+i2Kx-bq&qXUG?fD%kkHV?XHvF+kW~F+e0Qg3N@xg=c@oGc_`39Qjq@@SIlZ%mfru8UySh9{?`enFk}CGWVu8Fe?MdZC($baNu#;sg#?D(nd@0>c zr1mF^%KR7hqvk~iFcBGG>dei~2aQXjgASI~aOGm=?2P=!7g7O9De8=QDmQNs=^rDt ztP|9SEYpVj+IL!SR(d?$*l!(y9T#w{Wl5+jf(fcuN`obLH0J{(21sI2q0PQQzp$UG zhT!KjAqem+B#|tF2~B5Q^Ov`0+%jZw>BIO>6u{G$E8<9N40W}?g)^;cGr&91A7rpC zN95`f9eG;a*1Z%&SqK-pF?o?Uhzts6P%2&%f**=Pg0}TF7XJ@O(Lfs4$%yfLn)m@t z5-u34{klVIc}`*72mS{{mCR@lK&%V0OoSLBT#5V5r5so03#}F1PjnX=@BeKgZm_T1 z1%sP%E~FqDj>A2od^W^De$XVycYejr|AXkQ9n-bj`16jT`FMzOBTk(SklER>r8NyT zAHAOG&7z7R*$&f=$M?n84N{j&>hn&ZsiFbw^s+!Xiw{Qqw`?(98W0`882}eF5UFw4 zNIJ$P^jnM_X1IPn=w`&>C)&|j6{0@?D*pW2;=bO?6Y-PEPHXqUyLQRGd%5FXT?ve! zd9=g!fSp>hzhy@PXL@yy$PxwljxjSbae_H-qY!toZycwv6tn%!dX5$0w1mVHHBo{z z7#o$(IA&B=4O)BSp#w+jynMn1f@8NM@z%@5U7uI0+2~T^tE-q0Knb@%gz_0Pz_fVg zyB&$hiYA7p9yj5VmxTspTth2 z?uhO1=I@FVCKY*KCHpM*WvJMMO13!0+1ho@VmOs1;skIvF%7M-8$9~UQ1z@q3y(a5 zT1=I@-P zkxX+IA~RbHKMs+iRsmpBZ=#KeqeZQPFo+>(5w;aSCbQWdA_34O9BY%tkS!6FEKb_& z9Fr|Bh0818Mz`sCHN&yL1N~Q;2CB?W6oC1tUK~5(0bxJuKWeSDIO5Sul-uFKtk`91 zT|)CSu_cjEuH$_;-*Ja5ujOszKjI-m*m1r@xPZWUUCS>9WN&m}7EXZCpGdvubV$9& zbW6;=ofNRYonV?~)XIVzQ`a7va&tIlKqulBMKZ0lr$f&UQ26+(=} z6+(}TUj|J8Nn$KPP{;x(`_pD4VOs!Z_j(>6UqU$qSh--*mCer54{T(iu}x;$&Pl|V z6oYS=v#c>5rWYgzDClSeR`HrJZUBGt!Sp0P#$#cbAVr{BSI`n90$fI1Wj{dR+Z8N3 zp|WukMYBfa+{5;ClEgkk~`N$KRxM9#Fb80m2H9;GHhsa_t{vX7~yG^L& z@sy~E#dewK0tm^^m@}SkD>4nqtdr-d^N0xk0}OFcQyP@elmUV{_;LgsQWv06%Zc;C z0Nc>ZOlt97C@3x$G+asYrAzQcguwL z?X@H2qVtCA*Z}!FkzhPeVQVKS00SIn>wE|)4lpBA7nsEVMA5splFmJD? zvVnD0ciCbj!oB7)l0>~6+mK~4wXmJt9Q#w3*h%_`#F>-**$EBZdV64}F+9=(t@gSf zS|dAxF?+L3xfND7`+pG48+BZ$C1mDw2d~OXIpQv%$vGkFN-R+IC^H}1QIKN{zQKl- z7n-DNSP{TiDc5>G(GJ{Lxsm@1`#EGOYsb%!gx1s_F?AgzMn#wW%-Dn7K4=LCk z(jc+0bi(l~PeUX5K@-YmyTtg9bqZcuId~zch$+I=)|n1Q^BZ3=FgRh}pRB+-LG<@1$EQYuXjEm2pneUdF= z51>)h^+8gGcw@9bI|V{P7D@_=1w^O`o_sl+Lcb({A|6$x?h3Zq;(s;ppPzBfk#{dN zHOGXU1+-&i-r7MErvw_fjT>|i(c63qj=%BFcElkES|~@1Read all records and fields.

    ds_all_rows_all_fields <- redcap_read(redcap_uri=uri, token=token)$data
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-08-10 17:43:29.
    +
    Starting to read 5 records  at 2018-08-10 17:57:48.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    -
    5 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    +
    5 records and 24 columns were read from REDCap in 0.4 seconds.  The http status code was 200.
      record_id name_first name_last                                 address
     1         1     Nutmeg  Nutmouse 14 Rose Cottage St.\nKenning UK, 323232
    @@ -352,9 +352,9 @@ 

    Read a subset of the records.

    token = token, records = desired_records_v1 )$data
    -
    The data dictionary describing 16 fields was read from REDCap in 0.3 seconds.  The http status code was 200.
    +
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    2 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 2 records  at 2018-08-10 17:43:31.
    +
    Starting to read 2 records  at 2018-08-10 17:57:49.
    Reading batch 1 of 1, with subjects 1 through 3 (ie, 2 unique subject records).
    2 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    #Return only records with IDs of 1 and 3 (alternate way)
    @@ -366,7 +366,7 @@ 

    Read a subset of the records.

    )$data
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    2 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 2 records  at 2018-08-10 17:43:32.
    +
    Starting to read 2 records  at 2018-08-10 17:57:50.
    Reading batch 1 of 1, with subjects 1 through 3 (ie, 2 unique subject records).
    2 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    @@ -398,7 +398,7 @@

    Read a subset of the fields.

    )$data
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-08-10 17:43:33.
    +
    Starting to read 5 records  at 2018-08-10 17:57:51.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-08-10 17:43:34.
    +
    Starting to read 5 records  at 2018-08-10 17:57:53.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    @@ -434,9 +434,9 @@

    Read a subset of records, conditioned on the values in some variables.

    )$data
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-08-10 17:43:35.
    +
    Starting to read 5 records  at 2018-08-10 17:57:54.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    -
    5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    +
    5 records and 3 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
      record_id        dob weight
     1         1 2003-08-30      1
    @@ -461,8 +461,8 @@ 

    Read a subset of records, conditioned on the values in some variables.

    records = desired_records_v3 )$data
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    2 records and 1 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    -
    Starting to read 2 records  at 2018-08-10 17:43:37.
    +
    2 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    +
    Starting to read 2 records  at 2018-08-10 17:57:55.
    Reading batch 1 of 1, with subjects 3 through 5 (ie, 2 unique subject records).
    2 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    @@ -505,7 +505,7 @@

    Additional Returned Information

    )
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-08-10 17:43:38.
    +
    Starting to read 5 records  at 2018-08-10 17:57:56.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    @@ -542,7 +542,7 @@

    Additional Returned Information

    [1] "" $elapsed_seconds -[1] 1.169355 +[1] 1.116541
    @@ -579,6 +579,7 @@

    Session Information

    digest 0.6.15 2018-01-28 CRAN (R 3.5.0) dplyr 0.7.6 2018-06-29 CRAN (R 3.5.1) evaluate 0.11 2018-07-17 CRAN (R 3.5.1) + fs 1.2.5 2018-07-30 CRAN (R 3.5.1) git2r 0.23.0 2018-07-17 CRAN (R 3.5.1) glue 1.3.0 2018-07-17 CRAN (R 3.5.1) hms 0.4.2.9001 2018-08-09 Github (tidyverse/hms@979286f) @@ -587,11 +588,13 @@

    Session Information

    kableExtra 0.9.0 2018-05-21 CRAN (R 3.5.0) knitr * 1.20 2018-02-20 CRAN (R 3.5.0) magrittr * 1.5 2014-11-22 CRAN (R 3.5.0) + MASS 7.3-50 2018-04-30 CRAN (R 3.5.1) memoise 1.1.0 2017-04-21 CRAN (R 3.5.0) munsell 0.5.0 2018-06-12 CRAN (R 3.5.0) packrat 0.4.9-3 2018-06-01 CRAN (R 3.5.0) pillar 1.3.0 2018-07-14 CRAN (R 3.5.1) pkgconfig 2.0.1 2017-03-21 CRAN (R 3.5.0) + pkgdown 1.1.0 2018-06-02 CRAN (R 3.5.1) purrr 0.2.5 2018-05-29 CRAN (R 3.5.0) R6 2.2.2 2017-06-17 CRAN (R 3.5.0) Rcpp 0.12.18 2018-07-23 CRAN (R 3.5.1) @@ -616,7 +619,7 @@

    Session Information

    xml2 1.2.0 2018-01-24 CRAN (R 3.5.0) yaml 2.2.0 2018-07-25 CRAN (R 3.5.1) -

    Report rendered by Will at 2018-08-10, 17:43 -0500 in 10 seconds.

    +

    Report rendered by Will at 2018-08-10, 17:57 -0500 in 10 seconds.

    diff --git a/inst/doc/SecurityDatabase.Rmd b/inst/doc/SecurityDatabase.Rmd index f2bfa046..92f46746 100644 --- a/inst/doc/SecurityDatabase.Rmd +++ b/inst/doc/SecurityDatabase.Rmd @@ -100,8 +100,8 @@ Create user credentials to the auxiliary database Add a user's LDAP account to the `SecurityAuxiliary` database so that they can query the tables to retrieve their API. Notice that this only gives the permissions to retrieve the token. You must still: - 1. grant them API privileges to each appropriate REDCap project, and - 2. copy the API from the REDCap database into the SecurityAuxiliary database. +1. grant them API privileges to each appropriate REDCap project, and +2. copy the API from the REDCap database into the SecurityAuxiliary database. In the future `REDCapR` may expose a function that allows the user to perform the second step (through a stored procedure). @@ -199,7 +199,7 @@ rm(list=ls(all=TRUE)) #Clear the memory for any variables set from any previous # ---- load-packages ----------------------------------------------------------- library(magrittr) -requireNamespace("RODBC") +requireNamespace("odbc") requireNamespace("dplyr") requireNamespace("readr") requireNamespace("tibble") @@ -231,15 +231,15 @@ sep="\n")) # ---- load-data --------------------------------------------------------------- # Load the credentials from the first instance. -channel <- RODBC::odbcConnect("redcap-production") # odbcGetInfo(channel) -ds_prod <- RODBC::sqlQuery(channel, query=sql, stringsAsFactors=F) -RODBC::odbcClose(channel); rm(channel) +channel <- DBI::dbConnect(odbc::odbc(), dsn="redcap-production") +ds_prod <- DBI::dbGetQuery(channel, sql) +DBI::dbDisconnect(channel); rm(channel) # Load the credentials from the second instance. # Duplicate or remove this block, dependending on the number of instances. -channel <- RODBC::odbcConnect("redcap-dev") # odbcGetInfo(channel) -ds_dev <- RODBC::sqlQuery(channel, query=sql, stringsAsFactors=F) -RODBC::odbcClose(channel); rm(channel, sql) +channel <- DBI::dbConnect(odbc::odbc(), dsn="redcap-dev") +ds_dev <- DBI::dbGetQuery(channel, sql) +DBI::dbDisconnect(channel); rm(channel) # Assert these variables contain valid datasets (instead of a character error message), and # that at least some rows were returned. @@ -302,19 +302,19 @@ rm(columns_to_write) # ---- upload-to-db-credential ------------------------------------------------------------ -#Upload to SQL Server through ODBC. -(start_time <- Sys.time()) - -db_table <- "redcap_private.tbl_credential" -channel <- RODBC::odbcConnect("auxiliary_security") #getSqlTypeInfo("Microsoft SQL Server") #;odbcGetInfo(channel) - -column_info <- RODBC::sqlColumns(channel, db_table) -var_types <- as.character(column_info$TYPE_NAME) -names(var_types) <- as.character(column_info$COLUMN_NAME) #var_types - -RODBC::sqlClear(channel, db_table) -RODBC::sqlSave(channel, ds_slim, db_table, append=TRUE, rownames=FALSE, fast=TRUE, varTypes=var_types) -RODBC::odbcClose(channel); rm(channel) +if( !require(OuhscMunge) ) + stop('The `OuhscMunge` package needs to be installed with `devtools::install_github("OuhscBbmc/OuhscMunge")`.') + +OuhscMunge::upload_sqls_odbc( + d = ds_slim, + schema_name = "redcap_private", + table_name = "tbl_credential", + dsn_name = "auxiliary_security", + create_table = FALSE, + clear_table = TRUE, + transaction = TRUE, + verbose = TRUE +) (elapsed_duration <- Sys.time() - start_time) #0.6026149 secs 2016-08-29. rm(db_table, column_info, var_types, start_time, elapsed_duration) diff --git a/inst/doc/SecurityDatabase.html b/inst/doc/SecurityDatabase.html index 3cbdada0..4eec4068 100644 --- a/inst/doc/SecurityDatabase.html +++ b/inst/doc/SecurityDatabase.html @@ -452,7 +452,7 @@

    Transfer Credentials

    # ---- load-packages ----------------------------------------------------------- library(magrittr) -requireNamespace("RODBC") +requireNamespace("odbc") requireNamespace("dplyr") requireNamespace("readr") requireNamespace("tibble") @@ -484,15 +484,15 @@

    Transfer Credentials

    # ---- load-data --------------------------------------------------------------- # Load the credentials from the first instance. -channel <- RODBC::odbcConnect("redcap-production") # odbcGetInfo(channel) -ds_prod <- RODBC::sqlQuery(channel, query=sql, stringsAsFactors=F) -RODBC::odbcClose(channel); rm(channel) +channel <- DBI::dbConnect(odbc::odbc(), dsn="redcap-production") +ds_prod <- DBI::dbGetQuery(channel, sql) +DBI::dbDisconnect(channel); rm(channel) # Load the credentials from the second instance. # Duplicate or remove this block, dependending on the number of instances. -channel <- RODBC::odbcConnect("redcap-dev") # odbcGetInfo(channel) -ds_dev <- RODBC::sqlQuery(channel, query=sql, stringsAsFactors=F) -RODBC::odbcClose(channel); rm(channel, sql) +channel <- DBI::dbConnect(odbc::odbc(), dsn="redcap-dev") +ds_dev <- DBI::dbGetQuery(channel, sql) +DBI::dbDisconnect(channel); rm(channel) # Assert these variables contain valid datasets (instead of a character error message), and # that at least some rows were returned. @@ -555,19 +555,19 @@

    Transfer Credentials

    # ---- upload-to-db-credential ------------------------------------------------------------ -#Upload to SQL Server through ODBC. -(start_time <- Sys.time()) +if( !require(OuhscMunge) ) + stop('The `OuhscMunge` package needs to be installed with `devtools::install_github("OuhscBbmc/OuhscMunge")`.') -db_table <- "redcap_private.tbl_credential" -channel <- RODBC::odbcConnect("auxiliary_security") #getSqlTypeInfo("Microsoft SQL Server") #;odbcGetInfo(channel) - -column_info <- RODBC::sqlColumns(channel, db_table) -var_types <- as.character(column_info$TYPE_NAME) -names(var_types) <- as.character(column_info$COLUMN_NAME) #var_types - -RODBC::sqlClear(channel, db_table) -RODBC::sqlSave(channel, ds_slim, db_table, append=TRUE, rownames=FALSE, fast=TRUE, varTypes=var_types) -RODBC::odbcClose(channel); rm(channel) +OuhscMunge::upload_sqls_odbc( + d = ds_slim, + schema_name = "redcap_private", + table_name = "tbl_credential", + dsn_name = "auxiliary_security", + create_table = FALSE, + clear_table = TRUE, + transaction = TRUE, + verbose = TRUE +) (elapsed_duration <- Sys.time() - start_time) #0.6026149 secs 2016-08-29. rm(db_table, column_info, var_types, start_time, elapsed_duration) diff --git a/inst/doc/advanced-redcapr-operations.html b/inst/doc/advanced-redcapr-operations.html index 7b5f5556..5f6cbfdf 100644 --- a/inst/doc/advanced-redcapr-operations.html +++ b/inst/doc/advanced-redcapr-operations.html @@ -403,7 +403,7 @@

    SSL Options

    config_options = config_options )$data } -
    #> 5 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    +
    #> 5 records and 24 columns were read from REDCap in 0.3 seconds.  The http status code was 200.

    Force the connection to use SSL=3 (which is not preferred, and possibly insecure).

    config_options <- list(sslversion=3)
     ds_ssl_3 <- redcap_read_oneshot(
    @@ -454,6 +454,7 @@ 

    Session Information

    #> digest 0.6.15 2018-01-28 CRAN (R 3.5.0) #> dplyr 0.7.6 2018-06-29 CRAN (R 3.5.1) #> evaluate 0.11 2018-07-17 CRAN (R 3.5.1) +#> fs 1.2.5 2018-07-30 CRAN (R 3.5.1) #> git2r 0.23.0 2018-07-17 CRAN (R 3.5.1) #> glue 1.3.0 2018-07-17 CRAN (R 3.5.1) #> hms 0.4.2.9001 2018-08-09 Github (tidyverse/hms@979286f) @@ -462,11 +463,13 @@

    Session Information

    #> kableExtra 0.9.0 2018-05-21 CRAN (R 3.5.0) #> knitr * 1.20 2018-02-20 CRAN (R 3.5.0) #> magrittr * 1.5 2014-11-22 CRAN (R 3.5.0) +#> MASS 7.3-50 2018-04-30 CRAN (R 3.5.1) #> memoise 1.1.0 2017-04-21 CRAN (R 3.5.0) #> munsell 0.5.0 2018-06-12 CRAN (R 3.5.0) #> packrat 0.4.9-3 2018-06-01 CRAN (R 3.5.0) #> pillar 1.3.0 2018-07-14 CRAN (R 3.5.1) #> pkgconfig 2.0.1 2017-03-21 CRAN (R 3.5.0) +#> pkgdown 1.1.0 2018-06-02 CRAN (R 3.5.1) #> purrr 0.2.5 2018-05-29 CRAN (R 3.5.0) #> R6 2.2.2 2017-06-17 CRAN (R 3.5.0) #> Rcpp 0.12.18 2018-07-23 CRAN (R 3.5.1) @@ -491,7 +494,7 @@

    Session Information

    #> xml2 1.2.0 2018-01-24 CRAN (R 3.5.0) #> yaml 2.2.0 2018-07-25 CRAN (R 3.5.1)
    -

    Report rendered by Will at 2018-08-10, 17:43 -0500 in 2 seconds.

    +

    Report rendered by Will at 2018-08-10, 17:57 -0500 in 2 seconds.

    diff --git a/vignettes/SecurityDatabase.Rmd b/vignettes/SecurityDatabase.Rmd index f2bfa046..92f46746 100644 --- a/vignettes/SecurityDatabase.Rmd +++ b/vignettes/SecurityDatabase.Rmd @@ -100,8 +100,8 @@ Create user credentials to the auxiliary database Add a user's LDAP account to the `SecurityAuxiliary` database so that they can query the tables to retrieve their API. Notice that this only gives the permissions to retrieve the token. You must still: - 1. grant them API privileges to each appropriate REDCap project, and - 2. copy the API from the REDCap database into the SecurityAuxiliary database. +1. grant them API privileges to each appropriate REDCap project, and +2. copy the API from the REDCap database into the SecurityAuxiliary database. In the future `REDCapR` may expose a function that allows the user to perform the second step (through a stored procedure). @@ -199,7 +199,7 @@ rm(list=ls(all=TRUE)) #Clear the memory for any variables set from any previous # ---- load-packages ----------------------------------------------------------- library(magrittr) -requireNamespace("RODBC") +requireNamespace("odbc") requireNamespace("dplyr") requireNamespace("readr") requireNamespace("tibble") @@ -231,15 +231,15 @@ sep="\n")) # ---- load-data --------------------------------------------------------------- # Load the credentials from the first instance. -channel <- RODBC::odbcConnect("redcap-production") # odbcGetInfo(channel) -ds_prod <- RODBC::sqlQuery(channel, query=sql, stringsAsFactors=F) -RODBC::odbcClose(channel); rm(channel) +channel <- DBI::dbConnect(odbc::odbc(), dsn="redcap-production") +ds_prod <- DBI::dbGetQuery(channel, sql) +DBI::dbDisconnect(channel); rm(channel) # Load the credentials from the second instance. # Duplicate or remove this block, dependending on the number of instances. -channel <- RODBC::odbcConnect("redcap-dev") # odbcGetInfo(channel) -ds_dev <- RODBC::sqlQuery(channel, query=sql, stringsAsFactors=F) -RODBC::odbcClose(channel); rm(channel, sql) +channel <- DBI::dbConnect(odbc::odbc(), dsn="redcap-dev") +ds_dev <- DBI::dbGetQuery(channel, sql) +DBI::dbDisconnect(channel); rm(channel) # Assert these variables contain valid datasets (instead of a character error message), and # that at least some rows were returned. @@ -302,19 +302,19 @@ rm(columns_to_write) # ---- upload-to-db-credential ------------------------------------------------------------ -#Upload to SQL Server through ODBC. -(start_time <- Sys.time()) - -db_table <- "redcap_private.tbl_credential" -channel <- RODBC::odbcConnect("auxiliary_security") #getSqlTypeInfo("Microsoft SQL Server") #;odbcGetInfo(channel) - -column_info <- RODBC::sqlColumns(channel, db_table) -var_types <- as.character(column_info$TYPE_NAME) -names(var_types) <- as.character(column_info$COLUMN_NAME) #var_types - -RODBC::sqlClear(channel, db_table) -RODBC::sqlSave(channel, ds_slim, db_table, append=TRUE, rownames=FALSE, fast=TRUE, varTypes=var_types) -RODBC::odbcClose(channel); rm(channel) +if( !require(OuhscMunge) ) + stop('The `OuhscMunge` package needs to be installed with `devtools::install_github("OuhscBbmc/OuhscMunge")`.') + +OuhscMunge::upload_sqls_odbc( + d = ds_slim, + schema_name = "redcap_private", + table_name = "tbl_credential", + dsn_name = "auxiliary_security", + create_table = FALSE, + clear_table = TRUE, + transaction = TRUE, + verbose = TRUE +) (elapsed_duration <- Sys.time() - start_time) #0.6026149 secs 2016-08-29. rm(db_table, column_info, var_types, start_time, elapsed_duration) From c1bf7e6de633c51eededac5ee6a83f31ac3d2a57 Mon Sep 17 00:00:00 2001 From: Will Beasley Date: Fri, 10 Aug 2018 19:04:04 -0500 Subject: [PATCH 5/6] update & clarify security database vignette ref #233 --- vignettes/SecurityDatabase.Rmd | 55 +++++++++++++++++----------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/vignettes/SecurityDatabase.Rmd b/vignettes/SecurityDatabase.Rmd index 92f46746..15cf284b 100644 --- a/vignettes/SecurityDatabase.Rmd +++ b/vignettes/SecurityDatabase.Rmd @@ -19,11 +19,11 @@ This database contains the tokens and other sensitive content (such as passwords Create a DSN on each client ======================================== -After executing the SQL code in an existing database, create an ODBC [DSN](http://en.wikipedia.org/wiki/Data_source_name) on *each* client machine that calls the database. Download the most recent drivers (as of Aug 2016, the [most recent version is 13.1](https://msdn.microsoft.com/library/mt703139.aspx) for Windows and Linux, then run the wizard. Many values in the wizard will remain at the default values. Here are the important ones to change. +After executing the SQL code in an existing database, create an ODBC [DSN](http://en.wikipedia.org/wiki/Data_source_name) on *each* client machine that calls the database. Download the most recent drivers (as of Aug 2018, the [most recent version is 17](https://docs.microsoft.com/en-us/sql/connect/odbc/download-odbc-driver-for-sql-server) for Windows and Linux, then run the wizard. Many values in the wizard will remain at the default values. Here are the important ones to change. 1. Set the DSN's `name` field to whatever is used in the repository's R code. 2. Set the authenticity method to `Integrated Windows authentication`. -3. Set the `default database` to the name of the database that containing the tokens *i.e.*, corresponding to the SQL code below in the example). +3. Set the `default database` to the name of the database that containing the tokens (*e.g.*, corresponding to `auxiliary_security` in the SQL code below in the example). Note @@ -35,7 +35,7 @@ We use Microsoft SQL Server, because that fits our University's infrastructure t Create Database ======================================== -This SQL code is run once inside an existing database to establish the schemas, table, and stored procedure used by `REDCapR::retrieve_credential_mssql()`. +This SQL code is run once inside an existing database to establish the schemas, table, and stored procedure used by `REDCapR::retrieve_credential_mssql()`. In this example, we've arbitrarily called the database `auxiliary_security`. ```sql ------- SQL code to create necessary components in a Microsoft SQL Sever database ------- @@ -78,18 +78,19 @@ GO -- Notice it should a different (and more permissive) schema than the table. -- CREATE PROCEDURE [redcap].[prc_credential] - -- Add the parameters for the stored procedure here - @project_id smallint, - @instance varchar(30) + @instance varchar(30) AS BEGIN - -- SET NOCOUNT ON added to prevent extra result sets from - -- interfering with SELECT statements. SET NOCOUNT ON; SELECT username, project_id, token, redcap_uri FROM [redcap_private].[tbl_credential] - WHERE username=system_user AND project_id=@project_id AND instance=@instance + WHERE + username = system_user -- The username from the server's OS. + AND + project_id = @project_id -- Restricts to the desired REDCap project. + AND + instance = @instance -- System accommodates multiple REDCap instances. END ``` @@ -97,23 +98,23 @@ END Create user credentials to the auxiliary database ======================================== -Add a user's LDAP account to the `SecurityAuxiliary` database so that they can query the tables to retrieve their API. +Add a user's LDAP account to the `auxiliary_security` database so that they can query the tables to retrieve their API. Notice that this only gives the permissions to retrieve the token. You must still: 1. grant them API privileges to each appropriate REDCap project, and -2. copy the API from the REDCap database into the SecurityAuxiliary database. +2. copy the API from the REDCap database into the `auxiliary_security` database. -In the future `REDCapR` may expose a function that allows the user to perform the second step (through a stored procedure). +In the future ,`REDCapR` may expose a function that allows the user to perform the second step (through a stored procedure). -Also, do not give typical users authorization for the 'redcap_private' schema. The current system allows the to view only their own tokens. +Only database admins should have authorization for the 'redcap_private' schema. Typical users should not be authorized for this schema. The current system allows typical users to view only their own tokens. ```sql ----------------------------------------------------------------------- --- Add a OUHSC's user account to the auxiliary_security database so that they can query the tables to retrieve their API. +-- Add a user account to the auxiliary_security database so that they can query the tables to retrieve their API. -- Notice that this only gives the permissions to retrieve the token. You must still: -- 1) grant them API privileges to each appropriate REDCap project, and -- 2) copy the API from the REDCap database into the auxiliary_security database. --- Also, do not give typical users authorization for the 'redcap_private' schema. The current system allows the to view only their own tokens. +-- Also, do not give typical users authorization for the 'redcap_private' schema. The current system allows them to view only their own tokens. ----------------------------------------------------------------------- -- STEP #1: Declare the user name. If everything runs correctly, this should be the only piece of code that you need to modify. @@ -139,7 +140,7 @@ DECLARE @login_count AS INT; SET @login_count = (SELECT COUNT(*) AS login_count print 'Logins matching desired name should equal 1. It equals: ' + CONVERT(varchar, @login_count); print '' ----------------------------------------------------------------------- --- STEP #3: Create a user account for the *data base*, after switching the database under focus to auxiliary_security. +-- STEP #3: Create a user account for the *database*, after switching the database under focus to auxiliary_security. print 'Step #3 executing....' USE [auxiliary_security] DECLARE @sql_create_user nvarchar(max) @@ -186,11 +187,11 @@ print 'Step #5 executed'; print '' Transfer Credentials ======================================== -Manually transfer tokens to the auxiliary server becomes unmanageable as your institution's collection of API users grows. This script demonstrates how to progamatically transfer all tokens from multiple REDCap instances on your network. The basic steps are: +Manually transferring tokens to the auxiliary server becomes unmanageable as your institution's collection of API users grows. This script demonstrates how to progamatically transfer all tokens from multiple REDCap instances on your network. The basic steps are: -1. Read from the MySQL database underneath each REDCap instance. +1. Read from the MySQL database(s) underneath each REDCap instance on your campus. 1. Combine & groom the credentials. -1. Upload to SQL Server. +1. Upload to SQL Server (called `auxiliary_security` here). ```r rm(list=ls(all=TRUE)) #Clear the memory for any variables set from any previous runs. @@ -212,13 +213,13 @@ requireNamespace("checkmate") # (https://msdn.microsoft.com/en-us/library/ms179930.aspx) ldap_prefix <- "OUHSC\\" -#Create a SQL statement for each REDCap instance. Only the `instance` value should change. +# Create a SQL statement for each REDCap instance. Only the `instance` value should change. sql <- " SELECT username, project_id, api_token FROM redcap_user_rights WHERE api_token IS NOT NULL" -#Update this ad-hoc CSV. Each row should represent one REDCap instance. +# Update this ad-hoc CSV. Each row should represent one REDCap instance. # Choose any casual name for the first variable, consistent with the `tweak-data` chunk below. # Enter the exact URL for the second variable. ds_url <- readr::read_csv(paste( @@ -231,7 +232,7 @@ sep="\n")) # ---- load-data --------------------------------------------------------------- # Load the credentials from the first instance. -channel <- DBI::dbConnect(odbc::odbc(), dsn="redcap-production") +channel <- DBI::dbConnect(odbc::odbc(), dsn="redcap-production") ds_prod <- DBI::dbGetQuery(channel, sql) DBI::dbDisconnect(channel); rm(channel) @@ -247,14 +248,13 @@ DBI::dbDisconnect(channel); rm(channel) checkmate::assert_data_frame(ds_bbmc, min.rows=5) checkmate::assert_data_frame(ds_dev , min.rows=5) - # ---- tweak-data -------------------------------------------------------------- -#Label each instance, so they're distinguishable later. Add/remove lines, depending on the number of campus instances +# Label each instance, so they're distinguishable later. Add/remove lines, depending on the number of campus instances ds_prod$instance <- "production" ds_dev$instance <- "dev" -#Combine the token collection from each instance. Then prefix the username and include the URL of each instance. +# Combine the token collection from each instance. Then prefix the username and include the URL of each instance. ds <- ds_prod %>% dplyr::union(ds_dev) %>% # Add/remove unions, based on the number of REDCap instances. dplyr::select_( @@ -274,6 +274,7 @@ rm(ds_prod, ds_dev, ds_url) # ---- verify-values ----------------------------------------------------------- + # devtools::install_github("OuhscBbmc/OuhscMunge"); OuhscMunge::verify_value_headstart(ds) # Assert that the dataset is well-behaved. checkmate::assert_integer( ds$id , any.missing=F , lower=1, upper=2^31-1 , unique=T) @@ -302,7 +303,7 @@ rm(columns_to_write) # ---- upload-to-db-credential ------------------------------------------------------------ -if( !require(OuhscMunge) ) +if( !require(OuhscMunge) ) stop('The `OuhscMunge` package needs to be installed with `devtools::install_github("OuhscBbmc/OuhscMunge")`.') OuhscMunge::upload_sqls_odbc( @@ -324,4 +325,4 @@ rm(db_table, column_info, var_types, start_time, elapsed_duration) Document Info ======================================== -This document is primarily based on REDCap version 6.11.5, and was last updated 2016-08-30. A development version of the document is available on GitHub: https://ouhscbbmc.github.io/REDCapR/articles/SecurityDatabase.html +This document is primarily based on REDCap version 8.4.0, and was last updated 2018-08-10. A development version of the document is available on GitHub: https://ouhscbbmc.github.io/REDCapR/articles/SecurityDatabase.html From 753191ed692936c704cdaceba3b8a4fcef03b651 Mon Sep 17 00:00:00 2001 From: Will Beasley Date: Fri, 10 Aug 2018 20:38:08 -0500 Subject: [PATCH 6/6] modernized security database vignette closes #233 --- docs/articles/BasicREDCapROperations.html | 36 +- docs/articles/SecurityDatabase.html | 336 ++++++++++-------- .../articles/advanced-redcapr-operations.html | 10 +- docs/reference/kernel_api.html | 2 +- documentation-peek.pdf | Bin 249160 -> 249160 bytes inst/doc/BasicREDCapROperations.html | 29 +- inst/doc/SecurityDatabase.Rmd | 216 ++++++----- inst/doc/SecurityDatabase.html | 336 ++++++++++-------- inst/doc/advanced-redcapr-operations.html | 9 +- vignettes/SecurityDatabase.Rmd | 183 ++++++---- 10 files changed, 633 insertions(+), 524 deletions(-) diff --git a/docs/articles/BasicREDCapROperations.html b/docs/articles/BasicREDCapROperations.html index cd4c1b01..6aaaadfa 100644 --- a/docs/articles/BasicREDCapROperations.html +++ b/docs/articles/BasicREDCapROperations.html @@ -127,9 +127,9 @@

    ds_all_rows_all_fields <- redcap_read(redcap_uri=uri, token=token)$data
    The data dictionary describing 16 fields was read from REDCap in 0.4 seconds.  The http status code was 200.
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-08-10 17:58:25.
    +
    Starting to read 5 records  at 2018-08-10 20:36:45.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    -
    5 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    +
    5 records and 24 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
      record_id name_first name_last                                 address
     1         1     Nutmeg  Nutmouse 14 Rose Cottage St.\nKenning UK, 323232
    @@ -181,8 +181,8 @@ 

    records = desired_records_v1 )$data

    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    2 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 2 records  at 2018-08-10 17:58:27.
    +
    2 records and 1 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    +
    Starting to read 2 records  at 2018-08-10 20:36:46.
    Reading batch 1 of 1, with subjects 1 through 3 (ie, 2 unique subject records).
    2 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    2 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 2 records  at 2018-08-10 17:58:28.
    +
    2 records and 1 columns were read from REDCap in 0.5 seconds.  The http status code was 200.
    +
    Starting to read 2 records  at 2018-08-10 20:36:47.
    Reading batch 1 of 1, with subjects 1 through 3 (ie, 2 unique subject records).
    -
    2 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    +
    2 records and 24 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
      record_id name_first name_last                                 address
     1         1     Nutmeg  Nutmouse 14 Rose Cottage St.\nKenning UK, 323232
    @@ -227,7 +227,7 @@ 

    )$data

    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-08-10 17:58:29.
    +
    Starting to read 5 records  at 2018-08-10 20:36:49.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-08-10 17:58:30.
    +
    Starting to read 5 records  at 2018-08-10 20:36:50.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    @@ -262,9 +262,9 @@

    token = token, fields = desired_fields_v3 )$data -
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-08-10 17:58:31.
    +
    The data dictionary describing 16 fields was read from REDCap in 0.5 seconds.  The http status code was 200.
    +
    5 records and 1 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    +
    Starting to read 5 records  at 2018-08-10 20:36:52.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    @@ -292,9 +292,9 @@

    )$data
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    2 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 2 records  at 2018-08-10 17:58:33.
    +
    Starting to read 2 records  at 2018-08-10 20:36:53.
    Reading batch 1 of 1, with subjects 3 through 5 (ie, 2 unique subject records).
    -
    2 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    +
    2 records and 24 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
      record_id name_first name_last                            address
     1         3     Marcus      Wood     243 Hill St.\nGuthrie OK 73402
    @@ -336,7 +336,7 @@ 

    )

    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-08-10 17:58:34.
    +
    Starting to read 5 records  at 2018-08-10 20:36:54.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    @@ -373,7 +373,7 @@

    [1] "" $elapsed_seconds -[1] 1.17251 +[1] 1.211472
    @@ -427,7 +427,7 @@

    R6 2.2.2 2017-06-17 CRAN (R 3.5.0) Rcpp 0.12.18 2018-07-23 CRAN (R 3.5.1) readr 1.2.0 2018-08-09 Github (tidyverse/readr@4b2e93a) - REDCapR * 0.9.10.9001 2018-08-10 local + REDCapR * 0.9.10.9001 2018-08-11 local rlang 0.2.1 2018-05-30 CRAN (R 3.5.0) rmarkdown 1.10 2018-06-11 CRAN (R 3.5.0) roxygen2 6.1.0 2018-07-27 CRAN (R 3.5.1) @@ -444,7 +444,7 @@

    withr 2.1.2 2018-03-15 CRAN (R 3.5.0) xml2 1.2.0 2018-01-24 CRAN (R 3.5.0) yaml 2.2.0 2018-07-25 CRAN (R 3.5.1) -

    Report rendered by Will at 2018-08-10, 17:58 -0500 in 11 seconds.

    +

    Report rendered by Will at 2018-08-10, 20:36 -0500 in 12 seconds.

    diff --git a/docs/articles/SecurityDatabase.html b/docs/articles/SecurityDatabase.html index 1461e761..975beba9 100644 --- a/docs/articles/SecurityDatabase.html +++ b/docs/articles/SecurityDatabase.html @@ -108,28 +108,29 @@

    2018-08-10

    Description

    -

    The SQL code below adds schemas, a table and two stored procedures to an existing Microsoft SQL Database. This second database is not essential to calling the REDCap API, but it helps manage tokens securely.

    +

    The SQL code below adds schemas, a table and two stored procedures to an existing Microsoft SQL Server database. This second database is not essential to calling the REDCap API, but it helps manage tokens securely.

    This database contains the tokens and other sensitive content (such as passwords, API tokens, and file paths) that should not be stored in a Git repository (even a private Git repository). These passwords can be retrieved by REDCapR::retrieve_credential_mssql().

    Create a DSN on each client

    -

    After executing the SQL code in an existing database, create an ODBC DSN on each client machine that calls the database. Download the most recent drivers (as of Aug 2016, the most recent version is 13.1 for Windows and Linux, then run the wizard. Many values in the wizard will remain at the default values. Here are the important ones to change.

    +

    After executing the SQL code in an existing database, create an ODBC DSN on each client machine that calls the database. Download the most recent drivers (as of Aug 2018, the most recent version is 17 for Windows and Linux), then run the wizard. Many values in the wizard will remain at the default values. Here are the important ones to change.

    1. Set the DSN’s name field to whatever is used in the repository’s R code.
    2. Set the authenticity method to Integrated Windows authentication.
    3. -
    4. Set the default database to the name of the database that containing the tokens i.e., corresponding to the SQL code below in the example).
    5. +
    6. Set the default database to the name of the database that containing the tokens
    +

    In our code below, both DSN and database are named auxiliary_security.

    Note

    -

    We use Microsoft SQL Server, because that fits our University’s infrastructure the easiest. But this approach theoretically can work with any LDAP-enabled database server. Please contact us if your institution is using something other than SQL Server (or a different configuration of these components), and would like help adapting this approach to your infrastructure.

    +

    We use Microsoft SQL Server, because that fits our university’s infrastructure most easily. But this approach theoretically can work with any LDAP-enabled database server. Please contact us if your institution is using something other than SQL Server (or a different configuration of these components), and would like help adapting this approach.

    Create Database

    -

    This SQL code is run once inside an existing database to establish the schemas, table, and stored procedure used by REDCapR::retrieve_credential_mssql().

    +

    This SQL code is run once inside an existing database to establish the schemas, table, and stored procedure used by REDCapR::retrieve_credential_mssql(). In this example, we’ve arbitrarily called the database auxiliary_security.

    ------- SQL code to create necessary components in a Microsoft SQL Sever database -------
     
     -----------------------------------------------------------------------
    @@ -145,12 +146,12 @@ 

    -- Create a table to contain the token -- CREATE TABLE [redcap_private].[tbl_credential]( - [id] [smallint] NOT NULL, - [username] [varchar](30) NOT NULL, - [project_id] [smallint] NOT NULL, - [instance] [varchar](30) NOT NULL, - [token] [char](32) NOT NULL, - [redcap_uri] [varchar](255) NOT NULL, + id smallint NOT NULL, + username varchar(30) NOT NULL, + project_id smallint NOT NULL, + instance varchar(30) NOT NULL, + token char(32) NOT NULL, + redcap_uri varchar(255) NOT NULL, CONSTRAINT [PK_credential] PRIMARY KEY CLUSTERED ( [id] ASC @@ -159,9 +160,9 @@

    CREATE UNIQUE NONCLUSTERED INDEX [IX_tbl_credential_unique] ON [redcap_private].[tbl_credential] ( - [instance] ASC, - [project_id] ASC, - [username] ASC + instance ASC, + project_id ASC, + username ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] GO @@ -170,33 +171,33 @@

    -- Notice it should a different (and more permissive) schema than the table. -- CREATE PROCEDURE [redcap].[prc_credential] - -- Add the parameters for the stored procedure here - - @project_id smallint, - @instance varchar(30) -AS -BEGIN - -- SET NOCOUNT ON added to prevent extra result sets from - -- interfering with SELECT statements. - SET NOCOUNT ON; - - SELECT username, project_id, token, redcap_uri FROM [redcap_private].[tbl_credential] - WHERE username=system_user AND project_id=@project_id AND instance=@instance -END

    + @project_id smallint, + @instance varchar(30) +AS +BEGIN + SET NOCOUNT ON; + + SELECT username, project_id, token, redcap_uri FROM [redcap_private].[tbl_credential] + WHERE + username = system_user -- The username from the server's OS. + AND + project_id = @project_id -- Restricts to the desired REDCap project. + AND + instance = @instance -- System accommodates multiple REDCap instances. +END

    Create user credentials to the auxiliary database

    -

    Add a user’s LDAP account to the SecurityAuxiliary database so that they can query the tables to retrieve their API.

    -

    Notice that this only gives the permissions to retrieve the token. You must still: 1. grant them API privileges to each appropriate REDCap project, and 2. copy the API from the REDCap database into the SecurityAuxiliary database.

    -

    In the future REDCapR may expose a function that allows the user to perform the second step (through a stored procedure).

    -

    Also, do not give typical users authorization for the ‘redcap_private’ schema. The current system allows the to view only their own tokens.

    +

    Add a user’s LDAP account to the auxiliary_security database so that they can query the tables to retrieve their API.

    +

    Notice that this only gives the permissions to retrieve the token. You still must grant them API privileges to each appropriate REDCap project. The automation in the R file below will copy the API token from the MySQL database into the auxiliary_security database (see the ‘Transfer Credentials’ section).

    +

    Only database admins should have authorization for the ‘redcap_private’ schema. Typical users should not be authorized for this schema. The current system allows typical users to view only their own tokens.

    -----------------------------------------------------------------------
    --- Add a OUHSC's user account to the auxiliary_security database so that they can query the tables to retrieve their API.
    +-- Add a user account to the auxiliary_security database so that they can query the tables to retrieve their API.
     -- Notice that this only gives the permissions to retrieve the token.  You must still:
     --   1) grant them API privileges to each appropriate REDCap project, and
     --   2) copy the API from the REDCap database into the  auxiliary_security database.
    --- Also, do not give typical users authorization for the 'redcap_private' schema.  The current system allows the to view only their own tokens.
    +-- Also, do not give typical users authorization for the 'redcap_private' schema.  The current system allows them to view only their own tokens.
     -----------------------------------------------------------------------
     
     -- STEP #1: Declare the user name.  If everything runs correctly, this should be the only piece of code that you need to modify.
    @@ -222,7 +223,7 @@ 

    print 'Logins matching desired name should equal 1. It equals: ' + CONVERT(varchar, @login_count); print '' ----------------------------------------------------------------------- --- STEP #3: Create a user account for the *data base*, after switching the database under focus to auxiliary_security. +-- STEP #3: Create a user account for the *database*, after switching the database under focus to auxiliary_security. print 'Step #3 executing....' USE [auxiliary_security] DECLARE @sql_create_user nvarchar(max) @@ -269,142 +270,171 @@

    Transfer Credentials

    -

    Manually transfer tokens to the auxiliary server becomes unmanageable as your institution’s collection of API users grows. This script demonstrates how to progamatically transfer all tokens from multiple REDCap instances on your network. The basic steps are:

    +

    Manually transferring tokens to the auxiliary server becomes unmanageable as your institution’s collection of API users grows. This script demonstrates how to progamatically transfer all tokens from multiple REDCap instances. The basic steps are:

      -
    1. Read from the MySQL database underneath each REDCap instance.
    2. +
    3. Read from the MySQL database(s) underneath each REDCap instance on your campus.
    4. Combine & groom the credentials.
    5. -
    6. Upload to SQL Server.
    7. +
    8. Upload to SQL Server (called auxiliary_security here).
    rm(list=ls(all=TRUE)) #Clear the memory for any variables set from any previous runs.
     
     # ---- load-sources ------------------------------------------------------------
     
     # ---- load-packages -----------------------------------------------------------
    -library(magrittr)
    -requireNamespace("odbc")
    -requireNamespace("dplyr")
    -requireNamespace("readr")
    -requireNamespace("tibble")
    -requireNamespace("checkmate")
    -
    -# ---- declare-globals ---------------------------------------------------------
    -
    -# The Activity Directory name that should precede each username.
    -#   This should correspond with the result of `SYSTEM_USER`
    -#   (https://msdn.microsoft.com/en-us/library/ms179930.aspx)
    -ldap_prefix <- "OUHSC\\"
    -
    -#Create a SQL statement for each REDCap instance.  Only the `instance` value should change.
    -sql <- "
    -  SELECT username, project_id, api_token
    -  FROM redcap_user_rights
    -  WHERE api_token IS NOT NULL"
    -
    -#Update this ad-hoc CSV.  Each row should represent one REDCap instance.
    -#   Choose any casual name for the first variable, consistent with the `tweak-data` chunk below.
    -#   Enter the exact URL for the second variable.
    -ds_url <- readr::read_csv(paste(
    -  "instance,redcap_uri",
    -  "production,https://redcap-production.ouhsc.edu/redcap/api/",
    -  "dev,https://redcap-dev.ouhsc.edu/redcap/api/",
    -sep="\n"))
    +if( !require(OuhscMunge) )
    +  stop('The `OuhscMunge` package needs to be installed with `devtools::install_github("OuhscBbmc/OuhscMunge")`.')
    +
    +testit::assert(
    +  "The `OuhscMunge` package should meet a minimal version.",
    +  compareVersion( as.character(packageVersion("OuhscMunge")), "0.1.9.9009") >= 0L
    +)
    +
    +library(magrittr)
    +requireNamespace("DBI")
    +requireNamespace("odbc")
    +requireNamespace("dplyr")
    +requireNamespace("readr")
    +requireNamespace("tibble")
    +requireNamespace("testit")
    +requireNamespace("checkmate")
    +requireNamespace("OuhscMunge")  # devtools::install_github("OuhscBbmc/OuhscMunge")
    +
    +
    +# ---- declare-globals ---------------------------------------------------------
    +# This file assume your campus has two REDCap instances.
    +# Modify each (a) database name, (b) REDCap URL, and (c) DSN name.
    +
    +name_production <- "production"
    +name_dev        <- "dev"
    +
    +uri_production  <- "https://redcap-production.ouhsc.edu/redcap/api/",
    +uri_dev         <- "https://redcap-dev.ouhsc.edu/redcap/api/"
     
    -
    -# ---- load-data ---------------------------------------------------------------
    -
    -# Load the credentials from the first instance.
    -channel <- DBI::dbConnect(odbc::odbc(), dsn="redcap-production") 
    -ds_prod <- DBI::dbGetQuery(channel, sql)
    -DBI::dbDisconnect(channel); rm(channel)
    -
    -# Load the credentials from the second instance.
    -#   Duplicate or remove this block, dependending on the number of instances.
    -channel <- DBI::dbConnect(odbc::odbc(), dsn="redcap-dev")
    -ds_dev  <- DBI::dbGetQuery(channel, sql)
    -DBI::dbDisconnect(channel); rm(channel)
    +dsn_production  <- "redcap-production"
    +dsn_dev         <- "redcap-dev"
    +dsn_source      <- "auxiliary_security" # The DSN of the token server.
    +
    +# The Activity Directory name that should precede each username.
    +#   This should correspond with the result of SQL Server's `SYSTEM_USER` function
    +#   (https://msdn.microsoft.com/en-us/library/ms179930.aspx)
    +ldap_prefix <- "OUHSC\\"
    +
    +####
    +# Nothing below this line should need to change, assuming:
    +# 1. the vignette was followed exactly (https://ouhscbbmc.github.io/REDCapR/articles/SecurityDatabase.html),
    +# 2. your campus has exactly two REDCap instances.
     
    -# Assert these variables contain valid datasets (instead of a character error message), and
    -#   that at least some rows were returned.
    -#   Adjust this to smaller values if necessary.  It's really just to catch blatant retrieval problems.
    -checkmate::assert_data_frame(ds_bbmc, min.rows=5)
    -checkmate::assert_data_frame(ds_dev , min.rows=5)
    -
    +# SQL sent to the MySQL database underneath each REDCap instance.
    +sql <- "
    +  SELECT username, project_id, api_token
    +  FROM redcap_user_rights
    +  WHERE api_token IS NOT NULL
    +"
     
    -# ---- tweak-data --------------------------------------------------------------
    -
    -#Label each instance, so they're distinguishable later.  Add/remove lines, depending on the number of campus instances
    -ds_prod$instance <- "production"
    -ds_dev$instance  <- "dev"
    -
    -#Combine the token collection from each instance.  Then prefix the username and include the URL of each instance.
    -ds <- ds_prod %>%
    -  dplyr::union(ds_dev) %>%                                     # Add/remove unions, based on the number of REDCap instances.
    -  dplyr::select_(
    -    "username"             = "`username`"
    -    , "project_id"         = "`project_id`"
    -    , "instance"           = "`instance`"
    -    , "token"              = "`api_token`"
    -  ) %>%
    -  dplyr::arrange(instance, project_id, username) %>%
    -  dplyr::mutate(
    -    username               = paste0(ldap_prefix, username),    # Qualify for the Active Directory.
    -    id                     = seq_len(n())                      # For the sake of a clustered primary key.
    -  ) %>%
    -  dplyr::left_join( ds_url, by="instance")                     # Include the instance URL.
    -
    -rm(ds_prod, ds_dev, ds_url)
    -
    +# Update this ad-hoc CSV.  Each row should represent one REDCap instance.
    +ds_url <- tibble::tribble(
    +  ~instance         , ~redcap_uri,
    +  name_production   , uri_production,
    +  name_dev          , uri_dev
    +)
    +
    +# Remove variables that aren't used below.
    +rm(uri_production, uri_dev)
    +
    +
    +# ---- load-data ---------------------------------------------------------------
    +
    +# Load the credentials from the first/production REDCap instance.
    +cnn_production  <- DBI::dbConnect(odbc::odbc(), dsn=dsn_production)
    +ds_production   <- DBI::dbGetQuery(cnn_production, sql)
    +DBI::dbDisconnect(cnn_production); rm(cnn_production, dsn_production)
    +
    +# Load the credentials from the second/dev REDCap instance.
    +cnn_dev         <- DBI::dbConnect(odbc::odbc(), dsn=dsn_dev)
    +ds_dev          <- DBI::dbGetQuery(cnn_dev, sql)
    +DBI::dbDisconnect(cnn_dev); rm(cnn_dev, dsn_dev)
    +
    +rm(sql)
     
    -# ---- verify-values -----------------------------------------------------------
    -# devtools::install_github("OuhscBbmc/OuhscMunge"); OuhscMunge::verify_value_headstart(ds)
    -# Assert that the dataset is well-behaved.
    -checkmate::assert_integer(  ds$id         , any.missing=F , lower=1, upper=2^31-1 , unique=T)
    -checkmate::assert_character(ds$username   , any.missing=F , pattern="^.{1,255}$"            )
    -checkmate::assert_integer(  ds$project_id , any.missing=F , lower=1, upper=2^31-1           )
    -checkmate::assert_character(ds$token      , any.missing=F , pattern="^.{32}$"     , unique=T)
    -checkmate::assert_character(ds$instance   , any.missing=F , pattern="^.{1,255}$"            )
    -checkmate::assert_character(ds$redcap_uri , any.missing=F , pattern="^.{1,255}$"            )
    -
    -testit::assert(
    -  "The `username` x `project_id` x `instance` must be unique.",
    -  sum(duplicated(paste0(ds$username, "-", ds$project_id, "-", ds$instance))) == 0L
    -)
    -
    -testit::assert("There should be at least 10 tokens written." , 10L <= nrow(ds))
    -
    -
    -# ---- specify-columns-to-upload -----------------------------------------------
    -
    -# Dictate the exact columns and order that will be uploaded.
    -columns_to_write <- c("id", "username", "project_id", "instance", "token", "redcap_uri")
    -ds_slim <- ds[, columns_to_write]
    -
    -rm(columns_to_write)
    -
    -
    -# ---- upload-to-db-credential ------------------------------------------------------------
    +# Assert these are valid datasets and contain at least 5 rows.
    +#   Adjust '5' to smaller value if necessary.  It's just to catch blatant retrieval problems.
    +checkmate::assert_data_frame(ds_production, min.rows=5)
    +checkmate::assert_data_frame(ds_dev       , min.rows=5)
    +
    +
    +# ---- tweak-data --------------------------------------------------------------
    +
    +# Label each instance, so they're distinguishable later.
    +ds_production$instance <- name_production
    +ds_dev$instance        <- name_dev
    +
    +# Stack the token collection from each instance.  Then prefix the username and include the URL of each instance.
    +ds <- ds_production %>%
    +  dplyr::union(ds_dev) %>%                                # Remove union if the dev instance isn't included.
    +  tibble::as_tibble() %>%
    +  dplyr::select(
    +    username             = username,
    +    project_id           = project_id,
    +    instance             = instance,
    +    token                = api_token
    +  ) %>%
    +  dplyr::mutate(
    +    username             = paste0(ldap_prefix, username), # Qualify for the Active Directory.
    +  ) %>%
    +  dplyr::left_join( ds_url, by="instance") %>%            # Include the instance URL.
    +  dplyr::arrange(instance, project_id, username) %>%
    +  tibble::rowid_to_column("id")                           # For the sake of a clustered primary key.
     
    -if( !require(OuhscMunge) ) 
    -  stop('The `OuhscMunge` package needs to be installed with `devtools::install_github("OuhscBbmc/OuhscMunge")`.')
    -
    -OuhscMunge::upload_sqls_odbc(
    -  d               = ds_slim,
    -  schema_name     = "redcap_private",
    -  table_name      = "tbl_credential",
    -  dsn_name        = "auxiliary_security",
    -  create_table    = FALSE,
    -  clear_table     = TRUE,
    -  transaction     = TRUE,
    -  verbose         = TRUE
    -)
    -
    -(elapsed_duration <-  Sys.time() - start_time) #0.6026149 secs 2016-08-29.
    -rm(db_table, column_info, var_types, start_time, elapsed_duration)
    +rm(ds_production, ds_dev, ds_url) +rm(name_production, name_dev) +rm(ldap_prefix) + + +# ---- verify-values ----------------------------------------------------------- + +# Assert that the dataset is well-behaved. +# OuhscMunge::verify_value_headstart(ds) +checkmate::assert_integer( ds$id , any.missing=F, lower=1, upper=.Machine$integer.max, unique=T) +checkmate::assert_character(ds$username , any.missing=F, pattern="^.{1,255}$" ) +checkmate::assert_integer( ds$project_id , any.missing=F, lower=1, upper=.Machine$integer.max ) +checkmate::assert_character(ds$token , any.missing=F, pattern="^[A-Z0-9]{32}$" , unique=T) +checkmate::assert_character(ds$instance , any.missing=F, pattern="^.{1,255}$" ) +checkmate::assert_character(ds$redcap_uri , any.missing=F, pattern="^.{1,255}$" ) + +testit::assert( + "The `username` x `project_id` x `instance` must be unique.", + sum(duplicated(paste0(ds$username, "-", ds$project_id, "-", ds$instance))) == 0L +) + +testit::assert("At least 10 tokens should be ready to write." , 10L <= nrow(ds)) + + +# ---- specify-columns-to-upload ----------------------------------------------- + +# Dictate the exact columns and order that will be uploaded. +columns_to_write <- c("id", "username", "project_id", "instance", "token", "redcap_uri") +ds_slim <- ds[, columns_to_write] +rm(columns_to_write) + + +# ---- upload-to-db ------------------------------------------------------------------ + +OuhscMunge::upload_sqls_odbc( + d = ds_slim, + schema_name = "redcap_private", + table_name = "tbl_credential", + dsn_name = dsn_source, + create_table = FALSE, + clear_table = TRUE, + transaction = TRUE, + verbose = TRUE +) +# Uploading 252 tokens takes 0.004 minutes.

    Document Info

    -

    This document is primarily based on REDCap version 6.11.5, and was last updated 2016-08-30. A development version of the document is available on GitHub: https://ouhscbbmc.github.io/REDCapR/articles/SecurityDatabase.html

    +

    This document is primarily based on REDCap version 8.4.0, and was last updated 2018-08-10. A development version of the document is available on GitHub: https://ouhscbbmc.github.io/REDCapR/articles/SecurityDatabase.html

    diff --git a/docs/articles/advanced-redcapr-operations.html b/docs/articles/advanced-redcapr-operations.html index c166c010..8076f403 100644 --- a/docs/articles/advanced-redcapr-operations.html +++ b/docs/articles/advanced-redcapr-operations.html @@ -130,7 +130,7 @@

    events_to_retain <- c("dose_1_arm_1", "visit_1_arm_1", "dose_2_arm_1", "visit_2_arm_1") ds_long <- REDCapR::redcap_read_oneshot(redcap_uri=uri, token=token_longitudinal)$data -
    #> 18 records and 125 columns were read from REDCap in 0.4 seconds.  The http status code was 200.
    +
    #> 18 records and 125 columns were read from REDCap in 0.5 seconds.  The http status code was 200.
    ds_long %>% 
       dplyr::select(study_id, redcap_event_name, pmq1, pmq2, pmq3, pmq4)
    #>    study_id        redcap_event_name pmq1 pmq2 pmq3 pmq4
    @@ -238,14 +238,14 @@ 

    token = token_simple, config_options = config_options )$data

    -
    #> 5 records and 24 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    +
    #> 5 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    #> 5 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    +
    #> 5 records and 24 columns were read from REDCap in 0.3 seconds.  The http status code was 200.

    @@ -298,7 +298,7 @@

    #> R6 2.2.2 2017-06-17 CRAN (R 3.5.0) #> Rcpp 0.12.18 2018-07-23 CRAN (R 3.5.1) #> readr 1.2.0 2018-08-09 Github (tidyverse/readr@4b2e93a) -#> REDCapR * 0.9.10.9001 2018-08-10 local +#> REDCapR * 0.9.10.9001 2018-08-11 local #> rlang 0.2.1 2018-05-30 CRAN (R 3.5.0) #> rmarkdown 1.10 2018-06-11 CRAN (R 3.5.0) #> roxygen2 6.1.0 2018-07-27 CRAN (R 3.5.1) @@ -316,7 +316,7 @@

    #> withr 2.1.2 2018-03-15 CRAN (R 3.5.0) #> xml2 1.2.0 2018-01-24 CRAN (R 3.5.0) #> yaml 2.2.0 2018-07-25 CRAN (R 3.5.1) -

    Report rendered by Will at 2018-08-10, 17:58 -0500 in 3 seconds.

    +

    Report rendered by Will at 2018-08-10, 20:36 -0500 in 3 seconds.

    diff --git a/docs/reference/kernel_api.html b/docs/reference/kernel_api.html index 7a8bc9bf..a67bac8f 100644 --- a/docs/reference/kernel_api.html +++ b/docs/reference/kernel_api.html @@ -186,7 +186,7 @@

    Examp # Consume the results in a few different ways. kernel$result
    #> Response [https://bbmc.ouhsc.edu/redcap/api/] -#> Date: 2018-08-10 22:58 +#> Date: 2018-08-11 01:36 #> Status: 200 #> Content-Type: text/csv; charset=utf-8 #> Size: 557 B diff --git a/documentation-peek.pdf b/documentation-peek.pdf index a3d6606ad2d1533830ce1cdf54e6e62944666194..c303a4cdd32eee860c0e7795fd961719acacf2f1 100644 GIT binary patch delta 113 zcmX>xi~qzd{)QID7N#xCRg>9_42;bUET%V1W|oDrw=bT|>}u(3WMSxL;b?AXY2s?= hYT)YR3IvwMCXSBI<_6BDhQ@XZHiVQ+*FMWE3jjIE9wYz& delta 113 zcmX>xi~qzd{)QID7N#xCRg>8a%}p&#jixtDW|oDrw=bT|>}u(3W^7<;WNc~bXy#;Y i?BZnUXlCx>YVK@gU~KMaVq{`ur(i=!$#m_r%(4I`%N>*e diff --git a/inst/doc/BasicREDCapROperations.html b/inst/doc/BasicREDCapROperations.html index 9a27b334..be460122 100644 --- a/inst/doc/BasicREDCapROperations.html +++ b/inst/doc/BasicREDCapROperations.html @@ -300,9 +300,9 @@

    Read all records and fields.

    ds_all_rows_all_fields <- redcap_read(redcap_uri=uri, token=token)$data
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-08-10 17:57:48.
    +
    Starting to read 5 records  at 2018-08-10 20:36:05.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    -
    5 records and 24 columns were read from REDCap in 0.4 seconds.  The http status code was 200.
    +
    5 records and 24 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
      record_id name_first name_last                                 address
     1         1     Nutmeg  Nutmouse 14 Rose Cottage St.\nKenning UK, 323232
    @@ -354,7 +354,7 @@ 

    Read a subset of the records.

    )$data
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    2 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 2 records  at 2018-08-10 17:57:49.
    +
    Starting to read 2 records  at 2018-08-10 20:36:07.
    Reading batch 1 of 1, with subjects 1 through 3 (ie, 2 unique subject records).
    2 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    #Return only records with IDs of 1 and 3 (alternate way)
    @@ -366,9 +366,9 @@ 

    Read a subset of the records.

    )$data
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    2 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 2 records  at 2018-08-10 17:57:50.
    +
    Starting to read 2 records  at 2018-08-10 20:36:08.
    Reading batch 1 of 1, with subjects 1 through 3 (ie, 2 unique subject records).
    -
    2 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    +
    2 records and 24 columns were read from REDCap in 0.4 seconds.  The http status code was 200.
      record_id name_first name_last                                 address
     1         1     Nutmeg  Nutmouse 14 Rose Cottage St.\nKenning UK, 323232
    @@ -398,7 +398,7 @@ 

    Read a subset of the fields.

    )$data
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-08-10 17:57:51.
    +
    Starting to read 5 records  at 2018-08-10 20:36:09.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-08-10 17:57:53.
    +
    Starting to read 5 records  at 2018-08-10 20:36:10.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    @@ -434,9 +434,9 @@

    Read a subset of records, conditioned on the values in some variables.

    )$data
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-08-10 17:57:54.
    +
    Starting to read 5 records  at 2018-08-10 20:36:11.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    -
    5 records and 3 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    +
    5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
      record_id        dob weight
     1         1 2003-08-30      1
    @@ -462,7 +462,7 @@ 

    Read a subset of records, conditioned on the values in some variables.

    )$data
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    2 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 2 records  at 2018-08-10 17:57:55.
    +
    Starting to read 2 records  at 2018-08-10 20:36:12.
    Reading batch 1 of 1, with subjects 3 through 5 (ie, 2 unique subject records).
    2 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    @@ -505,7 +505,7 @@

    Additional Returned Information

    )
    The data dictionary describing 16 fields was read from REDCap in 0.2 seconds.  The http status code was 200.
    5 records and 1 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    -
    Starting to read 5 records  at 2018-08-10 17:57:56.
    +
    Starting to read 5 records  at 2018-08-10 20:36:14.
    Reading batch 1 of 1, with subjects 1 through 5 (ie, 5 unique subject records).
    5 records and 3 columns were read from REDCap in 0.2 seconds.  The http status code was 200.
    @@ -542,7 +542,7 @@

    Additional Returned Information

    [1] "" $elapsed_seconds -[1] 1.116541 +[1] 1.185983
    @@ -579,7 +579,6 @@

    Session Information

    digest 0.6.15 2018-01-28 CRAN (R 3.5.0) dplyr 0.7.6 2018-06-29 CRAN (R 3.5.1) evaluate 0.11 2018-07-17 CRAN (R 3.5.1) - fs 1.2.5 2018-07-30 CRAN (R 3.5.1) git2r 0.23.0 2018-07-17 CRAN (R 3.5.1) glue 1.3.0 2018-07-17 CRAN (R 3.5.1) hms 0.4.2.9001 2018-08-09 Github (tidyverse/hms@979286f) @@ -588,13 +587,11 @@

    Session Information

    kableExtra 0.9.0 2018-05-21 CRAN (R 3.5.0) knitr * 1.20 2018-02-20 CRAN (R 3.5.0) magrittr * 1.5 2014-11-22 CRAN (R 3.5.0) - MASS 7.3-50 2018-04-30 CRAN (R 3.5.1) memoise 1.1.0 2017-04-21 CRAN (R 3.5.0) munsell 0.5.0 2018-06-12 CRAN (R 3.5.0) packrat 0.4.9-3 2018-06-01 CRAN (R 3.5.0) pillar 1.3.0 2018-07-14 CRAN (R 3.5.1) pkgconfig 2.0.1 2017-03-21 CRAN (R 3.5.0) - pkgdown 1.1.0 2018-06-02 CRAN (R 3.5.1) purrr 0.2.5 2018-05-29 CRAN (R 3.5.0) R6 2.2.2 2017-06-17 CRAN (R 3.5.0) Rcpp 0.12.18 2018-07-23 CRAN (R 3.5.1) @@ -619,7 +616,7 @@

    Session Information

    xml2 1.2.0 2018-01-24 CRAN (R 3.5.0) yaml 2.2.0 2018-07-25 CRAN (R 3.5.1) -

    Report rendered by Will at 2018-08-10, 17:57 -0500 in 10 seconds.

    +

    Report rendered by Will at 2018-08-10, 20:36 -0500 in 10 seconds.

    diff --git a/inst/doc/SecurityDatabase.Rmd b/inst/doc/SecurityDatabase.Rmd index 92f46746..fef69fc6 100644 --- a/inst/doc/SecurityDatabase.Rmd +++ b/inst/doc/SecurityDatabase.Rmd @@ -11,7 +11,7 @@ vignette: > Description ======================================== -The SQL code below adds schemas, a table and two stored procedures to an existing Microsoft SQL Database. This second database is not essential to calling the REDCap API, but it helps manage tokens securely. +The SQL code below adds schemas, a table and two stored procedures to an existing Microsoft SQL Server database. This second database is not essential to calling the REDCap API, but it helps manage tokens securely. This database contains the tokens and other sensitive content (such as passwords, API tokens, and file paths) that should not be stored in a Git repository (even a private Git repository). These passwords can be retrieved by `REDCapR::retrieve_credential_mssql()`. @@ -19,23 +19,25 @@ This database contains the tokens and other sensitive content (such as passwords Create a DSN on each client ======================================== -After executing the SQL code in an existing database, create an ODBC [DSN](http://en.wikipedia.org/wiki/Data_source_name) on *each* client machine that calls the database. Download the most recent drivers (as of Aug 2016, the [most recent version is 13.1](https://msdn.microsoft.com/library/mt703139.aspx) for Windows and Linux, then run the wizard. Many values in the wizard will remain at the default values. Here are the important ones to change. +After executing the SQL code in an existing database, create an ODBC [DSN](http://en.wikipedia.org/wiki/Data_source_name) on *each* client machine that calls the database. Download the most recent drivers (as of Aug 2018, the [most recent version is 17](https://docs.microsoft.com/en-us/sql/connect/odbc/download-odbc-driver-for-sql-server) for Windows and Linux), then run the wizard. Many values in the wizard will remain at the default values. Here are the important ones to change. 1. Set the DSN's `name` field to whatever is used in the repository's R code. 2. Set the authenticity method to `Integrated Windows authentication`. -3. Set the `default database` to the name of the database that containing the tokens *i.e.*, corresponding to the SQL code below in the example). +3. Set the `default database` to the name of the database that containing the tokens + +In our code below, both DSN and database are named `auxiliary_security`. Note ======================================== -We use Microsoft SQL Server, because that fits our University's infrastructure the easiest. But this approach theoretically can work with any LDAP-enabled database server. Please contact us if your institution is using something other than SQL Server (or a different configuration of these components), and would like help adapting this approach to your infrastructure. +We use Microsoft SQL Server, because that fits our university's infrastructure most easily. But this approach theoretically can work with any LDAP-enabled database server. Please contact us if your institution is using something other than SQL Server (or a different configuration of these components), and would like help adapting this approach. Create Database ======================================== -This SQL code is run once inside an existing database to establish the schemas, table, and stored procedure used by `REDCapR::retrieve_credential_mssql()`. +This SQL code is run once inside an existing database to establish the schemas, table, and stored procedure used by `REDCapR::retrieve_credential_mssql()`. In this example, we've arbitrarily called the database `auxiliary_security`. ```sql ------- SQL code to create necessary components in a Microsoft SQL Sever database ------- @@ -53,12 +55,12 @@ GO -- Create a table to contain the token -- CREATE TABLE [redcap_private].[tbl_credential]( - [id] [smallint] NOT NULL, - [username] [varchar](30) NOT NULL, - [project_id] [smallint] NOT NULL, - [instance] [varchar](30) NOT NULL, - [token] [char](32) NOT NULL, - [redcap_uri] [varchar](255) NOT NULL, + id smallint NOT NULL, + username varchar(30) NOT NULL, + project_id smallint NOT NULL, + instance varchar(30) NOT NULL, + token char(32) NOT NULL, + redcap_uri varchar(255) NOT NULL, CONSTRAINT [PK_credential] PRIMARY KEY CLUSTERED ( [id] ASC @@ -67,9 +69,9 @@ CREATE TABLE [redcap_private].[tbl_credential]( CREATE UNIQUE NONCLUSTERED INDEX [IX_tbl_credential_unique] ON [redcap_private].[tbl_credential] ( - [instance] ASC, - [project_id] ASC, - [username] ASC + instance ASC, + project_id ASC, + username ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] GO @@ -78,18 +80,19 @@ GO -- Notice it should a different (and more permissive) schema than the table. -- CREATE PROCEDURE [redcap].[prc_credential] - -- Add the parameters for the stored procedure here - @project_id smallint, - @instance varchar(30) + @instance varchar(30) AS BEGIN - -- SET NOCOUNT ON added to prevent extra result sets from - -- interfering with SELECT statements. SET NOCOUNT ON; SELECT username, project_id, token, redcap_uri FROM [redcap_private].[tbl_credential] - WHERE username=system_user AND project_id=@project_id AND instance=@instance + WHERE + username = system_user -- The username from the server's OS. + AND + project_id = @project_id -- Restricts to the desired REDCap project. + AND + instance = @instance -- System accommodates multiple REDCap instances. END ``` @@ -97,23 +100,19 @@ END Create user credentials to the auxiliary database ======================================== -Add a user's LDAP account to the `SecurityAuxiliary` database so that they can query the tables to retrieve their API. - -Notice that this only gives the permissions to retrieve the token. You must still: -1. grant them API privileges to each appropriate REDCap project, and -2. copy the API from the REDCap database into the SecurityAuxiliary database. +Add a user's LDAP account to the `auxiliary_security` database so that they can query the tables to retrieve their API. -In the future `REDCapR` may expose a function that allows the user to perform the second step (through a stored procedure). +Notice that this only gives the permissions to retrieve the token. You still must grant them API privileges to each appropriate REDCap project. The automation in the R file below will copy the API token from the MySQL database into the `auxiliary_security` database (see the 'Transfer Credentials' section). -Also, do not give typical users authorization for the 'redcap_private' schema. The current system allows the to view only their own tokens. +Only database admins should have authorization for the 'redcap_private' schema. Typical users should not be authorized for this schema. The current system allows typical users to view only their own tokens. ```sql ----------------------------------------------------------------------- --- Add a OUHSC's user account to the auxiliary_security database so that they can query the tables to retrieve their API. +-- Add a user account to the auxiliary_security database so that they can query the tables to retrieve their API. -- Notice that this only gives the permissions to retrieve the token. You must still: -- 1) grant them API privileges to each appropriate REDCap project, and -- 2) copy the API from the REDCap database into the auxiliary_security database. --- Also, do not give typical users authorization for the 'redcap_private' schema. The current system allows the to view only their own tokens. +-- Also, do not give typical users authorization for the 'redcap_private' schema. The current system allows them to view only their own tokens. ----------------------------------------------------------------------- -- STEP #1: Declare the user name. If everything runs correctly, this should be the only piece of code that you need to modify. @@ -139,7 +138,7 @@ DECLARE @login_count AS INT; SET @login_count = (SELECT COUNT(*) AS login_count print 'Logins matching desired name should equal 1. It equals: ' + CONVERT(varchar, @login_count); print '' ----------------------------------------------------------------------- --- STEP #3: Create a user account for the *data base*, after switching the database under focus to auxiliary_security. +-- STEP #3: Create a user account for the *database*, after switching the database under focus to auxiliary_security. print 'Step #3 executing....' USE [auxiliary_security] DECLARE @sql_create_user nvarchar(max) @@ -186,11 +185,11 @@ print 'Step #5 executed'; print '' Transfer Credentials ======================================== -Manually transfer tokens to the auxiliary server becomes unmanageable as your institution's collection of API users grows. This script demonstrates how to progamatically transfer all tokens from multiple REDCap instances on your network. The basic steps are: +Manually transferring tokens to the auxiliary server becomes unmanageable as your institution's collection of API users grows. This script demonstrates how to progamatically transfer all tokens from multiple REDCap instances. The basic steps are: -1. Read from the MySQL database underneath each REDCap instance. +1. Read from the MySQL database(s) underneath each REDCap instance on your campus. 1. Combine & groom the credentials. -1. Upload to SQL Server. +1. Upload to SQL Server (called `auxiliary_security` here). ```r rm(list=ls(all=TRUE)) #Clear the memory for any variables set from any previous runs. @@ -198,130 +197,159 @@ rm(list=ls(all=TRUE)) #Clear the memory for any variables set from any previous # ---- load-sources ------------------------------------------------------------ # ---- load-packages ----------------------------------------------------------- +if( !require(OuhscMunge) ) + stop('The `OuhscMunge` package needs to be installed with `devtools::install_github("OuhscBbmc/OuhscMunge")`.') + +testit::assert( + "The `OuhscMunge` package should meet a minimal version.", + compareVersion( as.character(packageVersion("OuhscMunge")), "0.1.9.9009") >= 0L +) + library(magrittr) +requireNamespace("DBI") requireNamespace("odbc") requireNamespace("dplyr") requireNamespace("readr") requireNamespace("tibble") +requireNamespace("testit") requireNamespace("checkmate") +requireNamespace("OuhscMunge") # devtools::install_github("OuhscBbmc/OuhscMunge") + # ---- declare-globals --------------------------------------------------------- +# This file assume your campus has two REDCap instances. +# Modify each (a) database name, (b) REDCap URL, and (c) DSN name. + +name_production <- "production" +name_dev <- "dev" + +uri_production <- "https://redcap-production.ouhsc.edu/redcap/api/", +uri_dev <- "https://redcap-dev.ouhsc.edu/redcap/api/" + +dsn_production <- "redcap-production" +dsn_dev <- "redcap-dev" +dsn_source <- "auxiliary_security" # The DSN of the token server. # The Activity Directory name that should precede each username. -# This should correspond with the result of `SYSTEM_USER` +# This should correspond with the result of SQL Server's `SYSTEM_USER` function # (https://msdn.microsoft.com/en-us/library/ms179930.aspx) ldap_prefix <- "OUHSC\\" -#Create a SQL statement for each REDCap instance. Only the `instance` value should change. +#### +# Nothing below this line should need to change, assuming: +# 1. the vignette was followed exactly (https://ouhscbbmc.github.io/REDCapR/articles/SecurityDatabase.html), +# 2. your campus has exactly two REDCap instances. + +# SQL sent to the MySQL database underneath each REDCap instance. sql <- " SELECT username, project_id, api_token FROM redcap_user_rights - WHERE api_token IS NOT NULL" + WHERE api_token IS NOT NULL +" + +# Update this ad-hoc CSV. Each row should represent one REDCap instance. +ds_url <- tibble::tribble( + ~instance , ~redcap_uri, + name_production , uri_production, + name_dev , uri_dev +) -#Update this ad-hoc CSV. Each row should represent one REDCap instance. -# Choose any casual name for the first variable, consistent with the `tweak-data` chunk below. -# Enter the exact URL for the second variable. -ds_url <- readr::read_csv(paste( - "instance,redcap_uri", - "production,https://redcap-production.ouhsc.edu/redcap/api/", - "dev,https://redcap-dev.ouhsc.edu/redcap/api/", -sep="\n")) +# Remove variables that aren't used below. +rm(uri_production, uri_dev) # ---- load-data --------------------------------------------------------------- -# Load the credentials from the first instance. -channel <- DBI::dbConnect(odbc::odbc(), dsn="redcap-production") -ds_prod <- DBI::dbGetQuery(channel, sql) -DBI::dbDisconnect(channel); rm(channel) +# Load the credentials from the first/production REDCap instance. +cnn_production <- DBI::dbConnect(odbc::odbc(), dsn=dsn_production) +ds_production <- DBI::dbGetQuery(cnn_production, sql) +DBI::dbDisconnect(cnn_production); rm(cnn_production, dsn_production) -# Load the credentials from the second instance. -# Duplicate or remove this block, dependending on the number of instances. -channel <- DBI::dbConnect(odbc::odbc(), dsn="redcap-dev") -ds_dev <- DBI::dbGetQuery(channel, sql) -DBI::dbDisconnect(channel); rm(channel) +# Load the credentials from the second/dev REDCap instance. +cnn_dev <- DBI::dbConnect(odbc::odbc(), dsn=dsn_dev) +ds_dev <- DBI::dbGetQuery(cnn_dev, sql) +DBI::dbDisconnect(cnn_dev); rm(cnn_dev, dsn_dev) -# Assert these variables contain valid datasets (instead of a character error message), and -# that at least some rows were returned. -# Adjust this to smaller values if necessary. It's really just to catch blatant retrieval problems. -checkmate::assert_data_frame(ds_bbmc, min.rows=5) -checkmate::assert_data_frame(ds_dev , min.rows=5) +rm(sql) + +# Assert these are valid datasets and contain at least 5 rows. +# Adjust '5' to smaller value if necessary. It's just to catch blatant retrieval problems. +checkmate::assert_data_frame(ds_production, min.rows=5) +checkmate::assert_data_frame(ds_dev , min.rows=5) # ---- tweak-data -------------------------------------------------------------- -#Label each instance, so they're distinguishable later. Add/remove lines, depending on the number of campus instances -ds_prod$instance <- "production" -ds_dev$instance <- "dev" - -#Combine the token collection from each instance. Then prefix the username and include the URL of each instance. -ds <- ds_prod %>% - dplyr::union(ds_dev) %>% # Add/remove unions, based on the number of REDCap instances. - dplyr::select_( - "username" = "`username`" - , "project_id" = "`project_id`" - , "instance" = "`instance`" - , "token" = "`api_token`" +# Label each instance, so they're distinguishable later. +ds_production$instance <- name_production +ds_dev$instance <- name_dev + +# Stack the token collection from each instance. Then prefix the username and include the URL of each instance. +ds <- ds_production %>% + dplyr::union(ds_dev) %>% # Remove union if the dev instance isn't included. + tibble::as_tibble() %>% + dplyr::select( + username = username, + project_id = project_id, + instance = instance, + token = api_token ) %>% - dplyr::arrange(instance, project_id, username) %>% dplyr::mutate( - username = paste0(ldap_prefix, username), # Qualify for the Active Directory. - id = seq_len(n()) # For the sake of a clustered primary key. + username = paste0(ldap_prefix, username), # Qualify for the Active Directory. ) %>% - dplyr::left_join( ds_url, by="instance") # Include the instance URL. + dplyr::left_join( ds_url, by="instance") %>% # Include the instance URL. + dplyr::arrange(instance, project_id, username) %>% + tibble::rowid_to_column("id") # For the sake of a clustered primary key. -rm(ds_prod, ds_dev, ds_url) +rm(ds_production, ds_dev, ds_url) +rm(name_production, name_dev) +rm(ldap_prefix) # ---- verify-values ----------------------------------------------------------- -# devtools::install_github("OuhscBbmc/OuhscMunge"); OuhscMunge::verify_value_headstart(ds) + # Assert that the dataset is well-behaved. -checkmate::assert_integer( ds$id , any.missing=F , lower=1, upper=2^31-1 , unique=T) -checkmate::assert_character(ds$username , any.missing=F , pattern="^.{1,255}$" ) -checkmate::assert_integer( ds$project_id , any.missing=F , lower=1, upper=2^31-1 ) -checkmate::assert_character(ds$token , any.missing=F , pattern="^.{32}$" , unique=T) -checkmate::assert_character(ds$instance , any.missing=F , pattern="^.{1,255}$" ) -checkmate::assert_character(ds$redcap_uri , any.missing=F , pattern="^.{1,255}$" ) +# OuhscMunge::verify_value_headstart(ds) +checkmate::assert_integer( ds$id , any.missing=F, lower=1, upper=.Machine$integer.max, unique=T) +checkmate::assert_character(ds$username , any.missing=F, pattern="^.{1,255}$" ) +checkmate::assert_integer( ds$project_id , any.missing=F, lower=1, upper=.Machine$integer.max ) +checkmate::assert_character(ds$token , any.missing=F, pattern="^[A-Z0-9]{32}$" , unique=T) +checkmate::assert_character(ds$instance , any.missing=F, pattern="^.{1,255}$" ) +checkmate::assert_character(ds$redcap_uri , any.missing=F, pattern="^.{1,255}$" ) testit::assert( "The `username` x `project_id` x `instance` must be unique.", sum(duplicated(paste0(ds$username, "-", ds$project_id, "-", ds$instance))) == 0L ) -testit::assert("There should be at least 10 tokens written." , 10L <= nrow(ds)) +testit::assert("At least 10 tokens should be ready to write." , 10L <= nrow(ds)) # ---- specify-columns-to-upload ----------------------------------------------- # Dictate the exact columns and order that will be uploaded. columns_to_write <- c("id", "username", "project_id", "instance", "token", "redcap_uri") -ds_slim <- ds[, columns_to_write] - +ds_slim <- ds[, columns_to_write] rm(columns_to_write) -# ---- upload-to-db-credential ------------------------------------------------------------ - -if( !require(OuhscMunge) ) - stop('The `OuhscMunge` package needs to be installed with `devtools::install_github("OuhscBbmc/OuhscMunge")`.') +# ---- upload-to-db ------------------------------------------------------------------ OuhscMunge::upload_sqls_odbc( d = ds_slim, schema_name = "redcap_private", table_name = "tbl_credential", - dsn_name = "auxiliary_security", + dsn_name = dsn_source, create_table = FALSE, clear_table = TRUE, transaction = TRUE, verbose = TRUE ) - -(elapsed_duration <- Sys.time() - start_time) #0.6026149 secs 2016-08-29. -rm(db_table, column_info, var_types, start_time, elapsed_duration) +# Uploading 252 tokens takes 0.004 minutes. ``` Document Info ======================================== -This document is primarily based on REDCap version 6.11.5, and was last updated 2016-08-30. A development version of the document is available on GitHub: https://ouhscbbmc.github.io/REDCapR/articles/SecurityDatabase.html +This document is primarily based on REDCap version 8.4.0, and was last updated 2018-08-10. A development version of the document is available on GitHub: https://ouhscbbmc.github.io/REDCapR/articles/SecurityDatabase.html diff --git a/inst/doc/SecurityDatabase.html b/inst/doc/SecurityDatabase.html index 4eec4068..7dddb6d3 100644 --- a/inst/doc/SecurityDatabase.html +++ b/inst/doc/SecurityDatabase.html @@ -284,25 +284,26 @@

    2018-08-10

    Description

    -

    The SQL code below adds schemas, a table and two stored procedures to an existing Microsoft SQL Database. This second database is not essential to calling the REDCap API, but it helps manage tokens securely.

    +

    The SQL code below adds schemas, a table and two stored procedures to an existing Microsoft SQL Server database. This second database is not essential to calling the REDCap API, but it helps manage tokens securely.

    This database contains the tokens and other sensitive content (such as passwords, API tokens, and file paths) that should not be stored in a Git repository (even a private Git repository). These passwords can be retrieved by REDCapR::retrieve_credential_mssql().

    Create a DSN on each client

    -

    After executing the SQL code in an existing database, create an ODBC DSN on each client machine that calls the database. Download the most recent drivers (as of Aug 2016, the most recent version is 13.1 for Windows and Linux, then run the wizard. Many values in the wizard will remain at the default values. Here are the important ones to change.

    +

    After executing the SQL code in an existing database, create an ODBC DSN on each client machine that calls the database. Download the most recent drivers (as of Aug 2018, the most recent version is 17 for Windows and Linux), then run the wizard. Many values in the wizard will remain at the default values. Here are the important ones to change.

    1. Set the DSN’s name field to whatever is used in the repository’s R code.
    2. Set the authenticity method to Integrated Windows authentication.
    3. -
    4. Set the default database to the name of the database that containing the tokens i.e., corresponding to the SQL code below in the example).
    5. +
    6. Set the default database to the name of the database that containing the tokens
    +

    In our code below, both DSN and database are named auxiliary_security.

    Note

    -

    We use Microsoft SQL Server, because that fits our University’s infrastructure the easiest. But this approach theoretically can work with any LDAP-enabled database server. Please contact us if your institution is using something other than SQL Server (or a different configuration of these components), and would like help adapting this approach to your infrastructure.

    +

    We use Microsoft SQL Server, because that fits our university’s infrastructure most easily. But this approach theoretically can work with any LDAP-enabled database server. Please contact us if your institution is using something other than SQL Server (or a different configuration of these components), and would like help adapting this approach.

    Create Database

    -

    This SQL code is run once inside an existing database to establish the schemas, table, and stored procedure used by REDCapR::retrieve_credential_mssql().

    +

    This SQL code is run once inside an existing database to establish the schemas, table, and stored procedure used by REDCapR::retrieve_credential_mssql(). In this example, we’ve arbitrarily called the database auxiliary_security.

    ------- SQL code to create necessary components in a Microsoft SQL Sever database -------
     
     -----------------------------------------------------------------------
    @@ -318,12 +319,12 @@ 

    Create Database

    -- Create a table to contain the token -- CREATE TABLE [redcap_private].[tbl_credential]( - [id] [smallint] NOT NULL, - [username] [varchar](30) NOT NULL, - [project_id] [smallint] NOT NULL, - [instance] [varchar](30) NOT NULL, - [token] [char](32) NOT NULL, - [redcap_uri] [varchar](255) NOT NULL, + id smallint NOT NULL, + username varchar(30) NOT NULL, + project_id smallint NOT NULL, + instance varchar(30) NOT NULL, + token char(32) NOT NULL, + redcap_uri varchar(255) NOT NULL, CONSTRAINT [PK_credential] PRIMARY KEY CLUSTERED ( [id] ASC @@ -332,9 +333,9 @@

    Create Database

    CREATE UNIQUE NONCLUSTERED INDEX [IX_tbl_credential_unique] ON [redcap_private].[tbl_credential] ( - [instance] ASC, - [project_id] ASC, - [username] ASC + instance ASC, + project_id ASC, + username ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] GO @@ -343,32 +344,32 @@

    Create Database

    -- Notice it should a different (and more permissive) schema than the table. -- CREATE PROCEDURE [redcap].[prc_credential] - -- Add the parameters for the stored procedure here - - @project_id smallint, - @instance varchar(30) -AS -BEGIN - -- SET NOCOUNT ON added to prevent extra result sets from - -- interfering with SELECT statements. - SET NOCOUNT ON; - - SELECT username, project_id, token, redcap_uri FROM [redcap_private].[tbl_credential] - WHERE username=system_user AND project_id=@project_id AND instance=@instance -END
    + @project_id smallint, + @instance varchar(30) +AS +BEGIN + SET NOCOUNT ON; + + SELECT username, project_id, token, redcap_uri FROM [redcap_private].[tbl_credential] + WHERE + username = system_user -- The username from the server's OS. + AND + project_id = @project_id -- Restricts to the desired REDCap project. + AND + instance = @instance -- System accommodates multiple REDCap instances. +END

    Create user credentials to the auxiliary database

    -

    Add a user’s LDAP account to the SecurityAuxiliary database so that they can query the tables to retrieve their API.

    -

    Notice that this only gives the permissions to retrieve the token. You must still: 1. grant them API privileges to each appropriate REDCap project, and 2. copy the API from the REDCap database into the SecurityAuxiliary database.

    -

    In the future REDCapR may expose a function that allows the user to perform the second step (through a stored procedure).

    -

    Also, do not give typical users authorization for the ‘redcap_private’ schema. The current system allows the to view only their own tokens.

    +

    Add a user’s LDAP account to the auxiliary_security database so that they can query the tables to retrieve their API.

    +

    Notice that this only gives the permissions to retrieve the token. You still must grant them API privileges to each appropriate REDCap project. The automation in the R file below will copy the API token from the MySQL database into the auxiliary_security database (see the ‘Transfer Credentials’ section).

    +

    Only database admins should have authorization for the ‘redcap_private’ schema. Typical users should not be authorized for this schema. The current system allows typical users to view only their own tokens.

    -----------------------------------------------------------------------
    --- Add a OUHSC's user account to the auxiliary_security database so that they can query the tables to retrieve their API.
    +-- Add a user account to the auxiliary_security database so that they can query the tables to retrieve their API.
     -- Notice that this only gives the permissions to retrieve the token.  You must still:
     --   1) grant them API privileges to each appropriate REDCap project, and
     --   2) copy the API from the REDCap database into the  auxiliary_security database.
    --- Also, do not give typical users authorization for the 'redcap_private' schema.  The current system allows the to view only their own tokens.
    +-- Also, do not give typical users authorization for the 'redcap_private' schema.  The current system allows them to view only their own tokens.
     -----------------------------------------------------------------------
     
     -- STEP #1: Declare the user name.  If everything runs correctly, this should be the only piece of code that you need to modify.
    @@ -394,7 +395,7 @@ 

    Create user credentials to the auxiliary database

    print 'Logins matching desired name should equal 1. It equals: ' + CONVERT(varchar, @login_count); print '' ----------------------------------------------------------------------- --- STEP #3: Create a user account for the *data base*, after switching the database under focus to auxiliary_security. +-- STEP #3: Create a user account for the *database*, after switching the database under focus to auxiliary_security. print 'Step #3 executing....' USE [auxiliary_security] DECLARE @sql_create_user nvarchar(max) @@ -440,141 +441,170 @@

    Create user credentials to the auxiliary database

    Transfer Credentials

    -

    Manually transfer tokens to the auxiliary server becomes unmanageable as your institution’s collection of API users grows. This script demonstrates how to progamatically transfer all tokens from multiple REDCap instances on your network. The basic steps are:

    +

    Manually transferring tokens to the auxiliary server becomes unmanageable as your institution’s collection of API users grows. This script demonstrates how to progamatically transfer all tokens from multiple REDCap instances. The basic steps are:

      -
    1. Read from the MySQL database underneath each REDCap instance.
    2. +
    3. Read from the MySQL database(s) underneath each REDCap instance on your campus.
    4. Combine & groom the credentials.
    5. -
    6. Upload to SQL Server.
    7. +
    8. Upload to SQL Server (called auxiliary_security here).
    rm(list=ls(all=TRUE)) #Clear the memory for any variables set from any previous runs.
     
     # ---- load-sources ------------------------------------------------------------
     
     # ---- load-packages -----------------------------------------------------------
    -library(magrittr)
    -requireNamespace("odbc")
    -requireNamespace("dplyr")
    -requireNamespace("readr")
    -requireNamespace("tibble")
    -requireNamespace("checkmate")
    -
    -# ---- declare-globals ---------------------------------------------------------
    -
    -# The Activity Directory name that should precede each username.
    -#   This should correspond with the result of `SYSTEM_USER`
    -#   (https://msdn.microsoft.com/en-us/library/ms179930.aspx)
    -ldap_prefix <- "OUHSC\\"
    -
    -#Create a SQL statement for each REDCap instance.  Only the `instance` value should change.
    -sql <- "
    -  SELECT username, project_id, api_token
    -  FROM redcap_user_rights
    -  WHERE api_token IS NOT NULL"
    -
    -#Update this ad-hoc CSV.  Each row should represent one REDCap instance.
    -#   Choose any casual name for the first variable, consistent with the `tweak-data` chunk below.
    -#   Enter the exact URL for the second variable.
    -ds_url <- readr::read_csv(paste(
    -  "instance,redcap_uri",
    -  "production,https://redcap-production.ouhsc.edu/redcap/api/",
    -  "dev,https://redcap-dev.ouhsc.edu/redcap/api/",
    -sep="\n"))
    +if( !require(OuhscMunge) )
    +  stop('The `OuhscMunge` package needs to be installed with `devtools::install_github("OuhscBbmc/OuhscMunge")`.')
    +
    +testit::assert(
    +  "The `OuhscMunge` package should meet a minimal version.",
    +  compareVersion( as.character(packageVersion("OuhscMunge")), "0.1.9.9009") >= 0L
    +)
    +
    +library(magrittr)
    +requireNamespace("DBI")
    +requireNamespace("odbc")
    +requireNamespace("dplyr")
    +requireNamespace("readr")
    +requireNamespace("tibble")
    +requireNamespace("testit")
    +requireNamespace("checkmate")
    +requireNamespace("OuhscMunge")  # devtools::install_github("OuhscBbmc/OuhscMunge")
    +
    +
    +# ---- declare-globals ---------------------------------------------------------
    +# This file assume your campus has two REDCap instances.
    +# Modify each (a) database name, (b) REDCap URL, and (c) DSN name.
    +
    +name_production <- "production"
    +name_dev        <- "dev"
    +
    +uri_production  <- "https://redcap-production.ouhsc.edu/redcap/api/",
    +uri_dev         <- "https://redcap-dev.ouhsc.edu/redcap/api/"
     
    -
    -# ---- load-data ---------------------------------------------------------------
    -
    -# Load the credentials from the first instance.
    -channel <- DBI::dbConnect(odbc::odbc(), dsn="redcap-production") 
    -ds_prod <- DBI::dbGetQuery(channel, sql)
    -DBI::dbDisconnect(channel); rm(channel)
    -
    -# Load the credentials from the second instance.
    -#   Duplicate or remove this block, dependending on the number of instances.
    -channel <- DBI::dbConnect(odbc::odbc(), dsn="redcap-dev")
    -ds_dev  <- DBI::dbGetQuery(channel, sql)
    -DBI::dbDisconnect(channel); rm(channel)
    +dsn_production  <- "redcap-production"
    +dsn_dev         <- "redcap-dev"
    +dsn_source      <- "auxiliary_security" # The DSN of the token server.
    +
    +# The Activity Directory name that should precede each username.
    +#   This should correspond with the result of SQL Server's `SYSTEM_USER` function
    +#   (https://msdn.microsoft.com/en-us/library/ms179930.aspx)
    +ldap_prefix <- "OUHSC\\"
    +
    +####
    +# Nothing below this line should need to change, assuming:
    +# 1. the vignette was followed exactly (https://ouhscbbmc.github.io/REDCapR/articles/SecurityDatabase.html),
    +# 2. your campus has exactly two REDCap instances.
     
    -# Assert these variables contain valid datasets (instead of a character error message), and
    -#   that at least some rows were returned.
    -#   Adjust this to smaller values if necessary.  It's really just to catch blatant retrieval problems.
    -checkmate::assert_data_frame(ds_bbmc, min.rows=5)
    -checkmate::assert_data_frame(ds_dev , min.rows=5)
    -
    +# SQL sent to the MySQL database underneath each REDCap instance.
    +sql <- "
    +  SELECT username, project_id, api_token
    +  FROM redcap_user_rights
    +  WHERE api_token IS NOT NULL
    +"
     
    -# ---- tweak-data --------------------------------------------------------------
    -
    -#Label each instance, so they're distinguishable later.  Add/remove lines, depending on the number of campus instances
    -ds_prod$instance <- "production"
    -ds_dev$instance  <- "dev"
    -
    -#Combine the token collection from each instance.  Then prefix the username and include the URL of each instance.
    -ds <- ds_prod %>%
    -  dplyr::union(ds_dev) %>%                                     # Add/remove unions, based on the number of REDCap instances.
    -  dplyr::select_(
    -    "username"             = "`username`"
    -    , "project_id"         = "`project_id`"
    -    , "instance"           = "`instance`"
    -    , "token"              = "`api_token`"
    -  ) %>%
    -  dplyr::arrange(instance, project_id, username) %>%
    -  dplyr::mutate(
    -    username               = paste0(ldap_prefix, username),    # Qualify for the Active Directory.
    -    id                     = seq_len(n())                      # For the sake of a clustered primary key.
    -  ) %>%
    -  dplyr::left_join( ds_url, by="instance")                     # Include the instance URL.
    -
    -rm(ds_prod, ds_dev, ds_url)
    -
    +# Update this ad-hoc CSV.  Each row should represent one REDCap instance.
    +ds_url <- tibble::tribble(
    +  ~instance         , ~redcap_uri,
    +  name_production   , uri_production,
    +  name_dev          , uri_dev
    +)
    +
    +# Remove variables that aren't used below.
    +rm(uri_production, uri_dev)
    +
    +
    +# ---- load-data ---------------------------------------------------------------
    +
    +# Load the credentials from the first/production REDCap instance.
    +cnn_production  <- DBI::dbConnect(odbc::odbc(), dsn=dsn_production)
    +ds_production   <- DBI::dbGetQuery(cnn_production, sql)
    +DBI::dbDisconnect(cnn_production); rm(cnn_production, dsn_production)
    +
    +# Load the credentials from the second/dev REDCap instance.
    +cnn_dev         <- DBI::dbConnect(odbc::odbc(), dsn=dsn_dev)
    +ds_dev          <- DBI::dbGetQuery(cnn_dev, sql)
    +DBI::dbDisconnect(cnn_dev); rm(cnn_dev, dsn_dev)
    +
    +rm(sql)
     
    -# ---- verify-values -----------------------------------------------------------
    -# devtools::install_github("OuhscBbmc/OuhscMunge"); OuhscMunge::verify_value_headstart(ds)
    -# Assert that the dataset is well-behaved.
    -checkmate::assert_integer(  ds$id         , any.missing=F , lower=1, upper=2^31-1 , unique=T)
    -checkmate::assert_character(ds$username   , any.missing=F , pattern="^.{1,255}$"            )
    -checkmate::assert_integer(  ds$project_id , any.missing=F , lower=1, upper=2^31-1           )
    -checkmate::assert_character(ds$token      , any.missing=F , pattern="^.{32}$"     , unique=T)
    -checkmate::assert_character(ds$instance   , any.missing=F , pattern="^.{1,255}$"            )
    -checkmate::assert_character(ds$redcap_uri , any.missing=F , pattern="^.{1,255}$"            )
    -
    -testit::assert(
    -  "The `username` x `project_id` x `instance` must be unique.",
    -  sum(duplicated(paste0(ds$username, "-", ds$project_id, "-", ds$instance))) == 0L
    -)
    -
    -testit::assert("There should be at least 10 tokens written." , 10L <= nrow(ds))
    -
    -
    -# ---- specify-columns-to-upload -----------------------------------------------
    -
    -# Dictate the exact columns and order that will be uploaded.
    -columns_to_write <- c("id", "username", "project_id", "instance", "token", "redcap_uri")
    -ds_slim <- ds[, columns_to_write]
    -
    -rm(columns_to_write)
    -
    -
    -# ---- upload-to-db-credential ------------------------------------------------------------
    +# Assert these are valid datasets and contain at least 5 rows.
    +#   Adjust '5' to smaller value if necessary.  It's just to catch blatant retrieval problems.
    +checkmate::assert_data_frame(ds_production, min.rows=5)
    +checkmate::assert_data_frame(ds_dev       , min.rows=5)
    +
    +
    +# ---- tweak-data --------------------------------------------------------------
    +
    +# Label each instance, so they're distinguishable later.
    +ds_production$instance <- name_production
    +ds_dev$instance        <- name_dev
    +
    +# Stack the token collection from each instance.  Then prefix the username and include the URL of each instance.
    +ds <- ds_production %>%
    +  dplyr::union(ds_dev) %>%                                # Remove union if the dev instance isn't included.
    +  tibble::as_tibble() %>%
    +  dplyr::select(
    +    username             = username,
    +    project_id           = project_id,
    +    instance             = instance,
    +    token                = api_token
    +  ) %>%
    +  dplyr::mutate(
    +    username             = paste0(ldap_prefix, username), # Qualify for the Active Directory.
    +  ) %>%
    +  dplyr::left_join( ds_url, by="instance") %>%            # Include the instance URL.
    +  dplyr::arrange(instance, project_id, username) %>%
    +  tibble::rowid_to_column("id")                           # For the sake of a clustered primary key.
     
    -if( !require(OuhscMunge) ) 
    -  stop('The `OuhscMunge` package needs to be installed with `devtools::install_github("OuhscBbmc/OuhscMunge")`.')
    -
    -OuhscMunge::upload_sqls_odbc(
    -  d               = ds_slim,
    -  schema_name     = "redcap_private",
    -  table_name      = "tbl_credential",
    -  dsn_name        = "auxiliary_security",
    -  create_table    = FALSE,
    -  clear_table     = TRUE,
    -  transaction     = TRUE,
    -  verbose         = TRUE
    -)
    -
    -(elapsed_duration <-  Sys.time() - start_time) #0.6026149 secs 2016-08-29.
    -rm(db_table, column_info, var_types, start_time, elapsed_duration)
    +rm(ds_production, ds_dev, ds_url) +rm(name_production, name_dev) +rm(ldap_prefix) + + +# ---- verify-values ----------------------------------------------------------- + +# Assert that the dataset is well-behaved. +# OuhscMunge::verify_value_headstart(ds) +checkmate::assert_integer( ds$id , any.missing=F, lower=1, upper=.Machine$integer.max, unique=T) +checkmate::assert_character(ds$username , any.missing=F, pattern="^.{1,255}$" ) +checkmate::assert_integer( ds$project_id , any.missing=F, lower=1, upper=.Machine$integer.max ) +checkmate::assert_character(ds$token , any.missing=F, pattern="^[A-Z0-9]{32}$" , unique=T) +checkmate::assert_character(ds$instance , any.missing=F, pattern="^.{1,255}$" ) +checkmate::assert_character(ds$redcap_uri , any.missing=F, pattern="^.{1,255}$" ) + +testit::assert( + "The `username` x `project_id` x `instance` must be unique.", + sum(duplicated(paste0(ds$username, "-", ds$project_id, "-", ds$instance))) == 0L +) + +testit::assert("At least 10 tokens should be ready to write." , 10L <= nrow(ds)) + + +# ---- specify-columns-to-upload ----------------------------------------------- + +# Dictate the exact columns and order that will be uploaded. +columns_to_write <- c("id", "username", "project_id", "instance", "token", "redcap_uri") +ds_slim <- ds[, columns_to_write] +rm(columns_to_write) + + +# ---- upload-to-db ------------------------------------------------------------------ + +OuhscMunge::upload_sqls_odbc( + d = ds_slim, + schema_name = "redcap_private", + table_name = "tbl_credential", + dsn_name = dsn_source, + create_table = FALSE, + clear_table = TRUE, + transaction = TRUE, + verbose = TRUE +) +# Uploading 252 tokens takes 0.004 minutes.

    Document Info

    -

    This document is primarily based on REDCap version 6.11.5, and was last updated 2016-08-30. A development version of the document is available on GitHub: https://ouhscbbmc.github.io/REDCapR/articles/SecurityDatabase.html

    +

    This document is primarily based on REDCap version 8.4.0, and was last updated 2018-08-10. A development version of the document is available on GitHub: https://ouhscbbmc.github.io/REDCapR/articles/SecurityDatabase.html

    diff --git a/inst/doc/advanced-redcapr-operations.html b/inst/doc/advanced-redcapr-operations.html index 5f6cbfdf..688f2f2f 100644 --- a/inst/doc/advanced-redcapr-operations.html +++ b/inst/doc/advanced-redcapr-operations.html @@ -304,7 +304,7 @@

    Converting from tall/long to wide

    events_to_retain <- c("dose_1_arm_1", "visit_1_arm_1", "dose_2_arm_1", "visit_2_arm_1") ds_long <- REDCapR::redcap_read_oneshot(redcap_uri=uri, token=token_longitudinal)$data -
    #> 18 records and 125 columns were read from REDCap in 0.4 seconds.  The http status code was 200.
    +
    #> 18 records and 125 columns were read from REDCap in 0.5 seconds.  The http status code was 200.
    #>    study_id        redcap_event_name pmq1 pmq2 pmq3 pmq4
    @@ -403,7 +403,7 @@ 

    SSL Options

    config_options = config_options )$data }
    -
    #> 5 records and 24 columns were read from REDCap in 0.3 seconds.  The http status code was 200.
    +
    #> 5 records and 24 columns were read from REDCap in 0.2 seconds.  The http status code was 200.

    Force the connection to use SSL=3 (which is not preferred, and possibly insecure).

    config_options <- list(sslversion=3)
     ds_ssl_3 <- redcap_read_oneshot(
    @@ -454,7 +454,6 @@ 

    Session Information

    #> digest 0.6.15 2018-01-28 CRAN (R 3.5.0) #> dplyr 0.7.6 2018-06-29 CRAN (R 3.5.1) #> evaluate 0.11 2018-07-17 CRAN (R 3.5.1) -#> fs 1.2.5 2018-07-30 CRAN (R 3.5.1) #> git2r 0.23.0 2018-07-17 CRAN (R 3.5.1) #> glue 1.3.0 2018-07-17 CRAN (R 3.5.1) #> hms 0.4.2.9001 2018-08-09 Github (tidyverse/hms@979286f) @@ -463,13 +462,11 @@

    Session Information

    #> kableExtra 0.9.0 2018-05-21 CRAN (R 3.5.0) #> knitr * 1.20 2018-02-20 CRAN (R 3.5.0) #> magrittr * 1.5 2014-11-22 CRAN (R 3.5.0) -#> MASS 7.3-50 2018-04-30 CRAN (R 3.5.1) #> memoise 1.1.0 2017-04-21 CRAN (R 3.5.0) #> munsell 0.5.0 2018-06-12 CRAN (R 3.5.0) #> packrat 0.4.9-3 2018-06-01 CRAN (R 3.5.0) #> pillar 1.3.0 2018-07-14 CRAN (R 3.5.1) #> pkgconfig 2.0.1 2017-03-21 CRAN (R 3.5.0) -#> pkgdown 1.1.0 2018-06-02 CRAN (R 3.5.1) #> purrr 0.2.5 2018-05-29 CRAN (R 3.5.0) #> R6 2.2.2 2017-06-17 CRAN (R 3.5.0) #> Rcpp 0.12.18 2018-07-23 CRAN (R 3.5.1) @@ -494,7 +491,7 @@

    Session Information

    #> xml2 1.2.0 2018-01-24 CRAN (R 3.5.0) #> yaml 2.2.0 2018-07-25 CRAN (R 3.5.1)
    -

    Report rendered by Will at 2018-08-10, 17:57 -0500 in 2 seconds.

    +

    Report rendered by Will at 2018-08-10, 20:36 -0500 in 2 seconds.

    diff --git a/vignettes/SecurityDatabase.Rmd b/vignettes/SecurityDatabase.Rmd index 15cf284b..fef69fc6 100644 --- a/vignettes/SecurityDatabase.Rmd +++ b/vignettes/SecurityDatabase.Rmd @@ -11,7 +11,7 @@ vignette: > Description ======================================== -The SQL code below adds schemas, a table and two stored procedures to an existing Microsoft SQL Database. This second database is not essential to calling the REDCap API, but it helps manage tokens securely. +The SQL code below adds schemas, a table and two stored procedures to an existing Microsoft SQL Server database. This second database is not essential to calling the REDCap API, but it helps manage tokens securely. This database contains the tokens and other sensitive content (such as passwords, API tokens, and file paths) that should not be stored in a Git repository (even a private Git repository). These passwords can be retrieved by `REDCapR::retrieve_credential_mssql()`. @@ -19,17 +19,19 @@ This database contains the tokens and other sensitive content (such as passwords Create a DSN on each client ======================================== -After executing the SQL code in an existing database, create an ODBC [DSN](http://en.wikipedia.org/wiki/Data_source_name) on *each* client machine that calls the database. Download the most recent drivers (as of Aug 2018, the [most recent version is 17](https://docs.microsoft.com/en-us/sql/connect/odbc/download-odbc-driver-for-sql-server) for Windows and Linux, then run the wizard. Many values in the wizard will remain at the default values. Here are the important ones to change. +After executing the SQL code in an existing database, create an ODBC [DSN](http://en.wikipedia.org/wiki/Data_source_name) on *each* client machine that calls the database. Download the most recent drivers (as of Aug 2018, the [most recent version is 17](https://docs.microsoft.com/en-us/sql/connect/odbc/download-odbc-driver-for-sql-server) for Windows and Linux), then run the wizard. Many values in the wizard will remain at the default values. Here are the important ones to change. 1. Set the DSN's `name` field to whatever is used in the repository's R code. 2. Set the authenticity method to `Integrated Windows authentication`. -3. Set the `default database` to the name of the database that containing the tokens (*e.g.*, corresponding to `auxiliary_security` in the SQL code below in the example). +3. Set the `default database` to the name of the database that containing the tokens + +In our code below, both DSN and database are named `auxiliary_security`. Note ======================================== -We use Microsoft SQL Server, because that fits our University's infrastructure the easiest. But this approach theoretically can work with any LDAP-enabled database server. Please contact us if your institution is using something other than SQL Server (or a different configuration of these components), and would like help adapting this approach to your infrastructure. +We use Microsoft SQL Server, because that fits our university's infrastructure most easily. But this approach theoretically can work with any LDAP-enabled database server. Please contact us if your institution is using something other than SQL Server (or a different configuration of these components), and would like help adapting this approach. Create Database @@ -53,12 +55,12 @@ GO -- Create a table to contain the token -- CREATE TABLE [redcap_private].[tbl_credential]( - [id] [smallint] NOT NULL, - [username] [varchar](30) NOT NULL, - [project_id] [smallint] NOT NULL, - [instance] [varchar](30) NOT NULL, - [token] [char](32) NOT NULL, - [redcap_uri] [varchar](255) NOT NULL, + id smallint NOT NULL, + username varchar(30) NOT NULL, + project_id smallint NOT NULL, + instance varchar(30) NOT NULL, + token char(32) NOT NULL, + redcap_uri varchar(255) NOT NULL, CONSTRAINT [PK_credential] PRIMARY KEY CLUSTERED ( [id] ASC @@ -67,9 +69,9 @@ CREATE TABLE [redcap_private].[tbl_credential]( CREATE UNIQUE NONCLUSTERED INDEX [IX_tbl_credential_unique] ON [redcap_private].[tbl_credential] ( - [instance] ASC, - [project_id] ASC, - [username] ASC + instance ASC, + project_id ASC, + username ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] GO @@ -100,11 +102,7 @@ Create user credentials to the auxiliary database Add a user's LDAP account to the `auxiliary_security` database so that they can query the tables to retrieve their API. -Notice that this only gives the permissions to retrieve the token. You must still: -1. grant them API privileges to each appropriate REDCap project, and -2. copy the API from the REDCap database into the `auxiliary_security` database. - -In the future ,`REDCapR` may expose a function that allows the user to perform the second step (through a stored procedure). +Notice that this only gives the permissions to retrieve the token. You still must grant them API privileges to each appropriate REDCap project. The automation in the R file below will copy the API token from the MySQL database into the `auxiliary_security` database (see the 'Transfer Credentials' section). Only database admins should have authorization for the 'redcap_private' schema. Typical users should not be authorized for this schema. The current system allows typical users to view only their own tokens. @@ -187,7 +185,7 @@ print 'Step #5 executed'; print '' Transfer Credentials ======================================== -Manually transferring tokens to the auxiliary server becomes unmanageable as your institution's collection of API users grows. This script demonstrates how to progamatically transfer all tokens from multiple REDCap instances on your network. The basic steps are: +Manually transferring tokens to the auxiliary server becomes unmanageable as your institution's collection of API users grows. This script demonstrates how to progamatically transfer all tokens from multiple REDCap instances. The basic steps are: 1. Read from the MySQL database(s) underneath each REDCap instance on your campus. 1. Combine & groom the credentials. @@ -199,126 +197,155 @@ rm(list=ls(all=TRUE)) #Clear the memory for any variables set from any previous # ---- load-sources ------------------------------------------------------------ # ---- load-packages ----------------------------------------------------------- +if( !require(OuhscMunge) ) + stop('The `OuhscMunge` package needs to be installed with `devtools::install_github("OuhscBbmc/OuhscMunge")`.') + +testit::assert( + "The `OuhscMunge` package should meet a minimal version.", + compareVersion( as.character(packageVersion("OuhscMunge")), "0.1.9.9009") >= 0L +) + library(magrittr) +requireNamespace("DBI") requireNamespace("odbc") requireNamespace("dplyr") requireNamespace("readr") requireNamespace("tibble") +requireNamespace("testit") requireNamespace("checkmate") +requireNamespace("OuhscMunge") # devtools::install_github("OuhscBbmc/OuhscMunge") + # ---- declare-globals --------------------------------------------------------- +# This file assume your campus has two REDCap instances. +# Modify each (a) database name, (b) REDCap URL, and (c) DSN name. + +name_production <- "production" +name_dev <- "dev" + +uri_production <- "https://redcap-production.ouhsc.edu/redcap/api/", +uri_dev <- "https://redcap-dev.ouhsc.edu/redcap/api/" + +dsn_production <- "redcap-production" +dsn_dev <- "redcap-dev" +dsn_source <- "auxiliary_security" # The DSN of the token server. # The Activity Directory name that should precede each username. -# This should correspond with the result of `SYSTEM_USER` +# This should correspond with the result of SQL Server's `SYSTEM_USER` function # (https://msdn.microsoft.com/en-us/library/ms179930.aspx) ldap_prefix <- "OUHSC\\" -# Create a SQL statement for each REDCap instance. Only the `instance` value should change. +#### +# Nothing below this line should need to change, assuming: +# 1. the vignette was followed exactly (https://ouhscbbmc.github.io/REDCapR/articles/SecurityDatabase.html), +# 2. your campus has exactly two REDCap instances. + +# SQL sent to the MySQL database underneath each REDCap instance. sql <- " SELECT username, project_id, api_token FROM redcap_user_rights - WHERE api_token IS NOT NULL" + WHERE api_token IS NOT NULL +" # Update this ad-hoc CSV. Each row should represent one REDCap instance. -# Choose any casual name for the first variable, consistent with the `tweak-data` chunk below. -# Enter the exact URL for the second variable. -ds_url <- readr::read_csv(paste( - "instance,redcap_uri", - "production,https://redcap-production.ouhsc.edu/redcap/api/", - "dev,https://redcap-dev.ouhsc.edu/redcap/api/", -sep="\n")) +ds_url <- tibble::tribble( + ~instance , ~redcap_uri, + name_production , uri_production, + name_dev , uri_dev +) + +# Remove variables that aren't used below. +rm(uri_production, uri_dev) # ---- load-data --------------------------------------------------------------- -# Load the credentials from the first instance. -channel <- DBI::dbConnect(odbc::odbc(), dsn="redcap-production") -ds_prod <- DBI::dbGetQuery(channel, sql) -DBI::dbDisconnect(channel); rm(channel) +# Load the credentials from the first/production REDCap instance. +cnn_production <- DBI::dbConnect(odbc::odbc(), dsn=dsn_production) +ds_production <- DBI::dbGetQuery(cnn_production, sql) +DBI::dbDisconnect(cnn_production); rm(cnn_production, dsn_production) -# Load the credentials from the second instance. -# Duplicate or remove this block, dependending on the number of instances. -channel <- DBI::dbConnect(odbc::odbc(), dsn="redcap-dev") -ds_dev <- DBI::dbGetQuery(channel, sql) -DBI::dbDisconnect(channel); rm(channel) +# Load the credentials from the second/dev REDCap instance. +cnn_dev <- DBI::dbConnect(odbc::odbc(), dsn=dsn_dev) +ds_dev <- DBI::dbGetQuery(cnn_dev, sql) +DBI::dbDisconnect(cnn_dev); rm(cnn_dev, dsn_dev) + +rm(sql) + +# Assert these are valid datasets and contain at least 5 rows. +# Adjust '5' to smaller value if necessary. It's just to catch blatant retrieval problems. +checkmate::assert_data_frame(ds_production, min.rows=5) +checkmate::assert_data_frame(ds_dev , min.rows=5) -# Assert these variables contain valid datasets (instead of a character error message), and -# that at least some rows were returned. -# Adjust this to smaller values if necessary. It's really just to catch blatant retrieval problems. -checkmate::assert_data_frame(ds_bbmc, min.rows=5) -checkmate::assert_data_frame(ds_dev , min.rows=5) # ---- tweak-data -------------------------------------------------------------- -# Label each instance, so they're distinguishable later. Add/remove lines, depending on the number of campus instances -ds_prod$instance <- "production" -ds_dev$instance <- "dev" - -# Combine the token collection from each instance. Then prefix the username and include the URL of each instance. -ds <- ds_prod %>% - dplyr::union(ds_dev) %>% # Add/remove unions, based on the number of REDCap instances. - dplyr::select_( - "username" = "`username`" - , "project_id" = "`project_id`" - , "instance" = "`instance`" - , "token" = "`api_token`" +# Label each instance, so they're distinguishable later. +ds_production$instance <- name_production +ds_dev$instance <- name_dev + +# Stack the token collection from each instance. Then prefix the username and include the URL of each instance. +ds <- ds_production %>% + dplyr::union(ds_dev) %>% # Remove union if the dev instance isn't included. + tibble::as_tibble() %>% + dplyr::select( + username = username, + project_id = project_id, + instance = instance, + token = api_token ) %>% - dplyr::arrange(instance, project_id, username) %>% dplyr::mutate( - username = paste0(ldap_prefix, username), # Qualify for the Active Directory. - id = seq_len(n()) # For the sake of a clustered primary key. + username = paste0(ldap_prefix, username), # Qualify for the Active Directory. ) %>% - dplyr::left_join( ds_url, by="instance") # Include the instance URL. + dplyr::left_join( ds_url, by="instance") %>% # Include the instance URL. + dplyr::arrange(instance, project_id, username) %>% + tibble::rowid_to_column("id") # For the sake of a clustered primary key. -rm(ds_prod, ds_dev, ds_url) +rm(ds_production, ds_dev, ds_url) +rm(name_production, name_dev) +rm(ldap_prefix) # ---- verify-values ----------------------------------------------------------- -# devtools::install_github("OuhscBbmc/OuhscMunge"); OuhscMunge::verify_value_headstart(ds) # Assert that the dataset is well-behaved. -checkmate::assert_integer( ds$id , any.missing=F , lower=1, upper=2^31-1 , unique=T) -checkmate::assert_character(ds$username , any.missing=F , pattern="^.{1,255}$" ) -checkmate::assert_integer( ds$project_id , any.missing=F , lower=1, upper=2^31-1 ) -checkmate::assert_character(ds$token , any.missing=F , pattern="^.{32}$" , unique=T) -checkmate::assert_character(ds$instance , any.missing=F , pattern="^.{1,255}$" ) -checkmate::assert_character(ds$redcap_uri , any.missing=F , pattern="^.{1,255}$" ) +# OuhscMunge::verify_value_headstart(ds) +checkmate::assert_integer( ds$id , any.missing=F, lower=1, upper=.Machine$integer.max, unique=T) +checkmate::assert_character(ds$username , any.missing=F, pattern="^.{1,255}$" ) +checkmate::assert_integer( ds$project_id , any.missing=F, lower=1, upper=.Machine$integer.max ) +checkmate::assert_character(ds$token , any.missing=F, pattern="^[A-Z0-9]{32}$" , unique=T) +checkmate::assert_character(ds$instance , any.missing=F, pattern="^.{1,255}$" ) +checkmate::assert_character(ds$redcap_uri , any.missing=F, pattern="^.{1,255}$" ) testit::assert( "The `username` x `project_id` x `instance` must be unique.", sum(duplicated(paste0(ds$username, "-", ds$project_id, "-", ds$instance))) == 0L ) -testit::assert("There should be at least 10 tokens written." , 10L <= nrow(ds)) +testit::assert("At least 10 tokens should be ready to write." , 10L <= nrow(ds)) # ---- specify-columns-to-upload ----------------------------------------------- # Dictate the exact columns and order that will be uploaded. columns_to_write <- c("id", "username", "project_id", "instance", "token", "redcap_uri") -ds_slim <- ds[, columns_to_write] - +ds_slim <- ds[, columns_to_write] rm(columns_to_write) -# ---- upload-to-db-credential ------------------------------------------------------------ - -if( !require(OuhscMunge) ) - stop('The `OuhscMunge` package needs to be installed with `devtools::install_github("OuhscBbmc/OuhscMunge")`.') +# ---- upload-to-db ------------------------------------------------------------------ OuhscMunge::upload_sqls_odbc( d = ds_slim, schema_name = "redcap_private", table_name = "tbl_credential", - dsn_name = "auxiliary_security", + dsn_name = dsn_source, create_table = FALSE, clear_table = TRUE, transaction = TRUE, verbose = TRUE ) - -(elapsed_duration <- Sys.time() - start_time) #0.6026149 secs 2016-08-29. -rm(db_table, column_info, var_types, start_time, elapsed_duration) +# Uploading 252 tokens takes 0.004 minutes. ```