diff --git a/DESCRIPTION b/DESCRIPTION index 8920ef5..8e1ce2c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: redcapcustodian Type: Package Title: Data automation for R-centric workflows with a nod towards REDCap -Version: 1.12.0 +Version: 1.13.0 Authors@R: c( person("Philip", "Chase", email = "pbc@ufl.edu", @@ -59,6 +59,7 @@ Imports: Suggests: RSQLite, digest, + duckdb, fs, knitr (>= 1.18), rmarkdown (>= 2.0), diff --git a/Dockerfile b/Dockerfile index 63595a0..c76af9c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,7 @@ RUN R -e "install.packages(c( \ 'RCurl', \ 'REDCapR', \ 'RMariaDB', \ + 'argparse', \ 'checkmate', \ 'dbx', \ 'digest', \ diff --git a/NAMESPACE b/NAMESPACE index 54b7c7e..b0bb440 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -4,6 +4,7 @@ export(connect_to_db) export(connect_to_log_db) export(connect_to_redcap_db) export(convert_schema_to_sqlite) +export(copy_entire_table_to_db) export(create_allocation_rows) export(create_randomization_row) export(create_test_table) diff --git a/NEWS.md b/NEWS.md index 308d711..5d0e8d3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,10 @@ +# redcapcustodian 1.13.0 (released 2023-06-23) +- Add project_purpose_other_research_labels.rda (@pbchase, #123) +- Add project_status_labels and project_purpose_labels (@pbchase, #122) +- Add conn parameter to write_summary_metrics() (@pbchase, #122) +- Add copy_entire_table_to_db() (@pbchase, #122) +- Update render_report.R to support quarto files (@pbchase, #118) + # redcapcustodian 1.12.0 (released 2023-06-02) - Add unnest_job_summary_data_json_object() (@pbchase, #111) - Fix Version in DESCRIPTION (@pbchase) diff --git a/R/data.R b/R/data.R index 2bcb470..5d4eb68 100644 --- a/R/data.R +++ b/R/data.R @@ -94,6 +94,39 @@ #' @title project_life_cycle_descriptions #' @description A character vector of the descriptions used in the redcap_log_event table #' to describe the different stages in the life cycle of a REDCap Project -#' @format A charecter vector with 24 elements +#' @format A character vector with 24 elements #' @details DETAILS "project_life_cycle_descriptions" + +#' @title project_status_labels +#' @description A tibble of project status IDs and project statuses that reflect their +#' meaning as used in the `status` column of the `redcap_projects` table +#' @format A data frame with 4 rows and 2 variables: +#' \describe{ +#' \item{\code{id}}{double primary key} +#' \item{\code{project_status}}{character redcap project status} +#'} +#' @details DETAILS +"project_status_labels" + +#' @title project_purpose_labels +#' @description A tibble of project purpose IDs and project purposes that reflect their +#' meaning as used in the `purpose` column of the `redcap_projects` table +#' @format A data frame with 5 rows and 2 variables: +#' \describe{ +#' \item{\code{id}}{double primary key} +#' \item{\code{project_purpose}}{character redcap project purpose} +#'} +#' @details DETAILS +"project_purpose_labels" + +#' @title project_purpose_other_research_labels +#' @description A tibble project purpose other IDs and labels that reflect their +#' meaning as used in the `purpose_other` column of the `redcap_projects` table +#' @format A data frame with 5 rows and 2 variables: +#' \describe{ +#' \item{\code{id}}{double primary key} +#' \item{\code{project_purpose_other_research}}{character redcap purpose other research} +#'} +#' @details DETAILS +"project_purpose_other_research_labels" diff --git a/R/devtools.R b/R/devtools.R index 44f1ca5..d0b7564 100644 --- a/R/devtools.R +++ b/R/devtools.R @@ -146,3 +146,98 @@ mutate_columns_to_posixct <- function(data, column_names) { return(result) } + +#' @title copy_entire_table_to_db +#' @description Copy and entire DBI table from one DBI connection to another. +#' This is a developer tool designed as an aid to testing and development. +#' It designed to be called via \code{purrr::walk2()} to clone sets of tables in +#' a data-driven way to an ephemeral database created, generally with Duck +#' DB. +#' +#' \strong{Limitations} +#' +#' \itemize{ +#' \item The table referenced in \code{table_name} must not exist on \code{target_conn}. +#' \item This function is suitable for cloning small tables. +#' \item When called via \code{purrr::walk2()}, all tables in the vector of +#' table names will be copied to the single \code{target_conn} DBI object +#' even if the source table is on different \code{source_conn} DBI objects. +#' } +#' @param source_conn - the DBI connection object that holds the source table +#' @param table_name - the name of the table to be copied +#' @param target_conn - the DBI connection object to which the table will +#' be copied +#' +#' @return No result +#' @export +#' +#' @examples +#' # Build the objects need for testing +#' test_data <- dplyr::tribble( +#' ~a, ~b, ~c, ~d, +#' "asdf", 1, TRUE, lubridate::ymd_hms("2023-01-14 12:34:56"), +#' "qwer", 2, FALSE, lubridate::ymd_hms("2016-01-14 12:34:56") +#' ) +#' table_name <- "test_data" +#' source_conn <- DBI::dbConnect(duckdb::duckdb(), dbdir = ":memory:") +#' DBI::dbWriteTable(conn = source_conn, name = table_name, value = test_data) +#' +#' # copy the table +#' target_conn <- DBI::dbConnect(duckdb::duckdb(), dbdir = ":memory:") +#' copy_entire_table_to_db( +#' source_conn = source_conn, +#' table_name = table_name, +#' target_conn = target_conn +#' ) +#' +#' dplyr::collect(dplyr::tbl(target_conn, table_name)) +#' +#' \dontrun{ +#' library(tidyverse) +#' library(lubridate) +#' library(dotenv) +#' library(DBI) +#' library(RMariaDB) +#' library(redcapcustodian) +#' +#' init_etl("my_script_name") +#' +#' rc_conn <- connect_to_redcap_db() +#' log_conn <- get_package_scope_var("log_con") +#' +#' # describe the tables you want to clone +#' test_tables <- tribble( +#' ~conn, ~table, +#' rc_conn, "redcap_user_information", +#' rc_conn, "redcap_projects", +#' log_conn, "redcap_summary_metrics" +#' ) +#' +#' # make the target DB and clone the tables +#' target_conn <- DBI::dbConnect( +#' duckdb::duckdb(), +#' dbdir = ":memory:" +#' ) +#' purrr::walk2( +#' test_tables$conn, +#' test_tables$table, +#' copy_table_to_db, +#' target_conn +#' ) +#' +#' # Enumerate the tables you copied if you like +#' DBI::dbListTables(target_conn) +#' +#' # replace original connection objects +#' rc_conn <- target_conn +#' log_conn <- target_conn +#' +#' # At this point you can do destructive things on the original +#' # connection objects because they point at the ephemeral +#' # copies of the tables. +#' } +copy_entire_table_to_db <- function(source_conn, table_name, target_conn) { + dplyr::tbl(source_conn, table_name) %>% + dplyr::collect() %>% + DBI::dbWriteTable(conn = target_conn, name = table_name, value = .) +} diff --git a/R/summary_metrics.R b/R/summary_metrics.R index 786a6a6..36d71c3 100644 --- a/R/summary_metrics.R +++ b/R/summary_metrics.R @@ -4,6 +4,9 @@ #' @param reporting_period_end a datetime object, e.g. ymd_hms("2022-12-01 00:00:00") #' @param metric_type a character string representing the metric type, e.g. "flux", "state" #' @param metric_dataframe A wide data frame of key-value pairs with a single row of data +#' @param conn A DBI connection object to the database that holds the +#' `redcap_summary_metrics` table. Can be left as NULL if the connection is available +#' on the package scope var "log_con". #' #' @return nothing #' @@ -14,13 +17,19 @@ #' reporting_period_start = ymd_hms("2022-01-01 00:00:00", tz=Sys.getenv("TIME_ZONE")), #' reporting_period_end = ceiling_date(reporting_period_start, "month", change_on_boundary = T) #' metric_type = "state", -#' metric_dataframe = my_cool_df +#' metric_dataframe = my_cool_df, +#' conn = my_conn #' ) #' } write_summary_metrics <- function(reporting_period_start, reporting_period_end, metric_type, - metric_dataframe) { + metric_dataframe, + conn = NULL) { + + if (is.null(conn)) { + conn = get_package_scope_var("log_con") + } tall_df <- metric_dataframe %>% tidyr::pivot_longer( @@ -45,8 +54,6 @@ write_summary_metrics <- function(reporting_period_start, "script_run_time" ) - log_conn <- get_package_scope_var("log_con") - # log data in redcap_summary_metrics - DBI::dbAppendTable(log_conn, "redcap_summary_metrics", tall_df) + DBI::dbAppendTable(conn, "redcap_summary_metrics", tall_df) } diff --git a/VERSION b/VERSION index 0eed1a2..feaae22 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.12.0 +1.13.0 diff --git a/data-raw/redcap_lookup_tables.R b/data-raw/redcap_lookup_tables.R new file mode 100644 index 0000000..f35691d --- /dev/null +++ b/data-raw/redcap_lookup_tables.R @@ -0,0 +1,33 @@ +project_purpose_labels <- dplyr::tribble( + ~id, ~project_purpose, + 4, "Operational Support", + 2, "Research", + 3, "Quality Improvement", + 1, "Other", + 0, "Practice / Just for fun" +) +# write the data +usethis::use_data(project_purpose_labels, overwrite = T) + +project_purpose_other_research_labels <- dplyr::tribble( + ~id, ~project_purpose_other_research, + 0, "Basic or bench research", + 1, "Clinical research study or trial", + 2, "Translational research 1 (applying discoveries to the development of trials and studies in humans)", + 3, "Translational research 2 (enhancing adoption of research findings and best practices into the community)", + 4, "Behavioral or psychosocial research study", + 5, "Epidemiology", + 6, "Repository (developing a data or specimen repository for future use by investigators)", + 7, "Other" +) +usethis::use_data(project_purpose_other_research_labels, overwrite = T) + +project_status_labels <- dplyr::tribble( + ~id, ~project_status, + 0, "Development", + 1, "Production", + 2, "Inactive", + 3, "Archived" +) +# write the data +usethis::use_data(project_status_labels, overwrite = T) diff --git a/data/project_purpose_labels.rda b/data/project_purpose_labels.rda new file mode 100644 index 0000000..cafe3c4 Binary files /dev/null and b/data/project_purpose_labels.rda differ diff --git a/data/project_purpose_other_research_labels.rda b/data/project_purpose_other_research_labels.rda new file mode 100644 index 0000000..89fec29 Binary files /dev/null and b/data/project_purpose_other_research_labels.rda differ diff --git a/data/project_status_labels.rda b/data/project_status_labels.rda new file mode 100644 index 0000000..673e342 Binary files /dev/null and b/data/project_status_labels.rda differ diff --git a/man/copy_entire_table_to_db.Rd b/man/copy_entire_table_to_db.Rd new file mode 100644 index 0000000..5585ff3 --- /dev/null +++ b/man/copy_entire_table_to_db.Rd @@ -0,0 +1,102 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/devtools.R +\name{copy_entire_table_to_db} +\alias{copy_entire_table_to_db} +\title{copy_entire_table_to_db} +\usage{ +copy_entire_table_to_db(source_conn, table_name, target_conn) +} +\arguments{ +\item{source_conn}{- the DBI connection object that holds the source table} + +\item{table_name}{- the name of the table to be copied} + +\item{target_conn}{- the DBI connection object to which the table will +be copied} +} +\value{ +No result +} +\description{ +Copy and entire DBI table from one DBI connection to another. + This is a developer tool designed as an aid to testing and development. + It designed to be called via \code{purrr::walk2()} to clone sets of tables in + a data-driven way to an ephemeral database created, generally with Duck + DB. + + \strong{Limitations} + + \itemize{ + \item The table referenced in \code{table_name} must not exist on \code{target_conn}. + \item This function is suitable for cloning small tables. + \item When called via \code{purrr::walk2()}, all tables in the vector of + table names will be copied to the single \code{target_conn} DBI object + even if the source table is on different \code{source_conn} DBI objects. + } +} +\examples{ +# Build the objects need for testing +test_data <- dplyr::tribble( + ~a, ~b, ~c, ~d, + "asdf", 1, TRUE, lubridate::ymd_hms("2023-01-14 12:34:56"), + "qwer", 2, FALSE, lubridate::ymd_hms("2016-01-14 12:34:56") +) +table_name <- "test_data" +source_conn <- DBI::dbConnect(duckdb::duckdb(), dbdir = ":memory:") +DBI::dbWriteTable(conn = source_conn, name = table_name, value = test_data) + +# copy the table +target_conn <- DBI::dbConnect(duckdb::duckdb(), dbdir = ":memory:") +copy_entire_table_to_db( + source_conn = source_conn, + table_name = table_name, + target_conn = target_conn +) + +dplyr::collect(dplyr::tbl(target_conn, table_name)) + +\dontrun{ +library(tidyverse) +library(lubridate) +library(dotenv) +library(DBI) +library(RMariaDB) +library(redcapcustodian) + +init_etl("my_script_name") + +rc_conn <- connect_to_redcap_db() +log_conn <- get_package_scope_var("log_con") + +# describe the tables you want to clone +test_tables <- tribble( + ~conn, ~table, + rc_conn, "redcap_user_information", + rc_conn, "redcap_projects", + log_conn, "redcap_summary_metrics" +) + +# make the target DB and clone the tables +target_conn <- DBI::dbConnect( + duckdb::duckdb(), + dbdir = ":memory:" +) +purrr::walk2( + test_tables$conn, + test_tables$table, + copy_table_to_db, + target_conn +) + +# Enumerate the tables you copied if you like +DBI::dbListTables(target_conn) + +# replace original connection objects +rc_conn <- target_conn +log_conn <- target_conn + +# At this point you can do destructive things on the original +# connection objects because they point at the ephemeral +# copies of the tables. +} +} diff --git a/man/project_life_cycle_descriptions.Rd b/man/project_life_cycle_descriptions.Rd index 68bea0f..7e5f647 100644 --- a/man/project_life_cycle_descriptions.Rd +++ b/man/project_life_cycle_descriptions.Rd @@ -5,7 +5,7 @@ \alias{project_life_cycle_descriptions} \title{project_life_cycle_descriptions} \format{ -A charecter vector with 24 elements +A character vector with 24 elements } \usage{ project_life_cycle_descriptions diff --git a/man/project_purpose_labels.Rd b/man/project_purpose_labels.Rd new file mode 100644 index 0000000..053d663 --- /dev/null +++ b/man/project_purpose_labels.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/data.R +\docType{data} +\name{project_purpose_labels} +\alias{project_purpose_labels} +\title{project_purpose_labels} +\format{ +A data frame with 5 rows and 2 variables: +\describe{ + \item{\code{id}}{double primary key} + \item{\code{project_purpose}}{character redcap project purpose} +} +} +\usage{ +project_purpose_labels +} +\description{ +A tibble of project purpose IDs and project purposes that reflect their + meaning as used in the `purpose` column of the `redcap_projects` table +} +\details{ +DETAILS +} +\keyword{datasets} diff --git a/man/project_purpose_other_research_labels.Rd b/man/project_purpose_other_research_labels.Rd new file mode 100644 index 0000000..2d2c186 --- /dev/null +++ b/man/project_purpose_other_research_labels.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/data.R +\docType{data} +\name{project_purpose_other_research_labels} +\alias{project_purpose_other_research_labels} +\title{project_purpose_other_research_labels} +\format{ +A data frame with 5 rows and 2 variables: +\describe{ + \item{\code{id}}{double primary key} + \item{\code{project_purpose_other_research}}{character redcap purpose other research} +} +} +\usage{ +project_purpose_other_research_labels +} +\description{ +A tibble project purpose other IDs and labels that reflect their + meaning as used in the `purpose_other` column of the `redcap_projects` table +} +\details{ +DETAILS +} +\keyword{datasets} diff --git a/man/project_status_labels.Rd b/man/project_status_labels.Rd new file mode 100644 index 0000000..20f3a85 --- /dev/null +++ b/man/project_status_labels.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/data.R +\docType{data} +\name{project_status_labels} +\alias{project_status_labels} +\title{project_status_labels} +\format{ +A data frame with 4 rows and 2 variables: +\describe{ + \item{\code{id}}{double primary key} + \item{\code{project_status}}{character redcap project status} +} +} +\usage{ +project_status_labels +} +\description{ +A tibble of project status IDs and project statuses that reflect their + meaning as used in the `status` column of the `redcap_projects` table +} +\details{ +DETAILS +} +\keyword{datasets} diff --git a/man/write_summary_metrics.Rd b/man/write_summary_metrics.Rd index 30363d3..dd118da 100644 --- a/man/write_summary_metrics.Rd +++ b/man/write_summary_metrics.Rd @@ -8,7 +8,8 @@ write_summary_metrics( reporting_period_start, reporting_period_end, metric_type, - metric_dataframe + metric_dataframe, + conn = NULL ) } \arguments{ @@ -19,6 +20,10 @@ write_summary_metrics( \item{metric_type}{a character string representing the metric type, e.g. "flux", "state"} \item{metric_dataframe}{A wide data frame of key-value pairs with a single row of data} + +\item{conn}{A DBI connection object to the database that holds the +`redcap_summary_metrics` table. Can be left as NULL if the connection is available +on the package scope var "log_con".} } \value{ nothing @@ -32,7 +37,8 @@ Format and write summary metrics to the redcap_summary_metrics table in your LOG reporting_period_start = ymd_hms("2022-01-01 00:00:00", tz=Sys.getenv("TIME_ZONE")), reporting_period_end = ceiling_date(reporting_period_start, "month", change_on_boundary = T) metric_type = "state", - metric_dataframe = my_cool_df + metric_dataframe = my_cool_df, + conn = my_conn ) } } diff --git a/report/render_report.R b/report/render_report.R index 2ae113c..643dae0 100644 --- a/report/render_report.R +++ b/report/render_report.R @@ -5,38 +5,49 @@ library(lubridate) library(rmarkdown) library(sendmailR) library(redcapcustodian) +library(argparse) init_etl("render_report") -if (!dir.exists("output")){ - dir.create("output") -} - +parser <- ArgumentParser() +parser$add_argument("script_name", nargs=1, help="Script to be run") if (!interactive()) { - args <- commandArgs(trailingOnly = T) - script_name <- word(args, 2, sep = "=") + args <- parser$parse_args() + script_name <- args$script_name + if(!fs::file_exists(script_name)) { + stop(sprintf("Specified file, %s, does not exist", script_name)) + } } else { - script_name <- "sample_report.Rmd" + script_name <- "dummy.qmd" + stop(sprintf("Specified file, %s, does not exist", script_name)) } report_name <- word(script_name, 1, sep = "\\.") +report_type <- word(script_name, 2, sep = "\\.") script_run_time <- set_script_run_time() +output_file <- + paste0(str_replace(report_name, ".*/", ""), + "_", + format(script_run_time, "%Y%m%d%H%M%S"), + if_else(report_type == "qmd", ".pdf", "") + ) + +if (report_type == "qmd") { + quarto::quarto_render( + script_name, + output_file = output_file, + output_format = "pdf" + ) +} else { + render( + script_name, + output_file = output_file + ) +} -output_file <- here::here( - "output", - paste0(report_name, - "_", - format(script_run_time, "%Y%m%d%H%M%S")) -) - -full_path_to_output_file <- render( - here::here("report", script_name), - output_file = output_file -) - -output_file_extension <- word(full_path_to_output_file, 2 , sep = "\\.") -attachment_object <- mime_part(full_path_to_output_file, basename(full_path_to_output_file)) +output_file_extension <- word(output_file, 2 , sep = "\\.") +attachment_object <- mime_part(output_file, output_file) email_subject <- paste(report_name, "|", script_run_time) body <- "Please see the attached report." diff --git a/study_template/cron/quarto_html_example b/study_template/cron/quarto_html_example new file mode 100644 index 0000000..17ceb05 --- /dev/null +++ b/study_template/cron/quarto_html_example @@ -0,0 +1,2 @@ +# Run the example Quarto report +17 8-17 * * * root /usr/bin/docker run --rm --env-file /rcc/study_template/.env rcc.site Rscript report/render_report.R report/quarto_html_example.qmd diff --git a/study_template/cron/sample_report b/study_template/cron/sample_report index 2bbe5b0..0f40ad7 100644 --- a/study_template/cron/sample_report +++ b/study_template/cron/sample_report @@ -1,2 +1,2 @@ # Run the sample Rmd report -05 8-17 * * * root /usr/bin/docker run --rm --env-file /rcc/study_template/.env rcc.site Rscript report/render_report.R script_name=sample_report.Rmd +05 8-17 * * * root /usr/bin/docker run --rm --env-file /rcc/study_template/.env rcc.site Rscript report/render_report.R report/sample_report.Rmd diff --git a/study_template/report/quarto_html_example.qmd b/study_template/report/quarto_html_example.qmd new file mode 100644 index 0000000..1a89e4b --- /dev/null +++ b/study_template/report/quarto_html_example.qmd @@ -0,0 +1,27 @@ +--- +title: "Quarto HTML example" +author: "John Doe" +format: html +editor: visual +--- + +## Quarto + +Quarto enables you to weave together content and executable code into a finished document. To learn more about Quarto see . + +## Running Code + +When you click the **Render** button a document will be generated that includes both content and the output of embedded code. You can embed code like this: + +```{r} +1 + 1 +``` + +You can add options to executable code like this + +```{r} +#| echo: false +2 * 2 +``` + +The `echo: false` option disables the printing of code (only output is displayed). diff --git a/study_template/report/quarto_pdf_example.qmd b/study_template/report/quarto_pdf_example.qmd new file mode 100644 index 0000000..cf09dd6 --- /dev/null +++ b/study_template/report/quarto_pdf_example.qmd @@ -0,0 +1,26 @@ +--- +title: "Quarto PDF example" +format: pdf +editor: visual +--- + +## Quarto + +Quarto enables you to weave together content and executable code into a finished document. To learn more about Quarto see . + +## Running Code + +When you click the **Render** button a document will be generated that includes both content and the output of embedded code. You can embed code like this: + +```{r} +1 + 1 +``` + +You can add options to executable code like this + +```{r} +#| echo: false +2 * 2 +``` + +The `echo: false` option disables the printing of code (only output is displayed). diff --git a/study_template/report/render_report.R b/study_template/report/render_report.R index 7d0821d..643dae0 100644 --- a/study_template/report/render_report.R +++ b/study_template/report/render_report.R @@ -5,31 +5,49 @@ library(lubridate) library(rmarkdown) library(sendmailR) library(redcapcustodian) +library(argparse) init_etl("render_report") +parser <- ArgumentParser() +parser$add_argument("script_name", nargs=1, help="Script to be run") if (!interactive()) { - args <- commandArgs(trailingOnly = T) - script_name <- word(args, 2, sep = "=") + args <- parser$parse_args() + script_name <- args$script_name + if(!fs::file_exists(script_name)) { + stop(sprintf("Specified file, %s, does not exist", script_name)) + } } else { - script_name <- "sample_report.Rmd" + script_name <- "dummy.qmd" + stop(sprintf("Specified file, %s, does not exist", script_name)) } report_name <- word(script_name, 1, sep = "\\.") +report_type <- word(script_name, 2, sep = "\\.") script_run_time <- set_script_run_time() +output_file <- + paste0(str_replace(report_name, ".*/", ""), + "_", + format(script_run_time, "%Y%m%d%H%M%S"), + if_else(report_type == "qmd", ".pdf", "") + ) + +if (report_type == "qmd") { + quarto::quarto_render( + script_name, + output_file = output_file, + output_format = "pdf" + ) +} else { + render( + script_name, + output_file = output_file + ) +} -output_file = paste0(report_name, - "_", - format(script_run_time, "%Y%m%d%H%M%S")) - -full_path_to_output_file <- render( - paste0("report/", script_name), - output_file = output_file -) - -output_file_extension <- word(full_path_to_output_file, 2 , sep = "\\.") -attachment_object <- mime_part(full_path_to_output_file, paste0(output_file, ".", output_file_extension)) +output_file_extension <- word(output_file, 2 , sep = "\\.") +attachment_object <- mime_part(output_file, output_file) email_subject <- paste(report_name, "|", script_run_time) body <- "Please see the attached report." diff --git a/tests/testthat/test-devtools.R b/tests/testthat/test-devtools.R index 985dfb1..6a34858 100644 --- a/tests/testthat/test-devtools.R +++ b/tests/testthat/test-devtools.R @@ -51,3 +51,30 @@ testthat::test_that("convert_schema_to_sqlite can convert a MySQL schema to vali testthat::expect_equal(DBI::dbListTables(sqlite_conn), "redcap_entity_project_ownership") DBI::dbDisconnect(sqlite_conn) }) + +testthat::test_that("copy_entire_table_to_db works", { + + # Build the objects need for testing + test_data <- dplyr::tribble( + ~a, ~b, ~c, ~d, + "asdf", 1, TRUE, lubridate::ymd_hms("2023-01-14 12:34:56"), + "qwer", 2, FALSE, lubridate::ymd_hms("2016-01-14 12:34:56") + ) + table_name <- "test_data" + source_conn <- DBI::dbConnect(duckdb::duckdb(), dbdir = ":memory:") + DBI::dbWriteTable(conn = source_conn, name = table_name, value = test_data) + + # copy the table + target_conn <- DBI::dbConnect(duckdb::duckdb(), dbdir = ":memory:") + copy_entire_table_to_db( + source_conn = source_conn, + table_name = table_name, + target_conn = target_conn + ) + + # verify the copy + testthat::expect_equal( + dplyr::collect(dplyr::tbl(target_conn, table_name)), + test_data + ) +}) diff --git a/vignettes/custom_rscript.Rmd b/vignettes/custom_rscript.Rmd index 10e5fc0..6028a2b 100644 --- a/vignettes/custom_rscript.Rmd +++ b/vignettes/custom_rscript.Rmd @@ -194,7 +194,7 @@ Stop and think about architecture and study lifecycle. If you need something to run periodically, you need to make it easy to run with all of its dependencies packaged up and you need to schedule it. The modern way to package dependencies is to put them all into a container, add your application and run the container. REDCap Custodian supports containerization with Docker. The [`./study_template/Dockerfile`](https://github.com/ctsit/redcapcustodian/tree/master/study_template/Dockerfile) is template you can use to containerize your RScript and RMarkdown. Adapt it to your needs and build the container with [./build.sh](https://github.com/ctsit/redcapcustodian/tree/master/build.sh). If you have good container infrastructure, use it to build and deploy your containers. Running automated R Markdown reports from docker requires the use of an Rmd renderer. -[render_report.R](https://github.com/ctsit/redcapcustodian/tree/master/study_template/report/render_report.R) contains functionality to knit an Rmd, email it and then log the job run. The Rmd script name is passed as a command line argument to [render_report.R](https://github.com/ctsit/redcapcustodian/tree/master/study_template/report/render_report.R) via cron. Refer to [sample_report](https://github.com/ctsit/redcapcustodian/tree/master/study_template/cron/sample_report) for an example cron job. +[render_report.R](https://github.com/ctsit/redcapcustodian/tree/master/study_template/report/render_report.R) contains functionality to knit an Rmd or a Qmd, email it and then log the job run. The Rmd/Qmd script name is passed as a command line argument to [render_report.R](https://github.com/ctsit/redcapcustodian/tree/master/study_template/report/render_report.R) via cron. Refer to [sample_report](https://github.com/ctsit/redcapcustodian/tree/master/study_template/cron/sample_report) for an example cron job. ## Automation with Linux and cron