diff --git a/.Rbuildignore b/.Rbuildignore index ae6b391f..20f43252 100755 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -7,3 +7,5 @@ ^\.github$ ^\.lintr$ ^\.pre-commit-config\.yaml$ +^doc$ +^Meta$ diff --git a/.gitignore b/.gitignore index cf066375..399bfa9d 100755 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .Rhistory docs inst/doc +/doc/ +/Meta/ diff --git a/DESCRIPTION b/DESCRIPTION index a6d4a790..5b1cc68f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -12,7 +12,10 @@ Imports: checkmate, grid, R6, - yaml + yaml, + shiny, + shinyWidgets, + zip Suggests: ggplot2, knitr, diff --git a/NAMESPACE b/NAMESPACE index 0721d0a6..ff00728f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -3,6 +3,12 @@ export(ReportCard) export(Reporter) export(TealReportCard) +export(add_card_button_srv) +export(add_card_button_ui) +export(download_report_button_srv) +export(download_report_button_ui) +export(simple_reporter_srv) +export(simple_reporter_ui) importFrom(R6,R6Class) importFrom(checkmate,assert_string) importFrom(grid,grid.newpage) diff --git a/R/AddCardModule.R b/R/AddCardModule.R new file mode 100644 index 00000000..d1452020 --- /dev/null +++ b/R/AddCardModule.R @@ -0,0 +1,100 @@ +#' Add Card Button User Interface +#' @description button for adding views/cards to the Report. +#' +#' For more details see the vignette: `vignette("simpleReporter", "teal.reporter")`. +#' @param id `character` +#' @return `shiny::tagList` +#' @export +add_card_button_ui <- function(id) { + ns <- shiny::NS(id) + shiny::tagList( + shiny::tags$button( + id = ns("add_report_card_button"), + type = "button", + class = "btn btn-primary action-button", + `data-val` = shiny::restoreInput(id = ns("add_report_card_button"), default = NULL), + NULL, + "Add Card" + ) + ) +} + +#' Add Card Button Server +#' @description server for adding views/cards to the Report. +#' +#' For more details see the vignette: `vignette("simpleReporter", "teal.reporter")`. +#' @param id `character` +#' @param reporter `Reporter` instance. +#' @param card_fun `function` which returns a `ReportCard` instance, +#' the function have at`card`argument and optional `comment`. +#' @return `shiny::moduleServer` +#' @export +add_card_button_srv <- function(id, reporter, card_fun) { + checkmate::assert_function(card_fun) + checkmate::assert_class(reporter, "Reporter") + checkmate::assert_subset(names(formals(card_fun)), c("card", "comment"), empty.ok = FALSE) + + shiny::moduleServer( + id, + function(input, output, session) { + ns <- session$ns + add_modal <- function(failed = FALSE) { + shiny::modalDialog( + easyClose = TRUE, + shiny::tags$h3("Add the Card to the Report"), + shiny::tags$hr(), + shiny::textInput( + ns("comment"), + "Comment", + value = "The idea behind", + width = "100%" + ), + if (failed) { + shiny::tags$div( + shiny::tags$b("Invalid", style = "color: red;") + ) + }, + footer = shiny::tagList( + shiny::tags$button( + type = "button", + class = "btn btn-primary", + `data-dismiss` = "modal", + `data-bs-dismiss` = "modal", + NULL, + "Cancel" + ), + shiny::tags$button( + id = ns("add_card_ok"), + type = "button", + class = "btn btn-primary action-button", + `data-val` = shiny::restoreInput(id = ns("add_card_ok"), default = NULL), + NULL, + "Add Card" + ) + ) + ) + } + + shiny::observeEvent(input$add_report_card_button, { + shiny::showModal(add_modal()) + }) + + shiny::observeEvent(input$add_card_ok, { + card <- ReportCard$new() + card_fun_args_nams <- names(formals(card_fun)) + if (length(card_fun_args_nams) == 1) { + card_fun(card) + if (length(input$comment) > 0 && input$comment != "") { + card$append_text("Comment", "header3") + card$append_text(input$comment) + } + } else { + card_fun(card, input$comment) + } + checkmate::assert_class(card, "ReportCard") + reporter$append_cards(list(card)) + shiny::removeModal() + }) + } + ) +} diff --git a/R/ContentBlock.R b/R/ContentBlock.R index 0b515f77..c8a07710 100644 --- a/R/ContentBlock.R +++ b/R/ContentBlock.R @@ -42,13 +42,13 @@ ContentBlock <- R6::R6Class( # nolint: object_name_linter. private = list( content = character(0), - #' @description The copy constructor. - #' - #' @param name `character(1)` the name of the field - #' @param value the value assigned to the field - #' - #' @return the value of the copied field - #' + # @description The copy constructor. + # + # @param name `character(1)` the name of the field + # @param value the value assigned to the field + # + # @return the value of the copied field + # deep_clone = function(name, value) { if (name == "content" && checkmate::test_file_exists(value)) { extension <- "" diff --git a/R/DownloadModule.R b/R/DownloadModule.R new file mode 100644 index 00000000..cd34772e --- /dev/null +++ b/R/DownloadModule.R @@ -0,0 +1,197 @@ +#' Download Button Reporter User Interface +#' @description button for downloading the Report. +#' +#' For more details see the vignette: `vignette("simpleReporter", "teal.reporter")`. +#' @param id `character` +#' @return `shiny::tagList` +#' @export +download_report_button_ui <- function(id) { + ns <- shiny::NS(id) + shiny::tagList( + shiny::tags$button( + id = ns("download_button"), + type = "button", + class = "btn btn-primary action-button", + `data-val` = shiny::restoreInput(id = ns("download_button"), default = NULL), + NULL, + "Download Report" + ) + ) +} + +#' Download Button Server +#' @description server for downloading the Report. +#' +#' For more details see the vignette: `vignette("simpleReporter", "teal.reporter")`. +#' @param id `character` +#' @param reporter `Reporter` instance. +#' @param notification `logical` whether to add a shiny notification about the download process. Default `TRUE`. +#' @param rmd_output `character` vector with `rmarkdown` output types, +#' by default all possible `c("pdf_document", "html_document", "powerpoint_presentation", "word_document")`. +#' @param rmd_yaml_args `named list` vector with `Rmd` `yaml` header fields and their default values. +#' Default `list(author = "NEST", title = "Report", date = Sys.Date(), output = "html_document")`. +#' Please update only values at this moment. +#' @return `shiny::moduleServer` +#' @export +download_report_button_srv <- function(id, + reporter, + notification = TRUE, + rmd_output = c( + "html_document", "pdf_document", + "powerpoint_presentation", "word_document" + ), + rmd_yaml_args = list( + author = "NEST", title = "Report", + date = as.character(Sys.Date()), output = "html_document" + )) { + checkmate::assert_class(reporter, "Reporter") + checkmate::assert_flag(notification) + checkmate::assert_subset(rmd_output, c( + "html_document", "pdf_document", + "powerpoint_presentation", "word_document" + )) + checkmate::assert_list(rmd_yaml_args, names = "named") + checkmate::assert_true(all(c("author", "title", "date", "output") %in% names(rmd_yaml_args))) + + shiny::moduleServer( + id, + function(input, output, session) { + ns <- session$ns + download_modal <- function(failed = FALSE) { + nr_cards <- length(reporter$get_cards()) + downb <- shiny::tags$a( + id = ns("download_data"), + class = paste("btn btn-primary shiny-download-link", if (nr_cards) NULL else "disabled"), + style = if (nr_cards) NULL else "pointer-events: none;", + href = "", + target = "_blank", + download = NA, + shiny::icon("download"), + "Download Report" + ) + shiny::modalDialog( + easyClose = TRUE, + shiny::tags$h3("Download the Report"), + shiny::tags$hr(), + if (length(reporter$get_cards()) == 0) { + shiny::tags$div( + shiny::tags$p(shiny::tags$strong("No Cards Added"), style = "color: red; margin-bottom:15px;") + ) + } else { + shiny::tags$div( + style = "color: green; margin-bottom:15px;", + shiny::tags$p( + shiny::tags$strong(paste("Number of cards: ", nr_cards)) + ), + ) + }, + shiny::textInput(ns("author"), label = "Author:", value = rmd_yaml_args$author), + shiny::textInput(ns("title"), label = "Title:", value = rmd_yaml_args$title), + shiny::dateInput(ns("date"), "Date:", value = rmd_yaml_args$date), + shiny::tags$div( + shinyWidgets::pickerInput( + inputId = ns("output"), + label = "Choose a document type: ", + choices = rmd_output, + selected = rmd_yaml_args$output + ) + ), + if (failed) { + shiny::tags$div(shiny::tags$b("Invalid", style = "color: red;")) + }, + footer = shiny::tagList( + shiny::tags$button( + id = ns("reset_reporter"), + type = "button", + style = "float: left;", + class = "btn btn-danger action-button", + `data-val` = shiny::restoreInput(id = ns("reset_reporter"), default = NULL), + NULL, + "Reset Reporter" + ), + shiny::tags$button( + type = "button", + class = "btn btn-primary", + `data-dismiss` = "modal", + `data-bs-dismiss` = "modal", + NULL, + "Cancel" + ), + downb + ) + ) + } + + shiny::observeEvent(input$download_button, { + shiny::showModal(download_modal()) + }) + + shiny::observeEvent(input$reset_reporter, { + shiny::showModal( + shiny::modalDialog( + shiny::tags$h3("Reset the Report"), + shiny::tags$hr(), + shiny::tags$strong(shiny::tags$p("Are you sure you want to reset the report?")), + footer = shiny::tagList( + shiny::modalButton("Cancel"), + shiny::actionButton(ns("reset_reporter_ok"), "Reset") + ) + ) + ) + }) + + shiny::observeEvent(input$reset_reporter_ok, { + reporter$reset() + shiny::removeModal() + }) + + output$download_data <- shiny::downloadHandler( + filename = function() { + paste("report_", format(Sys.time(), "%y%m%d%H%M%S"), ".zip", sep = "") + }, + content = function(file) { + if (notification) { + shiny::showNotification(sprintf("Rendering and Downloading a document.")) + } + input_list <- lapply(names(rmd_yaml_args), function(x) input[[x]]) + names(input_list) <- names(rmd_yaml_args) + report_render_and_compress(reporter, input_list, file) + }, + contentType = "application/zip" + ) + } + ) +} + +#' Render the Report +#' @description render the report and zip the created directory. +#' @param reporter `Reporter` instance. +#' @param input `list` like shiny input converted to a regular list. +#' @param file `character` where to copy the returned directory. +#' @return `file` argument +#' @keywords internal +report_render_and_compress <- function(reporter, input, file = tempdir()) { + checkmate::assert_class(reporter, "Reporter") + checkmate::assert_list(input, names = "named") + checkmate::assert_string(file) + + yaml <- list( + author = input$author, + title = input$title, + date = as.character(input$date) + ) + if (!is.null(input$output)) { + yaml[["output"]] <- input$output + } + yaml_header <- md_header(yaml::as.yaml(yaml)) + + renderer <- Renderer$new() + renderer$render(reporter$get_blocks(), yaml_header) + + temp_zip_file <- tempfile(fileext = ".zip") + zip::zipr(temp_zip_file, renderer$get_output_dir()) + file.copy(temp_zip_file, file) + + rm(renderer) + file +} diff --git a/R/Renderer.R b/R/Renderer.R index 0631307a..5b1ee727 100644 --- a/R/Renderer.R +++ b/R/Renderer.R @@ -51,7 +51,7 @@ Renderer <- R6::R6Class( # nolint: object_name_linter. #' author = teal.reporter:::yaml_quoted("NEST"), #' title = teal.reporter:::yaml_quoted("Report"), #' date = teal.reporter:::yaml_quoted("07/04/2019"), - #' output = list(pdf_document = list(keep_tex = TRUE)) + #' output = list(html_document = list(toc = FALSE)) #' ) #' #' yaml_header <- teal.reporter:::md_header(yaml::as.yaml(yaml_l)) @@ -103,7 +103,7 @@ Renderer <- R6::R6Class( # nolint: object_name_linter. #' author = teal.reporter:::yaml_quoted("NEST"), #' title = teal.reporter:::yaml_quoted("Report"), #' date = teal.reporter:::yaml_quoted("07/04/2019"), - #' output = list(pdf_document = list(keep_tex = TRUE)) + #' output = list(html_document = list(toc = FALSE)) #' ) #' #' yaml_header <- teal.reporter:::md_header(yaml::as.yaml(yaml_l)) diff --git a/R/ReportCard.R b/R/ReportCard.R index 84262ddf..935036b1 100644 --- a/R/ReportCard.R +++ b/R/ReportCard.R @@ -135,12 +135,12 @@ ReportCard <- R6::R6Class( # nolint: object_name_linter. content = list(), chr_converters = list(), - #' @description The copy constructor. - #' - #' @param name the name of the field - #' @param value the value of the field - #' @return the new value of the field - #' + # @description The copy constructor. + # + # @param name the name of the field + # @param value the value of the field + # @return the new value of the field + # deep_clone = function(name, value) { if (name == "content") { lapply(value, function(content_block) { diff --git a/R/Reporter.R b/R/Reporter.R index 4ecbe5f8..ff260570 100644 --- a/R/Reporter.R +++ b/R/Reporter.R @@ -107,16 +107,24 @@ Reporter <- R6::R6Class( # nolint: object_name_linter. blocks <- append(blocks, private$cards[[length(private$cards)]]$get_content()) } blocks + }, + #' @description Removes all `ReportCard` objects added to this `Reporter`. + #' + #' @return invisibly self + #' + reset = function() { + private$cards <- list() + invisible(self) } ), private = list( cards = list(), - #' @description The copy constructor. - #' - #' @param name the name of the field - #' @param value the value of the field - #' @return the new value of the field - #' + # @description The copy constructor. + # + # @param name the name of the field + # @param value the value of the field + # @return the new value of the field + # deep_clone = function(name, value) { if (name == "cards") { lapply(value, function(card) card$clone(deep = TRUE)) diff --git a/R/SimpleReporter.R b/R/SimpleReporter.R new file mode 100644 index 00000000..824a0f3f --- /dev/null +++ b/R/SimpleReporter.R @@ -0,0 +1,36 @@ +#' Simple Reporter User Interface +#' @description two buttons for adding views and downloading the Report. +#' +#' For more details see the vignette: `vignette("simpleReporter", "teal.reporter")`. +#' @param id `character` +#' @return `shiny::tagList` +#' @export +simple_reporter_ui <- function(id) { + ns <- shiny::NS(id) + shiny::tagList( + add_card_button_ui(ns("add_report_card_simple")), + download_report_button_ui(ns("download_button_simple")), + ) +} + +#' Simple Reporter Server +#' @description two buttons for adding views and downloading the Report. +#' The add module has `add_report_card_simple` id and download module the `download_button_simple` id. +#' +#' For more details see the vignette: `vignette("simpleReporter", "teal.reporter")`. +#' @param id `character` +#' @param reporter `Reporter` instance. +#' @param card_fun `function` which returns a `ReportCard` instance, +#' the function have at`card`argument and optional `comment`. +#' @param notification logical if to add shiny notification about the download process. +#' @return `shiny::moduleServer` +#' @export +simple_reporter_srv <- function(id, reporter, card_fun, notification = TRUE) { + shiny::moduleServer( + id, + function(input, output, session) { + add_card_button_srv("add_report_card_simple", reporter = reporter, card_fun = card_fun) + download_report_button_srv("download_button_simple", reporter = reporter, notification = notification) + } + ) +} diff --git a/man/Renderer.Rd b/man/Renderer.Rd index 57d8fc35..8cc8eca4 100644 --- a/man/Renderer.Rd +++ b/man/Renderer.Rd @@ -44,7 +44,7 @@ yaml_l <- list( author = teal.reporter:::yaml_quoted("NEST"), title = teal.reporter:::yaml_quoted("Report"), date = teal.reporter:::yaml_quoted("07/04/2019"), - output = list(pdf_document = list(keep_tex = TRUE)) + output = list(html_document = list(toc = FALSE)) ) yaml_header <- teal.reporter:::md_header(yaml::as.yaml(yaml_l)) @@ -76,7 +76,7 @@ yaml_l <- list( author = teal.reporter:::yaml_quoted("NEST"), title = teal.reporter:::yaml_quoted("Report"), date = teal.reporter:::yaml_quoted("07/04/2019"), - output = list(pdf_document = list(keep_tex = TRUE)) + output = list(html_document = list(toc = FALSE)) ) yaml_header <- teal.reporter:::md_header(yaml::as.yaml(yaml_l)) @@ -183,7 +183,7 @@ yaml_l <- list( author = teal.reporter:::yaml_quoted("NEST"), title = teal.reporter:::yaml_quoted("Report"), date = teal.reporter:::yaml_quoted("07/04/2019"), - output = list(pdf_document = list(keep_tex = TRUE)) + output = list(html_document = list(toc = FALSE)) ) yaml_header <- teal.reporter:::md_header(yaml::as.yaml(yaml_l)) @@ -241,7 +241,7 @@ yaml_l <- list( author = teal.reporter:::yaml_quoted("NEST"), title = teal.reporter:::yaml_quoted("Report"), date = teal.reporter:::yaml_quoted("07/04/2019"), - output = list(pdf_document = list(keep_tex = TRUE)) + output = list(html_document = list(toc = FALSE)) ) yaml_header <- teal.reporter:::md_header(yaml::as.yaml(yaml_l)) diff --git a/man/Reporter.Rd b/man/Reporter.Rd index 325ed9a7..43aecb02 100644 --- a/man/Reporter.Rd +++ b/man/Reporter.Rd @@ -100,6 +100,7 @@ reporter$get_blocks() \item \href{#method-append_cards}{\code{Reporter$append_cards()}} \item \href{#method-get_cards}{\code{Reporter$get_cards()}} \item \href{#method-get_blocks}{\code{Reporter$get_blocks()}} +\item \href{#method-reset}{\code{Reporter$reset()}} \item \href{#method-clone}{\code{Reporter$clone()}} } } @@ -260,6 +261,19 @@ reporter$get_blocks() } +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-reset}{}}} +\subsection{Method \code{reset()}}{ +Removes all \code{ReportCard} objects added to this \code{Reporter}. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{Reporter$reset()}\if{html}{\out{
}} +} + +\subsection{Returns}{ +invisibly self +} } \if{html}{\out{
}} \if{html}{\out{}} diff --git a/man/add_card_button_srv.Rd b/man/add_card_button_srv.Rd new file mode 100644 index 00000000..fedec6b0 --- /dev/null +++ b/man/add_card_button_srv.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/AddCardModule.R +\name{add_card_button_srv} +\alias{add_card_button_srv} +\title{Add Card Button Server} +\usage{ +add_card_button_srv(id, reporter, card_fun) +} +\arguments{ +\item{id}{\code{character}} + +\item{reporter}{\code{Reporter} instance.} + +\item{card_fun}{\code{function} which returns a \code{ReportCard} instance, +the function have at\code{card}argument and optional \code{comment}.} +} +\value{ +\code{shiny::moduleServer} +} +\description{ +server for adding views/cards to the Report. + +For more details see the vignette: \code{vignette("simpleReporter", "teal.reporter")}. +} diff --git a/man/add_card_button_ui.Rd b/man/add_card_button_ui.Rd new file mode 100644 index 00000000..dcb88bb1 --- /dev/null +++ b/man/add_card_button_ui.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/AddCardModule.R +\name{add_card_button_ui} +\alias{add_card_button_ui} +\title{Add Card Button User Interface} +\usage{ +add_card_button_ui(id) +} +\arguments{ +\item{id}{\code{character}} +} +\value{ +\code{shiny::tagList} +} +\description{ +button for adding views/cards to the Report. + +For more details see the vignette: \code{vignette("simpleReporter", "teal.reporter")}. +} diff --git a/man/download_report_button_srv.Rd b/man/download_report_button_srv.Rd new file mode 100644 index 00000000..80c38935 --- /dev/null +++ b/man/download_report_button_srv.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/DownloadModule.R +\name{download_report_button_srv} +\alias{download_report_button_srv} +\title{Download Button Server} +\usage{ +download_report_button_srv( + id, + reporter, + notification = TRUE, + rmd_output = c("html_document", "pdf_document", "powerpoint_presentation", + "word_document"), + rmd_yaml_args = list(author = "NEST", title = "Report", date = + as.character(Sys.Date()), output = "html_document") +) +} +\arguments{ +\item{id}{\code{character}} + +\item{reporter}{\code{Reporter} instance.} + +\item{notification}{\code{logical} whether to add a shiny notification about the download process. Default \code{TRUE}.} + +\item{rmd_output}{\code{character} vector with \code{rmarkdown} output types, +by default all possible \code{c("pdf_document", "html_document", "powerpoint_presentation", "word_document")}.} + +\item{rmd_yaml_args}{\verb{named list} vector with \code{Rmd} \code{yaml} header fields and their default values. +Default \code{list(author = "NEST", title = "Report", date = Sys.Date(), output = "html_document")}. +Please update only values at this moment.} +} +\value{ +\code{shiny::moduleServer} +} +\description{ +server for downloading the Report. + +For more details see the vignette: \code{vignette("simpleReporter", "teal.reporter")}. +} diff --git a/man/download_report_button_ui.Rd b/man/download_report_button_ui.Rd new file mode 100644 index 00000000..e7419e2a --- /dev/null +++ b/man/download_report_button_ui.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/DownloadModule.R +\name{download_report_button_ui} +\alias{download_report_button_ui} +\title{Download Button Reporter User Interface} +\usage{ +download_report_button_ui(id) +} +\arguments{ +\item{id}{\code{character}} +} +\value{ +\code{shiny::tagList} +} +\description{ +button for downloading the Report. + +For more details see the vignette: \code{vignette("simpleReporter", "teal.reporter")}. +} diff --git a/man/report_render_and_compress.Rd b/man/report_render_and_compress.Rd new file mode 100644 index 00000000..574e74b4 --- /dev/null +++ b/man/report_render_and_compress.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/DownloadModule.R +\name{report_render_and_compress} +\alias{report_render_and_compress} +\title{Render the Report} +\usage{ +report_render_and_compress(reporter, input, file = tempdir()) +} +\arguments{ +\item{reporter}{\code{Reporter} instance.} + +\item{input}{\code{list} like shiny input converted to a regular list.} + +\item{file}{\code{character} where to copy the returned directory.} +} +\value{ +\code{file} argument +} +\description{ +render the report and zip the created directory. +} +\keyword{internal} diff --git a/man/simple_reporter_srv.Rd b/man/simple_reporter_srv.Rd new file mode 100644 index 00000000..4bf73e97 --- /dev/null +++ b/man/simple_reporter_srv.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/SimpleReporter.R +\name{simple_reporter_srv} +\alias{simple_reporter_srv} +\title{Simple Reporter Server} +\usage{ +simple_reporter_srv(id, reporter, card_fun, notification = TRUE) +} +\arguments{ +\item{id}{\code{character}} + +\item{reporter}{\code{Reporter} instance.} + +\item{card_fun}{\code{function} which returns a \code{ReportCard} instance, +the function have at\code{card}argument and optional \code{comment}.} + +\item{notification}{logical if to add shiny notification about the download process.} +} +\value{ +\code{shiny::moduleServer} +} +\description{ +two buttons for adding views and downloading the Report. +The add module has \code{add_report_card_simple} id and download module the \code{download_button_simple} id. + +For more details see the vignette: \code{vignette("simpleReporter", "teal.reporter")}. +} diff --git a/man/simple_reporter_ui.Rd b/man/simple_reporter_ui.Rd new file mode 100644 index 00000000..0f268fe1 --- /dev/null +++ b/man/simple_reporter_ui.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/SimpleReporter.R +\name{simple_reporter_ui} +\alias{simple_reporter_ui} +\title{Simple Reporter User Interface} +\usage{ +simple_reporter_ui(id) +} +\arguments{ +\item{id}{\code{character}} +} +\value{ +\code{shiny::tagList} +} +\description{ +two buttons for adding views and downloading the Report. + +For more details see the vignette: \code{vignette("simpleReporter", "teal.reporter")}. +} diff --git a/tests/testthat/test-DownloadReportModule.R b/tests/testthat/test-DownloadReportModule.R new file mode 100644 index 00000000..25c7c625 --- /dev/null +++ b/tests/testthat/test-DownloadReportModule.R @@ -0,0 +1,97 @@ +card1 <- ReportCard$new() +card1$append_text("Header 2 text", "header2") +card1$append_text("A paragraph of default text", "header2") +card1$append_plot( + ggplot2::ggplot(iris, ggplot2::aes(x = Petal.Length)) + + ggplot2::geom_histogram() +) + +reporter <- Reporter$new() +reporter$append_cards(list(card1)) + +testthat::test_that("download_report_button_srv - render and downlaod a document", { + shiny::testServer( + download_report_button_srv, + args = list(reporter = reporter, notification = FALSE), + expr = { + session$setInputs(`download_button` = 0) + session$setInputs(`output` = "html_document") + session$setInputs(`title` = "TITLE") + session$setInputs(`author` = "AUTHOR") + session$setInputs(`download_data` = 0) + + f <- output$download_data + testthat::expect_true(file.exists(f)) + tmp_dir <- tempdir() + output_dir <- file.path(tmp_dir, sprintf("report_test_%s", gsub("[.]", "", format(Sys.time(), "%Y%m%d%H%M%OS4")))) + dir.create(path = output_dir) + zip::unzip(f, exdir = output_dir) + files <- list.files(output_dir, recursive = TRUE) + testthat::expect_true(any(grepl("[.]Rmd", files))) + testthat::expect_true(any(grepl("[.]html", files))) + } + ) +}) + +card1 <- ReportCard$new() +card1$append_text("Header 2 text", "header2") +card1$append_text("A paragraph of default text", "header2") +card1$append_plot( + ggplot2::ggplot(iris, ggplot2::aes(x = Petal.Length)) + + ggplot2::geom_histogram() +) + +reporter <- Reporter$new() +reporter$append_cards(list(card1)) + +testthat::test_that("download_report_button_srv - reset a report", { + shiny::testServer( + download_report_button_srv, + args = list(reporter = reporter, notification = FALSE), + expr = { + testthat::expect_identical(reporter$get_cards(), list(card1)) + session$setInputs(`reset_reporter` = 0) + session$setInputs(`reset_reporter_ok` = 0) + testthat::expect_identical(reporter$get_blocks(), list()) + } + ) +}) + +testthat::test_that("download_report_button_ui - returns a tagList", { + testthat::expect_true( + inherits(download_report_button_ui("sth"), c("shiny.tag.list", "list")) + ) +}) + +card1 <- ReportCard$new() +card1$append_text("Header 2 text", "header2") +card1$append_text("A paragraph of default text", "header2") +card1$append_plot( + ggplot2::ggplot(iris, ggplot2::aes(x = Petal.Length)) + + ggplot2::geom_histogram() +) + +reporter <- Reporter$new() +reporter$append_cards(list(card1)) +input <- list(author = "NEST", title = "Report", output = "html_document") +temp_dir <- tempdir() + +testthat::test_that("report_render_and_compress - valid arguments", { + testthat::expect_error(report_render_and_compress(reporter, input, temp_dir), NA) +}) + +testthat::test_that("report_render_and_compress - invalid arguments", { + testthat::expect_error(report_render_and_compress(reporter, list(), temp_zip)) + testthat::expect_error(report_render_and_compress(reporter, input, 2)) + testthat::expect_error(report_render_and_compress(reporter, list, "")) +}) + +testthat::test_that("report_render_and_compress - render an html document", { + input <- list(author = "NEST", title = "Report", output = "html_document") + temp_dir <- tempdir() + res_path <- report_render_and_compress(reporter, input, temp_dir) + expect_identical(res_path, temp_dir) + files <- list.files(temp_dir, recursive = TRUE) + testthat::expect_true(any(grepl("[.]Rmd", files))) + testthat::expect_true(any(grepl("[.]html", files))) +}) diff --git a/tests/testthat/test-SimpleReporter.R b/tests/testthat/test-SimpleReporter.R new file mode 100644 index 00000000..a1485c7f --- /dev/null +++ b/tests/testthat/test-SimpleReporter.R @@ -0,0 +1,93 @@ +card_fun0 <- function(card = ReportCard$new()) { + card$append_text("Header 2 text", "header2") + card$append_text("A paragraph of default text", "header2") + card$append_plot( + ggplot2::ggplot(iris, ggplot2::aes(x = Petal.Length)) + + ggplot2::geom_histogram() + ) + card +} + + +reporter <- Reporter$new() +reporter$append_cards(list(card_fun0())) + +testthat::test_that("simple_reporter_srv - render and downlaod a document", { + shiny::testServer( + simple_reporter_srv, + args = list(reporter = reporter, notification = FALSE, card_fun = card_fun0), + expr = { + session$setInputs(`download_button_simple` = 0) + session$setInputs(`download_button_simple-output` = "html_document") + session$setInputs(`download_button_simple-title` = "TITLE") + session$setInputs(`download_button_simple-author` = "AUTHOR") + session$setInputs(`download_button_simple-download_data` = 0) + + + f <- output$`download_button_simple-download_data` + testthat::expect_true(file.exists(f)) + tmp_dir <- tempdir() + output_dir <- file.path(tmp_dir, sprintf("report_test_%s", gsub("[.]", "", format(Sys.time(), "%Y%m%d%H%M%OS4")))) + dir.create(path = output_dir) + zip::unzip(f, exdir = output_dir) + files <- list.files(output_dir, recursive = TRUE) + testthat::expect_true(any(grepl("[.]Rmd", files))) + testthat::expect_true(any(grepl("[.]html", files))) + } + ) +}) + +card_fun <- function(card = ReportCard$new(), comment = NULL) { + card$append_text("Header 2 text", "header2") + card$append_text("A paragraph of default text", "header2") + card$append_plot( + ggplot2::ggplot(iris, ggplot2::aes(x = Petal.Length)) + + ggplot2::geom_histogram() + ) + card +} + +card1 <- card_fun() + +reporter <- Reporter$new() +reporter$append_cards(list(card1)) + +testthat::test_that("simple_reporter_srv - reset a reporter", { + shiny::testServer( + simple_reporter_srv, + args = list(reporter = reporter, notification = FALSE, card_fun = card_fun), + expr = { + testthat::expect_identical(reporter$get_cards(), list(card1)) + session$setInputs(`download_button_simple-reset_reporter` = 0) + session$setInputs(`download_button_simple-reset_reporter_ok` = 0) + testthat::expect_identical(reporter$get_blocks(), list()) + } + ) +}) + + +reporter <- Reporter$new() + +testthat::test_that("simple_reporter_srv - add a Card to Reporter", { + shiny::testServer( + simple_reporter_srv, + args = list(reporter = reporter, notification = FALSE, card_fun = card_fun0), + expr = { + card_len <- length(card_fun0()$get_content()) + session$setInputs(`add_report_card_simple-add_report_card_button` = 0) + session$setInputs(`add_report_card_simple-comment` = "Comment Body") + session$setInputs(`add_report_card_simple-add_card_ok` = 0) + + testthat::expect_identical( + length(reporter$get_blocks()), + card_len + 2L + ) + } + ) +}) + +testthat::test_that("simple_reporter_ui - returns a tagList", { + testthat::expect_true( + inherits(simple_reporter_ui("sth"), c("shiny.tag.list", "list")) + ) +}) diff --git a/tests/testthat/test-addCardModule.R b/tests/testthat/test-addCardModule.R new file mode 100644 index 00000000..43d866ef --- /dev/null +++ b/tests/testthat/test-addCardModule.R @@ -0,0 +1,37 @@ +card_fun <- function(card = ReportCard$new(), + comment = NULL) { + card$append_text("Header 2 text", "header2") + card$append_text("A paragraph of default text", "header2") + card$append_plot( + ggplot2::ggplot(iris, ggplot2::aes(x = Petal.Length)) + + ggplot2::geom_histogram() + ) + card +} + + +reporter <- Reporter$new() + +testthat::test_that("add_card_button_srv - add a Card to the Reporter", { + shiny::testServer( + add_card_button_srv, + args = list(reporter = reporter, card_fun = card_fun), + expr = { + card_len <- length(card_fun()$get_content()) + session$setInputs(`add_report_card_button` = 0) + session$setInputs(comment = "Comment Body") + session$setInputs(`add_card_ok` = 0) + + testthat::expect_identical( + length(reporter$get_blocks()), + card_len + ) + } + ) +}) + +testthat::test_that("add_card_button_ui - returns a tagList", { + testthat::expect_true( + inherits(add_card_button_ui("sth"), c("shiny.tag.list", "list")) + ) +}) diff --git a/vignettes/.gitignore b/vignettes/.gitignore new file mode 100644 index 00000000..097b2416 --- /dev/null +++ b/vignettes/.gitignore @@ -0,0 +1,2 @@ +*.html +*.R diff --git a/vignettes/simpleReporter.Rmd b/vignettes/simpleReporter.Rmd new file mode 100644 index 00000000..24ab375b --- /dev/null +++ b/vignettes/simpleReporter.Rmd @@ -0,0 +1,221 @@ +--- +title: "Simple Reporter" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{Simple Reporter} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +``` + +```{r setup} +library(shiny) +library(teal.reporter) +library(ggplot2) +library(rtables) +``` + +Simple Reporter is a shiny module for capturing app views during the session, and eventually downloading a report document. +The Simple Reporter module consists of two separate modules one for each of the two buttons, Add Card and Download Report buttons modules. + +The code added to introduce the simple reporter is wrapped in the `### REPORTER` code blocks. + +The implementation should consist of 5 steps: + +1. Add modules user interface to the user interface of the app. +2. Initialize Reporter instance. +3. Create the Report Card function with two arguments: card and comment. +The function should build the Card step by step and assuming it is empty at the beginning, +the optional comment argument is a string provided by the user when the card is added. +If the comment argument is not specified then it is added automatically at the end of the Card. +This part requires the developer to use their imagination on how the document page should look like. +4. Invoke the servers with the reporter instance and the function to create the report card instance. + +Simple Reporter shiny app with separate modules for each button: + +```{r} +ui <- fluidPage( + titlePanel(""), + sidebarLayout( + sidebarPanel( + uiOutput("encoding") + ), + mainPanel( + ### REPORTER + teal.reporter::add_card_button_ui("addReportCard"), + teal.reporter::download_report_button_ui("downloadButton"), + ### + tags$br(), + tags$br(), + tabsetPanel( + id = "tabs", + tabPanel("Plot", plotOutput("dist_plot")), + tabPanel("Table", verbatimTextOutput("table")) + ) + ) + ) +) + +server <- function(input, output, session) { + output$encoding <- renderUI({ + if (input$tabs == "Plot") { + sliderInput( + "binwidth", + "binwidth", + min = 2, + max = 10, + value = 8 + ) + } else { + selectInput( + "stat", + label = "Statistic", + choices = c("mean", "median", "sd"), + "mean" + ) + } + }) + + plot <- reactive({ + req(input$binwidth) + x <- mtcars$mpg + ggplot2::ggplot(data = mtcars, ggplot2::aes(x = mpg)) + + ggplot2::geom_histogram(binwidth = input$binwidth) + }) + + output$dist_plot <- renderPlot({ + plot() + }) + + table <- reactive({ + req(input$stat) + lyt <- basic_table() %>% + split_rows_by("Month", label_pos = "visible") %>% + analyze("Ozone", afun = eval(str2expression(input$stat))) + + build_table(lyt, airquality) + }) + + output$table <- renderPrint({ + table() + }) + + ### REPORTER + reporter <- teal.reporter::Reporter$new() + card_fun <- function(card = ReportCard$new(), comment) { + if (input$tabs == "Plot") { + card$append_text("My plot", "header2") + card$append_plot(plot()) + } else if (input$tabs == "Table") { + card$append_text("My Table", "header2") + card$append_table(table()) + } + card$append_text("Comment", "header3") + card$append_text(comment) + card + } + + teal.reporter::add_card_button_srv("addReportCard", reporter = reporter, card_fun = card_fun) + teal.reporter::download_report_button_srv("downloadButton", reporter = reporter) + ### +} + +shinyApp(ui = ui, server = server) +``` + +Simple Reporter shiny app with combined buttons modules: + +```{r} +ui <- fluidPage( + titlePanel(""), + sidebarLayout( + sidebarPanel( + uiOutput("encoding") + ), + mainPanel( + ### REPORTER + teal.reporter::simple_reporter_ui("simpleReporter"), + ### + tags$br(), + tags$br(), + tabsetPanel( + id = "tabs", + tabPanel("Plot", plotOutput("dist_plot")), + tabPanel("Table", verbatimTextOutput("table")) + ) + ) + ) +) + +server <- function(input, output, session) { + output$encoding <- renderUI({ + if (input$tabs == "Plot") { + sliderInput( + "binwidth", + "binwidth", + min = 2, + max = 10, + value = 8 + ) + } else { + selectInput( + "stat", + label = "Statistic", + choices = c("mean", "median", "sd"), + "mean" + ) + } + }) + + plot <- reactive({ + req(input$binwidth) + x <- mtcars$mpg + ggplot2::ggplot(data = mtcars, ggplot2::aes(x = mpg)) + + ggplot2::geom_histogram(binwidth = input$binwidth) + }) + + output$dist_plot <- renderPlot({ + plot() + }) + + table <- reactive({ + req(input$stat) + lyt <- basic_table() %>% + split_rows_by("Month", label_pos = "visible") %>% + analyze("Ozone", afun = eval(str2expression(input$stat))) + + build_table(lyt, airquality) + }) + + output$table <- renderPrint({ + table() + }) + + ### REPORTER + reporter <- teal.reporter::Reporter$new() + card_fun <- function(card = ReportCard$new(), comment) { + if (input$tabs == "Plot") { + card$append_text("My plot", "header2") + card$append_plot(plot()) + } else if (input$tabs == "Table") { + card$append_text("My Table", "header2") + card$append_table(table()) + } + card$append_text("Comment", "header3") + card$append_text(comment) + card + } + + teal.reporter::simple_reporter_srv("simpleReporter", reporter = reporter, card_fun = card_fun) + ### +} + +shinyApp(ui = ui, server = server) +``` +