From d082c1636465799a1bc9ffd0322b628b86c2777b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Wed, 5 Nov 2025 17:35:20 +0000 Subject: [PATCH 01/22] feat: adds tm_rmarkdown module --- DESCRIPTION | 2 +- R/tm_rmarkdown.R | 217 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 R/tm_rmarkdown.R diff --git a/DESCRIPTION b/DESCRIPTION index 886ba3c1d..55a1272c9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -48,6 +48,7 @@ Imports: lattice (>= 0.18-4), lifecycle (>= 0.2.0), MASS (>= 7.3-60), + rmarkdown (>= 2.23), rtables (>= 0.6.11), scales (>= 1.3.0), shinyjs (>= 2.1.0), @@ -73,7 +74,6 @@ Suggests: nestcolor (>= 0.1.0), pkgload, rlang (>= 1.0.0), - rmarkdown (>= 2.23), roxy.shinylive, rvest, shinytest2, diff --git a/R/tm_rmarkdown.R b/R/tm_rmarkdown.R new file mode 100644 index 000000000..7a044a302 --- /dev/null +++ b/R/tm_rmarkdown.R @@ -0,0 +1,217 @@ +#' `teal` module: Rmarkdown render +#' +#' Module to render R Markdown files using the data provided in the `teal_data` object. +#' +#' The R Markdown file should be designed to accept parameters corresponding to the datasets. +#' See using `params` in R Markdown documentation: +#' [bookdown.org/yihui/rmarkdown/params-use.html](https://bookdown.org/yihui/rmarkdown/params-use.html) +#' +#' For example, if the `teal_data` object contains datasets named "mtcars" and "iris", +#' the R Markdown file can define parameters as follows: +#' ```yaml +#' --- +#' title: "R Markdown Report" +#' output: html_document +#' params: +#' mtcars: NULL +#' iris: NULL +#' --- +#' ```` +#' +#' The libraries used in the R Markdown file must be available in +#' the Shiny app environment. +#' +#' @inheritParams teal::module +#' @inheritParams shared_params +#' +#' @param rmd_file (`character`) Path to the R Markdown file to be rendered. +#' The file must be accessible from the Shiny app environment. +#' @param allow_download (`logical`) whether to allow downloading of the R Markdown file. +#' Defaults to `TRUE`. +#' +#' @inherit shared_params return +#' +#' @inheritSection teal::example_module Reporting +#' +#' @examplesShinylive +#' library(teal.modules.general) +#' interactive <- function() TRUE +#' {{ next_example }} +#' @examples +#' +#' # general data example +#' data <- teal_data() +#' data <- within(data, { +#' CO2 <- CO2 +#' CO2[["primary_key"]] <- seq_len(nrow(CO2)) +#' }) +#' join_keys(data) <- join_keys(join_key("CO2", "CO2", "primary_key")) +#' +#' +#' app <- init( +#' data = data, +#' modules = modules( +#' tm_rmarkdown( +#' label = "RMarkdown Module", +#' rmd_file = "test.Rmd" +#' ) +#' ) +#' ) +#' if (interactive()) { +#' shinyApp(app$ui, app$server) +#' } +#' +#' @export +#' +tm_rmarkdown <- function(label = "Outliers Module", + rmd_file, + datanames = "all", + allow_download = TRUE, + pre_output = NULL, + post_output = NULL, + transformators = list()) { + message("Initializing tm_rmarkdown") + + # Start of assertions + + checkmate::assert_string(label) + checkmate::assert_file(rmd_file, access = "r") + checkmate::assert_flag(allow_download) + + checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + + # End of assertions + + # Make UI args + args <- as.list(environment()) + + ans <- module( + label = label, + server = srv_rmarkdown, + server_args = list(rmd_file = rmd_file, allow_download = allow_download), + ui = ui_rmarkdown, + ui_args = args, + transformators = transformators, + datanames = datanames + ) + # attr(ans, "teal_bookmarkable") <- TRUE + ans +} + +# UI function for the outliers module +ui_rmarkdown <- function(id, rmd_file, allow_download, ...) { + args <- list(...) + ns <- NS(id) + + teal.widgets::standard_layout( + output = teal.widgets::white_small_well( + tags$div( + tags$h4( + "Module from R Markdown file: ", + tags$code(basename(rmd_file)) + ), + if (allow_download) { + downloadButton( + ns("download_rmd"), + sprintf("Download '%s'", basename(rmd_file)), + class = "btn-primary btn-sm" + ) + } + + ), + tags$hr(), + uiOutput(ns("rmd_output")) + ), + encoding = NULL, + pre_output = args$pre_output, + post_output = args$post_output + ) +} + +# Server function for the outliers module +# Server function for the outliers module +srv_rmarkdown <- function(id, data, rmd_file, allow_download) { + checkmate::assert_class(data, "reactive") + checkmate::assert_class(isolate(data()), "teal_data") + moduleServer(id, function(input, output, session) { + output$download_rmd <- downloadHandler( + filename = function() basename(rmd_file), + content = function(file) file.copy(rmd_file, file), + contentType = "text/plain" + ) + + q_r <- reactive({ + data_q <- req(data()) + teal.reporter::teal_card(data_q) <- c( + teal.reporter::teal_card(data_q), + teal.reporter::teal_card("## Module's output(s)") + ) + eval_code( + data_q, + sprintf( + "rmd_data <- list(%s)", + toString(sprintf("%1$s = %1$s", sapply(names(data_q), as.name))) + ) + ) + }) + + rendered_path_r <- reactive({ + datasets <- req(q_r()) # Ensure data is available + + temp_dir <- tempdir() + temp_rmd <- tempfile(tmpdir = temp_dir, fileext = ".Rmd") + temp_html <- tempfile(tmpdir = temp_dir, fileext = ".md") + file.copy(rmd_file, temp_rmd) # Use a copy of the Rmd file to avoid modifying the original + + tryCatch({ + rmarkdown::render( + temp_rmd, + output_format = rmarkdown::md_document( + variant = "gfm", + toc = TRUE, + preserve_yaml = TRUE + ), + output_file = temp_html, + params = datasets[["rmd_data"]], + envir = new.env(parent = globalenv()), + quiet = TRUE, + runtime = "static" + ) + temp_html + }, error = function(e) { + warning("Error rendering RMD file: ", e$message) # verbose error in logs + e + }) + }) + + rendered_html_r <- reactive({ + output_path <- req(rendered_path_r()) + validate( + need(inherits(output_path, "character"), "Error rendering RMD file. Please contact the app developer.") + ) + htmltools::includeMarkdown(output_path) + }) + + output$rmd_output <- renderUI(rendered_html_r()) + + reactive({ + out_data <- eval_code( + q_r(), + paste( + sep = "\n", + sprintf("## R Markdown contents are generated from file, please download it from the module UI."), + sprintf("# rmarkdown::render(%s, params = rmd_data)", shQuote(basename(rmd_file), type = "cmd")) + ) + ) + + out_data@verified <- FALSE + + teal.reporter::teal_card(out_data) <- c( + teal.reporter::teal_card(out_data), + rendered_html_r() + ) + out_data + }) + }) +} From 6d064e1d3155d38464c5c2b1a3392f0bec394a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Wed, 5 Nov 2025 17:38:02 +0000 Subject: [PATCH 02/22] docs: update roxygen --- NAMESPACE | 1 + man/tm_rmarkdown.Rd | 91 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 man/tm_rmarkdown.Rd diff --git a/NAMESPACE b/NAMESPACE index 302a2f68e..e38348c37 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -23,6 +23,7 @@ export(tm_g_scatterplot) export(tm_g_scatterplotmatrix) export(tm_missing_data) export(tm_outliers) +export(tm_rmarkdown) export(tm_t_crosstable) export(tm_variable_browser) import(ggplot2) diff --git a/man/tm_rmarkdown.Rd b/man/tm_rmarkdown.Rd new file mode 100644 index 000000000..32a83f10f --- /dev/null +++ b/man/tm_rmarkdown.Rd @@ -0,0 +1,91 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/tm_rmarkdown.R +\name{tm_rmarkdown} +\alias{tm_rmarkdown} +\title{\code{teal} module: Rmarkdown render} +\usage{ +tm_rmarkdown( + label = "Outliers Module", + rmd_file, + datanames = "all", + allow_download = TRUE, + pre_output = NULL, + post_output = NULL, + transformators = list() +) +} +\arguments{ +\item{rmd_file}{(\code{character}) Path to the R Markdown file to be rendered. +The file must be accessible from the Shiny app environment.} + +\item{allow_download}{(\code{logical}) whether to allow downloading of the R Markdown file. +Defaults to \code{TRUE}.} + +\item{pre_output}{(\code{shiny.tag}) optional, text or UI element to be displayed before the module's output, +providing context or a title. +with text placed before the output to put the output into context. For example a title.} + +\item{post_output}{(\code{shiny.tag}) optional, text or UI element to be displayed after the module's output, +adding context or further instructions. Elements like \code{shiny::helpText()} are useful.} +} +\value{ +Object of class \code{teal_module} to be used in \code{teal} applications. +} +\description{ +Module to render R Markdown files using the data provided in the \code{teal_data} object. +} +\details{ +The R Markdown file should be designed to accept parameters corresponding to the datasets. +See using \code{params} in R Markdown documentation: +\href{https://bookdown.org/yihui/rmarkdown/params-use.html}{bookdown.org/yihui/rmarkdown/params-use.html} + +For example, if the \code{teal_data} object contains datasets named "mtcars" and "iris", +the R Markdown file can define parameters as follows: + +\if{html}{\out{
}}\preformatted{--- +title: "R Markdown Report" +output: html_document +params: + mtcars: NULL + iris: NULL +--- +}\if{html}{\out{
}} + +The libraries used in the R Markdown file must be available in +the Shiny app environment. +} +\examples{ + +# general data example +data <- teal_data() +data <- within(data, { + CO2 <- CO2 + CO2[["primary_key"]] <- seq_len(nrow(CO2)) +}) +join_keys(data) <- join_keys(join_key("CO2", "CO2", "primary_key")) + + +app <- init( + data = data, + modules = modules( + tm_rmarkdown( + label = "RMarkdown Module", + rmd_file = "test.Rmd" + ) + ) +) +if (interactive()) { + shinyApp(app$ui, app$server) +} + +} +\section{Examples in Shinylive}{ +\describe{ + \item{example-1}{ + \href{https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqajGIgEwCu1OAGcMAcwpxm1AJQAdCLTIyoBUrQBucAAQAeALS6AZoIgbaJdnN0AVLAFUAokrcQAxLqkQ11XfxQpFC6cAAesKgiSoHBBsZc1AD6sVA2MUEhRroA7rSkABYq7Km4uiBKuroAwgDyAEzxNQ2VzfXAwApgqIy0MCysSQDWcKxdALrjTaJwAI5JIhDsEIxEOex19XKKEAC+OwBWRCrDo6IlmbbZRycjrOc3EKccXZtdZa8teLpdPX0Dzy623cSjQqCaKny7FaqV0AF4AplcK0+EIRKJ4bpUcIxNCIFUqqQYElGP1GEN+Gslq0CbpqFB6HB-AiulgALIsClU3RsgQ4940gmk-hJEy0ESYrrkUSkbAwfhdQU7Ko7Ha0Ey6dgqcjMSw6Gy2Cr43SiIoQVgAQXQ7DBABJBLQynaZowdIwdrslGBduMgA}{Open in Shinylive} + \if{html}{\out{}} + \if{html}{\out{}} + } +} +} + From 2c6b90e2d57f53173f978012b1d2f4a2af2954c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Wed, 5 Nov 2025 17:40:07 +0000 Subject: [PATCH 03/22] docs: update roxygen --- R/tm_rmarkdown.R | 2 +- man/tm_rmarkdown.Rd | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/R/tm_rmarkdown.R b/R/tm_rmarkdown.R index 7a044a302..0d8f4caab 100644 --- a/R/tm_rmarkdown.R +++ b/R/tm_rmarkdown.R @@ -63,7 +63,7 @@ #' #' @export #' -tm_rmarkdown <- function(label = "Outliers Module", +tm_rmarkdown <- function(label = "RMarkdown Module", rmd_file, datanames = "all", allow_download = TRUE, diff --git a/man/tm_rmarkdown.Rd b/man/tm_rmarkdown.Rd index 32a83f10f..bf209264f 100644 --- a/man/tm_rmarkdown.Rd +++ b/man/tm_rmarkdown.Rd @@ -5,7 +5,7 @@ \title{\code{teal} module: Rmarkdown render} \usage{ tm_rmarkdown( - label = "Outliers Module", + label = "RMarkdown Module", rmd_file, datanames = "all", allow_download = TRUE, @@ -15,9 +15,21 @@ tm_rmarkdown( ) } \arguments{ +\item{label}{(\code{character(1)}) Label shown in the navigation item for the module or module group. +For \code{modules()} defaults to \code{"root"}. See \code{Details}.} + \item{rmd_file}{(\code{character}) Path to the R Markdown file to be rendered. The file must be accessible from the Shiny app environment.} +\item{datanames}{(\code{character}) Names of the datasets relevant to the item. +There are 2 reserved values that have specific behaviors: +\itemize{ +\item The keyword \code{"all"} includes all datasets available in the data passed to the teal application. +\item \code{NULL} hides the sidebar panel completely. +\item If \code{transformators} are specified, their \code{datanames} are automatically added to this \code{datanames} +argument. +}} + \item{allow_download}{(\code{logical}) whether to allow downloading of the R Markdown file. Defaults to \code{TRUE}.} @@ -27,6 +39,9 @@ with text placed before the output to put the output into context. For example a \item{post_output}{(\code{shiny.tag}) optional, text or UI element to be displayed after the module's output, adding context or further instructions. Elements like \code{shiny::helpText()} are useful.} + +\item{transformators}{(\code{list} of \code{teal_transform_module}) that will be applied to transform module's data input. +To learn more check \code{vignette("transform-input-data", package = "teal")}.} } \value{ Object of class \code{teal_module} to be used in \code{teal} applications. From b326567f29adacc050eb2ada74782038f72bf4f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Wed, 5 Nov 2025 17:41:08 +0000 Subject: [PATCH 04/22] chore: fix some comments --- R/tm_rmarkdown.R | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/R/tm_rmarkdown.R b/R/tm_rmarkdown.R index 0d8f4caab..7c93867c4 100644 --- a/R/tm_rmarkdown.R +++ b/R/tm_rmarkdown.R @@ -99,7 +99,7 @@ tm_rmarkdown <- function(label = "RMarkdown Module", ans } -# UI function for the outliers module +# UI function for the rmarkdown module ui_rmarkdown <- function(id, rmd_file, allow_download, ...) { args <- list(...) ns <- NS(id) @@ -129,8 +129,7 @@ ui_rmarkdown <- function(id, rmd_file, allow_download, ...) { ) } -# Server function for the outliers module -# Server function for the outliers module +# Server function for the rmarkdown module srv_rmarkdown <- function(id, data, rmd_file, allow_download) { checkmate::assert_class(data, "reactive") checkmate::assert_class(isolate(data()), "teal_data") From 2e690ffc371b7ad5da20203642ec8d8719a428bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Wed, 5 Nov 2025 17:42:33 +0000 Subject: [PATCH 05/22] fix: minor typo --- R/tm_rmarkdown.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/tm_rmarkdown.R b/R/tm_rmarkdown.R index 7c93867c4..d8fa31018 100644 --- a/R/tm_rmarkdown.R +++ b/R/tm_rmarkdown.R @@ -108,7 +108,7 @@ ui_rmarkdown <- function(id, rmd_file, allow_download, ...) { output = teal.widgets::white_small_well( tags$div( tags$h4( - "Module from R Markdown file: ", + "Rendered report from: ", tags$code(basename(rmd_file)) ), if (allow_download) { From 2b17cde077ad3252ef5ae3bbce81bb6411a04777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Wed, 5 Nov 2025 17:47:26 +0000 Subject: [PATCH 06/22] chore: enforce srv protection against allow_download = FALSE --- R/tm_rmarkdown.R | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/R/tm_rmarkdown.R b/R/tm_rmarkdown.R index d8fa31018..0c644f830 100644 --- a/R/tm_rmarkdown.R +++ b/R/tm_rmarkdown.R @@ -134,11 +134,13 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download) { checkmate::assert_class(data, "reactive") checkmate::assert_class(isolate(data()), "teal_data") moduleServer(id, function(input, output, session) { - output$download_rmd <- downloadHandler( - filename = function() basename(rmd_file), - content = function(file) file.copy(rmd_file, file), - contentType = "text/plain" - ) + if (allow_download) { + output$download_rmd <- downloadHandler( + filename = function() basename(rmd_file), + content = function(file) file.copy(rmd_file, file), + contentType = "text/plain" + ) + } q_r <- reactive({ data_q <- req(data()) From 157c1946dbd15749b4a18e7d985f5acb2b959af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 6 Nov 2025 12:56:04 +0000 Subject: [PATCH 07/22] feat: add extra inputs --- R/tm_rmarkdown.R | 81 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 62 insertions(+), 19 deletions(-) diff --git a/R/tm_rmarkdown.R b/R/tm_rmarkdown.R index 0c644f830..12cb55e72 100644 --- a/R/tm_rmarkdown.R +++ b/R/tm_rmarkdown.R @@ -28,6 +28,8 @@ #' The file must be accessible from the Shiny app environment. #' @param allow_download (`logical`) whether to allow downloading of the R Markdown file. #' Defaults to `TRUE`. +#' @param extra_transform (`list`) of [teal::teal_transform_module()] that will be added in the module's UI. +#' This can be used to create interactive inputs that modify the parameters in R Markdown rendering. #' #' @inherit shared_params return #' @@ -61,6 +63,38 @@ #' shinyApp(app$ui, app$server) #' } #' +#' @examples +#' static_decorator <- teal_transform_module( +#' label = "N Rows selector", +#' ui = function(id) { +#' ns <- NS(id) +#' tags$div( +#' numericInput(ns("n_rows"), "Show n rows", value = 5, min = 0, max = 200, step = 5) +#' ) +#' }, +#' server = function(id, data) { +#' moduleServer(id, function(input, output, session) { +#' reactive({ +#' req(data()) +#' within(data(), { +#' rmd_data$n_rows <- n_rows_value +#' }, n_rows_value = input$n_rows) +#' }) +#' }) +#' } +#' ) +#' +#' app <- init( +#' data = data, +#' modules = modules( +#' tm_rmarkdown( +#' label = "RMarkdown Module", +#' rmd_file = "test.Rmd", +#' allow_download = FALSE, +#' decorators = list(static_decorator) +#' ) +#' ) +#' ) |> shiny::runApp() #' @export #' tm_rmarkdown <- function(label = "RMarkdown Module", @@ -69,7 +103,8 @@ tm_rmarkdown <- function(label = "RMarkdown Module", allow_download = TRUE, pre_output = NULL, post_output = NULL, - transformators = list()) { + transformators = list(), + extra_transform = list()) { message("Initializing tm_rmarkdown") # Start of assertions @@ -89,7 +124,7 @@ tm_rmarkdown <- function(label = "RMarkdown Module", ans <- module( label = label, server = srv_rmarkdown, - server_args = list(rmd_file = rmd_file, allow_download = allow_download), + server_args = list(rmd_file = rmd_file, allow_download = allow_download, extra_transform = extra_transform), ui = ui_rmarkdown, ui_args = args, transformators = transformators, @@ -100,7 +135,7 @@ tm_rmarkdown <- function(label = "RMarkdown Module", } # UI function for the rmarkdown module -ui_rmarkdown <- function(id, rmd_file, allow_download, ...) { +ui_rmarkdown <- function(id, rmd_file, allow_download, extra_transform, ...) { args <- list(...) ns <- NS(id) @@ -117,8 +152,8 @@ ui_rmarkdown <- function(id, rmd_file, allow_download, ...) { sprintf("Download '%s'", basename(rmd_file)), class = "btn-primary btn-sm" ) - } - + }, + ui_transform_teal_data(ns("extra_transform"), transformators = extra_transform) ), tags$hr(), uiOutput(ns("rmd_output")) @@ -130,7 +165,7 @@ ui_rmarkdown <- function(id, rmd_file, allow_download, ...) { } # Server function for the rmarkdown module -srv_rmarkdown <- function(id, data, rmd_file, allow_download) { +srv_rmarkdown <- function(id, data, rmd_file, allow_download, decorators) { checkmate::assert_class(data, "reactive") checkmate::assert_class(isolate(data()), "teal_data") moduleServer(id, function(input, output, session) { @@ -142,7 +177,7 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download) { ) } - q_r <- reactive({ + pre_decorated_q_r <- reactive({ data_q <- req(data()) teal.reporter::teal_card(data_q) <- c( teal.reporter::teal_card(data_q), @@ -157,9 +192,14 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download) { ) }) + q_r <- data_with_output_decorated <- teal::srv_transform_teal_data( + "extra_transform", + data = pre_decorated_q_r, + transformators = extra_transform + ) + rendered_path_r <- reactive({ datasets <- req(q_r()) # Ensure data is available - temp_dir <- tempdir() temp_rmd <- tempfile(tmpdir = temp_dir, fileext = ".Rmd") temp_html <- tempfile(tmpdir = temp_dir, fileext = ".md") @@ -197,22 +237,25 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download) { output$rmd_output <- renderUI(rendered_html_r()) reactive({ - out_data <- eval_code( - q_r(), - paste( - sep = "\n", - sprintf("## R Markdown contents are generated from file, please download it from the module UI."), - sprintf("# rmarkdown::render(%s, params = rmd_data)", shQuote(basename(rmd_file), type = "cmd")) - ) - ) + out_data <- q_r() - out_data@verified <- FALSE + if (allow_download) { + out_data <- eval_code( + q_r(), + paste( + sep = "\n", + sprintf("## R Markdown contents are generated from file, please download it from the module UI."), + sprintf("# rmarkdown::render(%s, params = rmd_data)", shQuote(basename(rmd_file), type = "cmd")) + ) + ) + out_data@verified <- FALSE # manual change verified status as code is being injected + } teal.reporter::teal_card(out_data) <- c( teal.reporter::teal_card(out_data), rendered_html_r() ) out_data - }) - }) + }) +}) } From a279ec07cb54eaa79dbd7478726e923207b6bbf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 6 Nov 2025 12:57:56 +0000 Subject: [PATCH 08/22] fix: minor fix in names --- R/tm_rmarkdown.R | 6 +++--- inst/sample_files/test.Rmd | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 inst/sample_files/test.Rmd diff --git a/R/tm_rmarkdown.R b/R/tm_rmarkdown.R index 12cb55e72..a54d36621 100644 --- a/R/tm_rmarkdown.R +++ b/R/tm_rmarkdown.R @@ -64,7 +64,7 @@ #' } #' #' @examples -#' static_decorator <- teal_transform_module( +#' nrow_transform <- teal_transform_module( #' label = "N Rows selector", #' ui = function(id) { #' ns <- NS(id) @@ -91,7 +91,7 @@ #' label = "RMarkdown Module", #' rmd_file = "test.Rmd", #' allow_download = FALSE, -#' decorators = list(static_decorator) +#' extra_transform = list(nrow_transform) #' ) #' ) #' ) |> shiny::runApp() @@ -165,7 +165,7 @@ ui_rmarkdown <- function(id, rmd_file, allow_download, extra_transform, ...) { } # Server function for the rmarkdown module -srv_rmarkdown <- function(id, data, rmd_file, allow_download, decorators) { +srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) { checkmate::assert_class(data, "reactive") checkmate::assert_class(isolate(data()), "teal_data") moduleServer(id, function(input, output, session) { diff --git a/inst/sample_files/test.Rmd b/inst/sample_files/test.Rmd new file mode 100644 index 000000000..958ac8f65 --- /dev/null +++ b/inst/sample_files/test.Rmd @@ -0,0 +1,34 @@ +--- +title: "Test R Markdown" +output: html_document +params: + CO2: NULL + yada: 42 + n_rows: NULL +--- + +This is an example of an R markdown file with an inline r execution that gives the current date: `r Sys.Date()` + +Code chunk that performs a simple calculation (`1+1`) + +```{r} +1+1 +``` + +Code chunk that shows the structure of the params object + +```{r} +lapply(params, class) +``` + +Code chunk that shows the summary of the first `n_rows` of the `CO2` dataset if it is provided + +```{r} +summary(head(params$CO2, n = params$n_rows)) +``` + +Code chunk that plots the first `n_rows` of the `CO2` dataset if it is provided + +```{r, eval = !is.null(params$CO2)} +plot(head(params$CO2, n = params$n_rows)) +``` \ No newline at end of file From 611acf52ba8c3573f70c6ce59ec7d8bee98a5119 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 13:01:14 +0000 Subject: [PATCH 09/22] [skip style] [skip vbump] Restyle files --- R/tm_rmarkdown.R | 58 +++++++++++++++++++++----------------- inst/sample_files/test.Rmd | 4 +-- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/R/tm_rmarkdown.R b/R/tm_rmarkdown.R index a54d36621..f9ff28993 100644 --- a/R/tm_rmarkdown.R +++ b/R/tm_rmarkdown.R @@ -76,9 +76,12 @@ #' moduleServer(id, function(input, output, session) { #' reactive({ #' req(data()) -#' within(data(), { -#' rmd_data$n_rows <- n_rows_value -#' }, n_rows_value = input$n_rows) +#' within(data(), +#' { +#' rmd_data$n_rows <- n_rows_value +#' }, +#' n_rows_value = input$n_rows +#' ) #' }) #' }) #' } @@ -153,7 +156,7 @@ ui_rmarkdown <- function(id, rmd_file, allow_download, extra_transform, ...) { class = "btn-primary btn-sm" ) }, - ui_transform_teal_data(ns("extra_transform"), transformators = extra_transform) + ui_transform_teal_data(ns("extra_transform"), transformators = extra_transform) ), tags$hr(), uiOutput(ns("rmd_output")) @@ -199,31 +202,34 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) { ) rendered_path_r <- reactive({ - datasets <- req(q_r()) # Ensure data is available + datasets <- req(q_r()) # Ensure data is available temp_dir <- tempdir() temp_rmd <- tempfile(tmpdir = temp_dir, fileext = ".Rmd") temp_html <- tempfile(tmpdir = temp_dir, fileext = ".md") file.copy(rmd_file, temp_rmd) # Use a copy of the Rmd file to avoid modifying the original - tryCatch({ - rmarkdown::render( - temp_rmd, - output_format = rmarkdown::md_document( - variant = "gfm", - toc = TRUE, - preserve_yaml = TRUE - ), - output_file = temp_html, - params = datasets[["rmd_data"]], - envir = new.env(parent = globalenv()), - quiet = TRUE, - runtime = "static" - ) - temp_html - }, error = function(e) { - warning("Error rendering RMD file: ", e$message) # verbose error in logs - e - }) + tryCatch( + { + rmarkdown::render( + temp_rmd, + output_format = rmarkdown::md_document( + variant = "gfm", + toc = TRUE, + preserve_yaml = TRUE + ), + output_file = temp_html, + params = datasets[["rmd_data"]], + envir = new.env(parent = globalenv()), + quiet = TRUE, + runtime = "static" + ) + temp_html + }, + error = function(e) { + warning("Error rendering RMD file: ", e$message) # verbose error in logs + e + } + ) }) rendered_html_r <- reactive({ @@ -256,6 +262,6 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) { rendered_html_r() ) out_data - }) -}) + }) + }) } diff --git a/inst/sample_files/test.Rmd b/inst/sample_files/test.Rmd index 958ac8f65..7e293f07e 100644 --- a/inst/sample_files/test.Rmd +++ b/inst/sample_files/test.Rmd @@ -12,7 +12,7 @@ This is an example of an R markdown file with an inline r execution that gives t Code chunk that performs a simple calculation (`1+1`) ```{r} -1+1 +1 + 1 ``` Code chunk that shows the structure of the params object @@ -31,4 +31,4 @@ Code chunk that plots the first `n_rows` of the `CO2` dataset if it is provided ```{r, eval = !is.null(params$CO2)} plot(head(params$CO2, n = params$n_rows)) -``` \ No newline at end of file +``` From 1fa0d8b9e5e19dccd94437e809e47614bf3cf460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 6 Nov 2025 13:03:24 +0000 Subject: [PATCH 10/22] chore: remove extra parameter --- inst/sample_files/test.Rmd | 1 - 1 file changed, 1 deletion(-) diff --git a/inst/sample_files/test.Rmd b/inst/sample_files/test.Rmd index 7e293f07e..196de2bf7 100644 --- a/inst/sample_files/test.Rmd +++ b/inst/sample_files/test.Rmd @@ -3,7 +3,6 @@ title: "Test R Markdown" output: html_document params: CO2: NULL - yada: 42 n_rows: NULL --- From 309c5ac219f08b60c65e18115bbe629fdb8a3a3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 6 Nov 2025 13:06:54 +0000 Subject: [PATCH 11/22] docs: update documentation --- R/tm_rmarkdown.R | 4 ++-- man/tm_rmarkdown.Rd | 46 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/R/tm_rmarkdown.R b/R/tm_rmarkdown.R index f9ff28993..9d6fd28eb 100644 --- a/R/tm_rmarkdown.R +++ b/R/tm_rmarkdown.R @@ -55,7 +55,7 @@ #' modules = modules( #' tm_rmarkdown( #' label = "RMarkdown Module", -#' rmd_file = "test.Rmd" +#' rmd_file = system.file(file.path("sample_files", "test.Rmd"), package = "teal.modules.general") #' ) #' ) #' ) @@ -92,7 +92,7 @@ #' modules = modules( #' tm_rmarkdown( #' label = "RMarkdown Module", -#' rmd_file = "test.Rmd", +#' rmd_file = system.file(file.path("sample_files", "test.Rmd"), package = "teal.modules.general"), #' allow_download = FALSE, #' extra_transform = list(nrow_transform) #' ) diff --git a/man/tm_rmarkdown.Rd b/man/tm_rmarkdown.Rd index bf209264f..0cb83b6ff 100644 --- a/man/tm_rmarkdown.Rd +++ b/man/tm_rmarkdown.Rd @@ -11,7 +11,8 @@ tm_rmarkdown( allow_download = TRUE, pre_output = NULL, post_output = NULL, - transformators = list() + transformators = list(), + extra_transform = list() ) } \arguments{ @@ -42,6 +43,9 @@ adding context or further instructions. Elements like \code{shiny::helpText()} a \item{transformators}{(\code{list} of \code{teal_transform_module}) that will be applied to transform module's data input. To learn more check \code{vignette("transform-input-data", package = "teal")}.} + +\item{extra_transform}{(\code{list}) of \code{\link[teal:teal_transform_module]{teal::teal_transform_module()}} that will be added in the module's UI. +This can be used to create interactive inputs that modify the parameters in R Markdown rendering.} } \value{ Object of class \code{teal_module} to be used in \code{teal} applications. @@ -85,7 +89,7 @@ app <- init( modules = modules( tm_rmarkdown( label = "RMarkdown Module", - rmd_file = "test.Rmd" + rmd_file = system.file(file.path("sample_files", "test.Rmd"), package = "teal.modules.general") ) ) ) @@ -93,12 +97,46 @@ if (interactive()) { shinyApp(app$ui, app$server) } +nrow_transform <- teal_transform_module( + label = "N Rows selector", + ui = function(id) { + ns <- NS(id) + tags$div( + numericInput(ns("n_rows"), "Show n rows", value = 5, min = 0, max = 200, step = 5) + ) + }, + server = function(id, data) { + moduleServer(id, function(input, output, session) { + reactive({ + req(data()) + within(data(), + { + rmd_data$n_rows <- n_rows_value + }, + n_rows_value = input$n_rows + ) + }) + }) + } +) + +app <- init( + data = data, + modules = modules( + tm_rmarkdown( + label = "RMarkdown Module", + rmd_file = system.file(file.path("sample_files", "test.Rmd"), package = "teal.modules.general"), + allow_download = FALSE, + extra_transform = list(nrow_transform) + ) + ) +) |> shiny::runApp() } \section{Examples in Shinylive}{ \describe{ \item{example-1}{ - \href{https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqajGIgEwCu1OAGcMAcwpxm1AJQAdCLTIyoBUrQBucAAQAeALS6AZoIgbaJdnN0AVLAFUAokrcQAxLqkQ11XfxQpFC6cAAesKgiSoHBBsZc1AD6sVA2MUEhRroA7rSkABYq7Km4uiBKuroAwgDyAEzxNQ2VzfXAwApgqIy0MCysSQDWcKxdALrjTaJwAI5JIhDsEIxEOex19XKKEAC+OwBWRCrDo6IlmbbZRycjrOc3EKccXZtdZa8teLpdPX0Dzy623cSjQqCaKny7FaqV0AF4AplcK0+EIRKJ4bpUcIxNCIFUqqQYElGP1GEN+Gslq0CbpqFB6HB-AiulgALIsClU3RsgQ4940gmk-hJEy0ESYrrkUSkbAwfhdQU7Ko7Ha0Ey6dgqcjMSw6Gy2Cr43SiIoQVgAQXQ7DBABJBLQynaZowdIwdrslGBduMgA}{Open in Shinylive} - \if{html}{\out{}} + \href{https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqajGIgEwCu1OAGcMAcwpxm1AJQAdCLTIyoBUrQBucAAQAeALS6AZoIgbaJdnN0AVLAFUAokrcQAxLqkQ11XfxQpFC6cAAesKgiSoHBBsZc1AD6sVA2MUEhRroA7rSkABYq7Km4uiBKuroAwgDyAEzxNQ2VzfXAwApgqIy0MCysSQDWcKxdALrjTaJwAI5JIhDsEIxEOex19XKKEAC+OwBWRCrDo6IlmbbZRycjrOc3EKccXZtdZa8teLpdPX0Dzy623cSjQqCaKny7FaqV0AF4AplcK0+EIRKJ4bpUcIxNCIFUqqQYElGP1GEN+Gslq0CbpqFB6HB-AiulgALIsClU3RsgQ4940gmk-hJEy0ESY0T3cgwDBikTseVwDCoIIFaFgUSRESi8Vid4-MDkUSkbAwfhAsqqghDKBSTFdRK8PnoyTSWRAwU7Ko7Ha0Ey6dgqcjMSw6Gy2Cr43SiIoQVgAQXQ7DBABJBLQymmZowdIwdrslGBduMgA}{Open in Shinylive} + \if{html}{\out{}} \if{html}{\out{}} } } From 3640c419e50153317982bed1b8da9ad022ba7d32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 6 Nov 2025 13:37:04 +0000 Subject: [PATCH 12/22] pr: copy rmd and setup tempdir only once --- R/tm_rmarkdown.R | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/R/tm_rmarkdown.R b/R/tm_rmarkdown.R index 9d6fd28eb..16398b8d3 100644 --- a/R/tm_rmarkdown.R +++ b/R/tm_rmarkdown.R @@ -201,12 +201,15 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) { transformators = extra_transform ) + temp_dir <- tempdir() + temp_rmd <- tempfile(tmpdir = temp_dir, fileext = ".Rmd") + file.copy(rmd_file, temp_rmd) # Use a copy of the Rmd file to avoid modifying the original + rendered_path_r <- reactive({ datasets <- req(q_r()) # Ensure data is available - temp_dir <- tempdir() - temp_rmd <- tempfile(tmpdir = temp_dir, fileext = ".Rmd") - temp_html <- tempfile(tmpdir = temp_dir, fileext = ".md") - file.copy(rmd_file, temp_rmd) # Use a copy of the Rmd file to avoid modifying the original + + temp_output <- tempfile(tmpdir = temp_dir, fileext = ".md") + tryCatch( { @@ -217,13 +220,13 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) { toc = TRUE, preserve_yaml = TRUE ), - output_file = temp_html, + output_file = temp_output, params = datasets[["rmd_data"]], envir = new.env(parent = globalenv()), quiet = TRUE, runtime = "static" ) - temp_html + temp_output }, error = function(e) { warning("Error rendering RMD file: ", e$message) # verbose error in logs From 3b0c5d4b0642df47904bfb31a5604cf16e254d39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 6 Nov 2025 13:40:52 +0000 Subject: [PATCH 13/22] chore: use the output of render (file path) instead of explicit name --- R/tm_rmarkdown.R | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/R/tm_rmarkdown.R b/R/tm_rmarkdown.R index 16398b8d3..8300da93b 100644 --- a/R/tm_rmarkdown.R +++ b/R/tm_rmarkdown.R @@ -207,10 +207,6 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) { rendered_path_r <- reactive({ datasets <- req(q_r()) # Ensure data is available - - temp_output <- tempfile(tmpdir = temp_dir, fileext = ".md") - - tryCatch( { rmarkdown::render( @@ -220,13 +216,11 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) { toc = TRUE, preserve_yaml = TRUE ), - output_file = temp_output, params = datasets[["rmd_data"]], envir = new.env(parent = globalenv()), quiet = TRUE, runtime = "static" ) - temp_output }, error = function(e) { warning("Error rendering RMD file: ", e$message) # verbose error in logs @@ -240,6 +234,7 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) { validate( need(inherits(output_path, "character"), "Error rendering RMD file. Please contact the app developer.") ) + print(output_path) htmltools::includeMarkdown(output_path) }) From 14cdf42ad96a03398a3f8e74d42de140160bc935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 6 Nov 2025 14:22:19 +0000 Subject: [PATCH 14/22] pr: rename rmd_data to params for consistency --- R/tm_rmarkdown.R | 9 ++++----- man/tm_rmarkdown.Rd | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/R/tm_rmarkdown.R b/R/tm_rmarkdown.R index 8300da93b..416f710fd 100644 --- a/R/tm_rmarkdown.R +++ b/R/tm_rmarkdown.R @@ -78,7 +78,7 @@ #' req(data()) #' within(data(), #' { -#' rmd_data$n_rows <- n_rows_value +#' params$n_rows <- n_rows_value #' }, #' n_rows_value = input$n_rows #' ) @@ -189,7 +189,7 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) { eval_code( data_q, sprintf( - "rmd_data <- list(%s)", + "params <- list(%s)", toString(sprintf("%1$s = %1$s", sapply(names(data_q), as.name))) ) ) @@ -216,7 +216,7 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) { toc = TRUE, preserve_yaml = TRUE ), - params = datasets[["rmd_data"]], + params = datasets[["params"]], envir = new.env(parent = globalenv()), quiet = TRUE, runtime = "static" @@ -234,7 +234,6 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) { validate( need(inherits(output_path, "character"), "Error rendering RMD file. Please contact the app developer.") ) - print(output_path) htmltools::includeMarkdown(output_path) }) @@ -249,7 +248,7 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) { paste( sep = "\n", sprintf("## R Markdown contents are generated from file, please download it from the module UI."), - sprintf("# rmarkdown::render(%s, params = rmd_data)", shQuote(basename(rmd_file), type = "cmd")) + sprintf("# rmarkdown::render(%s, params = params)", shQuote(basename(rmd_file), type = "cmd")) ) ) out_data@verified <- FALSE # manual change verified status as code is being injected diff --git a/man/tm_rmarkdown.Rd b/man/tm_rmarkdown.Rd index 0cb83b6ff..bdfe7cd3d 100644 --- a/man/tm_rmarkdown.Rd +++ b/man/tm_rmarkdown.Rd @@ -111,7 +111,7 @@ nrow_transform <- teal_transform_module( req(data()) within(data(), { - rmd_data$n_rows <- n_rows_value + params$n_rows <- n_rows_value }, n_rows_value = input$n_rows ) From c563c71f5b6db97501a2ef2fbc52769c1d255839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Fri, 7 Nov 2025 18:06:13 +0000 Subject: [PATCH 15/22] pr: address main points of review from @gogonzo --- NAMESPACE | 1 + R/tm_rmarkdown.R | 115 +++++++++++++++++++++++++++++-------- inst/sample_files/test.Rmd | 19 +++--- 3 files changed, 102 insertions(+), 33 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index e38348c37..e6c66c906 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -8,6 +8,7 @@ S3method(create_sparklines,default) S3method(create_sparklines,factor) S3method(create_sparklines,logical) S3method(create_sparklines,numeric) +S3method(tools::toHTML,markdown_teal_internal) export(add_facet_labels) export(get_scatterplotmatrix_stats) export(tm_a_pca) diff --git a/R/tm_rmarkdown.R b/R/tm_rmarkdown.R index 416f710fd..988744576 100644 --- a/R/tm_rmarkdown.R +++ b/R/tm_rmarkdown.R @@ -78,7 +78,7 @@ #' req(data()) #' within(data(), #' { -#' params$n_rows <- n_rows_value +#' n_rows <- n_rows_value #' }, #' n_rows_value = input$n_rows #' ) @@ -172,27 +172,13 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) { checkmate::assert_class(data, "reactive") checkmate::assert_class(isolate(data()), "teal_data") moduleServer(id, function(input, output, session) { - if (allow_download) { - output$download_rmd <- downloadHandler( - filename = function() basename(rmd_file), - content = function(file) file.copy(rmd_file, file), - contentType = "text/plain" - ) - } - pre_decorated_q_r <- reactive({ data_q <- req(data()) teal.reporter::teal_card(data_q) <- c( teal.reporter::teal_card(data_q), teal.reporter::teal_card("## Module's output(s)") ) - eval_code( - data_q, - sprintf( - "params <- list(%s)", - toString(sprintf("%1$s = %1$s", sapply(names(data_q), as.name))) - ) - ) + data_q }) q_r <- data_with_output_decorated <- teal::srv_transform_teal_data( @@ -201,6 +187,32 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) { transformators = extra_transform ) + if (allow_download) { + output$download_rmd <- downloadHandler( + filename = function() basename(rmd_file), + content = function(file) { + lines <- readLines(rmd_file) + + # find the end of the YAML header or start of the file + # and insert the contents of teal.code::get_code(q_r()) + yaml_end <- which(lines == "---")[2] + insert_pos <- if (!is.na(yaml_end)) yaml_end else 0 + note_lines <- c( + "", + "```{r}", + "# The following code chunk was automatically added by the teal markdown module", + "# It shows how to generate the data used in this report", + teal.code::get_code(q_r()), + "```", + "" + ) + lines <- append(lines, note_lines, after = insert_pos) + writeLines(lines, con = file) + }, + contentType = "text/plain" + ) + } + temp_dir <- tempdir() temp_rmd <- tempfile(tmpdir = temp_dir, fileext = ".Rmd") file.copy(rmd_file, temp_rmd) # Use a copy of the Rmd file to avoid modifying the original @@ -212,12 +224,11 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) { rmarkdown::render( temp_rmd, output_format = rmarkdown::md_document( - variant = "gfm", - toc = TRUE, - preserve_yaml = TRUE + variant = "markdown", + standalone = TRUE, + dev = "png" ), - params = datasets[["params"]], - envir = new.env(parent = globalenv()), + envir = environment(datasets), quiet = TRUE, runtime = "static" ) @@ -254,11 +265,69 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) { out_data@verified <- FALSE # manual change verified status as code is being injected } + report_doc <- .markdown_internal(rendered_path_r(), temp_dir, rendered_html_r()) teal.reporter::teal_card(out_data) <- c( - teal.reporter::teal_card(out_data), - rendered_html_r() + teal.reporter::teal_card(out_data), report_doc ) out_data }) }) } + +#' @exportS3Method tools::toHTML +toHTML.markdown_teal_internal <- function(block, ...) { + cached_html <- attr(block, "cached_html", exact = TRUE) + if (!is.null(cached_html)) { + return(cached_html) + } + NextMethod(unclass(block), ...) +} + +#' @method to_rmd markdown_internal +to_rmd.markdown_teal_internal <- function(block, figures_dir = "figures", include_chunk_output = TRUE, ...) { + images_base64 <- attr(block, "images_base64", exact = TRUE) + for (img_path in names(images_base64)) { + img_data <- sub("^data:.*;base64,", "", images_base64[[img_path]]) + img_tag_pattern <- paste0("!\\[.*?\\]\\(", img_path, "\\)") + dir.create(figures_dir, showWarnings = FALSE, recursive = TRUE) + path <- file.path( + figures_dir, + sprintf( + "markdown_img_%s.%s", + substr(rlang::hash(img_data), 1, 6), + sprintf("%s", tools::file_ext(img_path)) + ) + ) + writeBin(base64enc::base64decode(img_data), path) + replacement_tag <- sprintf("![](%s)", path) + block <- gsub(img_tag_pattern, replacement_tag, block, fixed = FALSE) + } + NextMethod(unclass(block), ...) +} + +.markdown_internal <- function(markdown_file, temp_dir, rendered_html) { + # Read the markdown file + lines <- readLines(markdown_file) + images_base64 <- list() + + # Extract images based on pattern ![](.*) + img_pattern <- "!\\[.*?\\]\\((.*?)\\)" + img_tags <- unlist(regmatches(lines, gregexpr(img_pattern, lines))) + for (ix in seq_along(img_tags)) { + img_tag <- img_tags[[ix]] + img_path <- gsub("!\\[.*?\\]\\((.*?)\\)", "\\1", img_tag) + full_img_path <- file.path(temp_dir, img_path) + if (file.exists(full_img_path)) { + img_data <- knitr::image_uri(full_img_path) + images_base64[[img_path]] <- img_data + } + } + + # Create new custom structure with contents and images in base64 as attribute + structure( + lines, + class = c("markdown_teal_internal", "character"), + images_base64 = images_base64, + cached_html = rendered_html + ) +} diff --git a/inst/sample_files/test.Rmd b/inst/sample_files/test.Rmd index 196de2bf7..3fb2760c8 100644 --- a/inst/sample_files/test.Rmd +++ b/inst/sample_files/test.Rmd @@ -1,11 +1,14 @@ --- title: "Test R Markdown" output: html_document -params: - CO2: NULL - n_rows: NULL --- +```{r, eval=!exists(".raw_data"), include=FALSE} +# Set your local data here. note that when used in teal the `data` must be available to the module +CO2 <- datasets::CO2 +n_rows <- Inf +``` + This is an example of an R markdown file with an inline r execution that gives the current date: `r Sys.Date()` Code chunk that performs a simple calculation (`1+1`) @@ -16,18 +19,14 @@ Code chunk that performs a simple calculation (`1+1`) Code chunk that shows the structure of the params object -```{r} -lapply(params, class) -``` - Code chunk that shows the summary of the first `n_rows` of the `CO2` dataset if it is provided ```{r} -summary(head(params$CO2, n = params$n_rows)) +summary(head(CO2, n = n_rows)) ``` Code chunk that plots the first `n_rows` of the `CO2` dataset if it is provided -```{r, eval = !is.null(params$CO2)} -plot(head(params$CO2, n = params$n_rows)) +```{r} +plot(head(CO2, n = n_rows)) ``` From 8c1acaa1376f88364fe3b1d0160c7eef6018c54a Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 18:13:50 +0000 Subject: [PATCH 16/22] [skip roxygen] [skip vbump] Roxygen Man Pages Auto Update --- man/tm_rmarkdown.Rd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/tm_rmarkdown.Rd b/man/tm_rmarkdown.Rd index bdfe7cd3d..5fdc25030 100644 --- a/man/tm_rmarkdown.Rd +++ b/man/tm_rmarkdown.Rd @@ -111,7 +111,7 @@ nrow_transform <- teal_transform_module( req(data()) within(data(), { - params$n_rows <- n_rows_value + n_rows <- n_rows_value }, n_rows_value = input$n_rows ) From d05971a41f4864733d77e7ba4f2bcd6cdfc60ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Fri, 7 Nov 2025 18:20:25 +0000 Subject: [PATCH 17/22] fix: adds baseenc for now in description --- DESCRIPTION | 1 + 1 file changed, 1 insertion(+) diff --git a/DESCRIPTION b/DESCRIPTION index 55a1272c9..5cef2eda3 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -29,6 +29,7 @@ Depends: teal (>= 1.0.0.9003), teal.transform (>= 0.7.0) Imports: + base64enc (>= 0.1-3), bslib (>= 0.8.0), checkmate (>= 2.1.0), colourpicker (>= 1.3.0), From 59736a716ffae2e97a79c4d5e84c2a0fa7f3e421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Wed, 12 Nov 2025 13:51:06 +0000 Subject: [PATCH 18/22] pr: apply feedback from meeting with @gogonzo --- NAMESPACE | 3 +- R/tm_rmarkdown.R | 138 ++++++++++++++++++++--------------------------- 2 files changed, 61 insertions(+), 80 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index e6c66c906..10d634628 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -8,7 +8,8 @@ S3method(create_sparklines,default) S3method(create_sparklines,factor) S3method(create_sparklines,logical) S3method(create_sparklines,numeric) -S3method(tools::toHTML,markdown_teal_internal) +S3method(teal.reporter::to_rmd,markdown_internal) +S3method(tools::toHTML,markdown_internal) export(add_facet_labels) export(get_scatterplotmatrix_stats) export(tm_a_pca) diff --git a/R/tm_rmarkdown.R b/R/tm_rmarkdown.R index 988744576..bbaa9096f 100644 --- a/R/tm_rmarkdown.R +++ b/R/tm_rmarkdown.R @@ -24,8 +24,8 @@ #' @inheritParams teal::module #' @inheritParams shared_params #' -#' @param rmd_file (`character`) Path to the R Markdown file to be rendered. -#' The file must be accessible from the Shiny app environment. +#' @param rmd_content (`character`) Content of the R Markdown file to be rendered. +#' This can be the value of `readLines("path/to/file.Rmd")`. #' @param allow_download (`logical`) whether to allow downloading of the R Markdown file. #' Defaults to `TRUE`. #' @param extra_transform (`list`) of [teal::teal_transform_module()] that will be added in the module's UI. @@ -55,7 +55,7 @@ #' modules = modules( #' tm_rmarkdown( #' label = "RMarkdown Module", -#' rmd_file = system.file(file.path("sample_files", "test.Rmd"), package = "teal.modules.general") +#' rmd_content = readLines(system.file(file.path("sample_files", "test.Rmd"), package = "teal.modules.general")) #' ) #' ) #' ) @@ -92,7 +92,7 @@ #' modules = modules( #' tm_rmarkdown( #' label = "RMarkdown Module", -#' rmd_file = system.file(file.path("sample_files", "test.Rmd"), package = "teal.modules.general"), +#' rmd_content = readLines(system.file(file.path("sample_files", "test.Rmd"), package = "teal.modules.general")), #' allow_download = FALSE, #' extra_transform = list(nrow_transform) #' ) @@ -101,7 +101,7 @@ #' @export #' tm_rmarkdown <- function(label = "RMarkdown Module", - rmd_file, + rmd_content, datanames = "all", allow_download = TRUE, pre_output = NULL, @@ -113,7 +113,7 @@ tm_rmarkdown <- function(label = "RMarkdown Module", # Start of assertions checkmate::assert_string(label) - checkmate::assert_file(rmd_file, access = "r") + checkmate::assert_character(rmd_content) checkmate::assert_flag(allow_download) checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) @@ -127,32 +127,29 @@ tm_rmarkdown <- function(label = "RMarkdown Module", ans <- module( label = label, server = srv_rmarkdown, - server_args = list(rmd_file = rmd_file, allow_download = allow_download, extra_transform = extra_transform), + server_args = list(rmd_content = rmd_content, allow_download = allow_download, extra_transform = extra_transform), ui = ui_rmarkdown, ui_args = args, transformators = transformators, datanames = datanames ) # attr(ans, "teal_bookmarkable") <- TRUE - ans + disable_src(ans) } # UI function for the rmarkdown module -ui_rmarkdown <- function(id, rmd_file, allow_download, extra_transform, ...) { +ui_rmarkdown <- function(id, rmd_content, allow_download, extra_transform, ...) { args <- list(...) ns <- NS(id) teal.widgets::standard_layout( output = teal.widgets::white_small_well( tags$div( - tags$h4( - "Rendered report from: ", - tags$code(basename(rmd_file)) - ), + tags$h4("Rendered report from Rmd"), if (allow_download) { downloadButton( ns("download_rmd"), - sprintf("Download '%s'", basename(rmd_file)), + sprintf("Download R Markdown file"), class = "btn-primary btn-sm" ) }, @@ -168,7 +165,7 @@ ui_rmarkdown <- function(id, rmd_file, allow_download, extra_transform, ...) { } # Server function for the rmarkdown module -srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) { +srv_rmarkdown <- function(id, data, rmd_content, allow_download, extra_transform) { checkmate::assert_class(data, "reactive") checkmate::assert_class(isolate(data()), "teal_data") moduleServer(id, function(input, output, session) { @@ -189,36 +186,52 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) { if (allow_download) { output$download_rmd <- downloadHandler( - filename = function() basename(rmd_file), + filename = function() "teal_module.Rmd", # TODO: find a better name content = function(file) { - lines <- readLines(rmd_file) - # find the end of the YAML header or start of the file # and insert the contents of teal.code::get_code(q_r()) - yaml_end <- which(lines == "---")[2] + yaml_end <- which(rmd_content == "---")[2] insert_pos <- if (!is.na(yaml_end)) yaml_end else 0 note_lines <- c( + "", + "### Pre-processing data", + "", + "The following code chunk was automatically added by the teal markdown module.", + "It shows how to generate the data used in this report.", "", "```{r}", - "# The following code chunk was automatically added by the teal markdown module", - "# It shows how to generate the data used in this report", teal.code::get_code(q_r()), "```", "" ) - lines <- append(lines, note_lines, after = insert_pos) - writeLines(lines, con = file) + rmd_content <- append(rmd_content, note_lines, after = insert_pos) + writeLines(rmd_content, con = file) }, contentType = "text/plain" ) } - temp_dir <- tempdir() - temp_rmd <- tempfile(tmpdir = temp_dir, fileext = ".Rmd") - file.copy(rmd_file, temp_rmd) # Use a copy of the Rmd file to avoid modifying the original + clean_up_r <- shiny::reactiveVal(list()) + # Can only clean on sessionEnded as temporary files are needed for the reporter + # during session + onSessionEnded(function() { + logger::log_debug("srv_rmarkdown: cleaning up temporary folders.") + lapply(shiny::isolate(clean_up_r()), function(f) f()) + }, session) rendered_path_r <- reactive({ datasets <- req(q_r()) # Ensure data is available + + temp_dir <- tempfile(pattern = "rmd_") + dir.create(temp_dir, showWarnings = FALSE, recursive = TRUE) + temp_rmd <- tempfile(pattern = "rmarkdown_module-", tmpdir = temp_dir, fileext = ".Rmd") + # Schedule cleanup of temp files when reactive is re-executed + shiny::isolate({ + old_clean_up <- clean_up_r() + clean_up_r(c(old_clean_up, function() unlink(temp_dir, recursive = TRUE))) + }) + writeLines(rmd_content, con = temp_rmd) + tryCatch( { rmarkdown::render( @@ -252,20 +265,7 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) { reactive({ out_data <- q_r() - - if (allow_download) { - out_data <- eval_code( - q_r(), - paste( - sep = "\n", - sprintf("## R Markdown contents are generated from file, please download it from the module UI."), - sprintf("# rmarkdown::render(%s, params = params)", shQuote(basename(rmd_file), type = "cmd")) - ) - ) - out_data@verified <- FALSE # manual change verified status as code is being injected - } - - report_doc <- .markdown_internal(rendered_path_r(), temp_dir, rendered_html_r()) + report_doc <- .markdown_internal(rendered_path_r(), rendered_html_r()) teal.reporter::teal_card(out_data) <- c( teal.reporter::teal_card(out_data), report_doc ) @@ -275,7 +275,7 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) { } #' @exportS3Method tools::toHTML -toHTML.markdown_teal_internal <- function(block, ...) { +toHTML.markdown_internal <- function(block, ...) { cached_html <- attr(block, "cached_html", exact = TRUE) if (!is.null(cached_html)) { return(cached_html) @@ -284,50 +284,30 @@ toHTML.markdown_teal_internal <- function(block, ...) { } #' @method to_rmd markdown_internal -to_rmd.markdown_teal_internal <- function(block, figures_dir = "figures", include_chunk_output = TRUE, ...) { - images_base64 <- attr(block, "images_base64", exact = TRUE) - for (img_path in names(images_base64)) { - img_data <- sub("^data:.*;base64,", "", images_base64[[img_path]]) - img_tag_pattern <- paste0("!\\[.*?\\]\\(", img_path, "\\)") - dir.create(figures_dir, showWarnings = FALSE, recursive = TRUE) - path <- file.path( - figures_dir, - sprintf( - "markdown_img_%s.%s", - substr(rlang::hash(img_data), 1, 6), - sprintf("%s", tools::file_ext(img_path)) - ) - ) - writeBin(base64enc::base64decode(img_data), path) - replacement_tag <- sprintf("![](%s)", path) - block <- gsub(img_tag_pattern, replacement_tag, block, fixed = FALSE) - } +#' @exportS3Method teal.reporter::to_rmd +to_rmd.markdown_internal <- function(block, figures_dir = "figures", include_chunk_output = TRUE, ...) { + old_base_path <- attr(block, "old_base_path", exact = TRUE) + parent_path <- attr(block, "parent_path", exact = TRUE) + new_base_path <- file.path(figures_dir, old_base_path) + + # Copy figures from old path to new location + dir.create(figures_dir, showWarnings = FALSE, recursive = TRUE) + file.copy(file.path(parent_path, old_base_path), figures_dir, recursive = TRUE) + + # Change the image paths in the markdown content + block <- gsub(pattern = old_base_path, replacement = new_base_path, x = block, fixed = TRUE) NextMethod(unclass(block), ...) } -.markdown_internal <- function(markdown_file, temp_dir, rendered_html) { - # Read the markdown file - lines <- readLines(markdown_file) - images_base64 <- list() - - # Extract images based on pattern ![](.*) - img_pattern <- "!\\[.*?\\]\\((.*?)\\)" - img_tags <- unlist(regmatches(lines, gregexpr(img_pattern, lines))) - for (ix in seq_along(img_tags)) { - img_tag <- img_tags[[ix]] - img_path <- gsub("!\\[.*?\\]\\((.*?)\\)", "\\1", img_tag) - full_img_path <- file.path(temp_dir, img_path) - if (file.exists(full_img_path)) { - img_data <- knitr::image_uri(full_img_path) - images_base64[[img_path]] <- img_data - } - } +.markdown_internal <- function(markdown_file, rendered_html) { + base_file <- basename(markdown_file) # Create new custom structure with contents and images in base64 as attribute structure( - lines, - class = c("markdown_teal_internal", "character"), - images_base64 = images_base64, + readLines(markdown_file), + class = c("markdown_internal", "character"), + parent_path = dirname(markdown_file), + old_base_path = sprintf("%s_files/", tools::file_path_sans_ext(base_file)), cached_html = rendered_html ) } From 5dc5747504198942c3a12c545fac100e82a1e658 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 12 Nov 2025 13:58:45 +0000 Subject: [PATCH 19/22] [skip roxygen] [skip vbump] Roxygen Man Pages Auto Update --- man/tm_rmarkdown.Rd | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/man/tm_rmarkdown.Rd b/man/tm_rmarkdown.Rd index 5fdc25030..4d8a51da5 100644 --- a/man/tm_rmarkdown.Rd +++ b/man/tm_rmarkdown.Rd @@ -6,7 +6,7 @@ \usage{ tm_rmarkdown( label = "RMarkdown Module", - rmd_file, + rmd_content, datanames = "all", allow_download = TRUE, pre_output = NULL, @@ -19,8 +19,8 @@ tm_rmarkdown( \item{label}{(\code{character(1)}) Label shown in the navigation item for the module or module group. For \code{modules()} defaults to \code{"root"}. See \code{Details}.} -\item{rmd_file}{(\code{character}) Path to the R Markdown file to be rendered. -The file must be accessible from the Shiny app environment.} +\item{rmd_content}{(\code{character}) Content of the R Markdown file to be rendered. +This can be the value of \code{readLines("path/to/file.Rmd")}.} \item{datanames}{(\code{character}) Names of the datasets relevant to the item. There are 2 reserved values that have specific behaviors: @@ -89,7 +89,7 @@ app <- init( modules = modules( tm_rmarkdown( label = "RMarkdown Module", - rmd_file = system.file(file.path("sample_files", "test.Rmd"), package = "teal.modules.general") + rmd_content = readLines(system.file(file.path("sample_files", "test.Rmd"), package = "teal.modules.general")) ) ) ) @@ -125,7 +125,7 @@ app <- init( modules = modules( tm_rmarkdown( label = "RMarkdown Module", - rmd_file = system.file(file.path("sample_files", "test.Rmd"), package = "teal.modules.general"), + rmd_content = readLines(system.file(file.path("sample_files", "test.Rmd"), package = "teal.modules.general")), allow_download = FALSE, extra_transform = list(nrow_transform) ) @@ -135,8 +135,8 @@ app <- init( \section{Examples in Shinylive}{ \describe{ \item{example-1}{ - \href{https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqajGIgEwCu1OAGcMAcwpxm1AJQAdCLTIyoBUrQBucAAQAeALS6AZoIgbaJdnN0AVLAFUAokrcQAxLqkQ11XfxQpFC6cAAesKgiSoHBBsZc1AD6sVA2MUEhRroA7rSkABYq7Km4uiBKuroAwgDyAEzxNQ2VzfXAwApgqIy0MCysSQDWcKxdALrjTaJwAI5JIhDsEIxEOex19XKKEAC+OwBWRCrDo6IlmbbZRycjrOc3EKccXZtdZa8teLpdPX0Dzy623cSjQqCaKny7FaqV0AF4AplcK0+EIRKJ4bpUcIxNCIFUqqQYElGP1GEN+Gslq0CbpqFB6HB-AiulgALIsClU3RsgQ4940gmk-hJEy0ESY0T3cgwDBikTseVwDCoIIFaFgUSRESi8Vid4-MDkUSkbAwfhAsqqghDKBSTFdRK8PnoyTSWRAwU7Ko7Ha0Ey6dgqcjMSw6Gy2Cr43SiIoQVgAQXQ7DBABJBLQymmZowdIwdrslGBduMgA}{Open in Shinylive} - \if{html}{\out{}} + \href{https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqajGIgEwCu1OAGcMAcwpxm1AJQAdCLTIyoBUrQBucAAQAeALS6AZoIgbaJdnN0AVLAFUAokrcQAxLqkQ11XfxQpFC6cAAesKgiSoHBBsZc1AD6sVA2MUEhRroA7rSkABYq7Km4uiBKuroAwgDyAEzxNQ2VzfXAwApgqIy0MCysSQDWcKxdALrjTaJwAI5JIhDsEIxEOex19XKKEAC+OwBWRCrDo6IlmbbZRycjrOc3EKccXZtdZa8teLpdPX0Dzy623cSjQqCaKny7FaqV0AF4AplcK0+EIRKJ4bpUcIxNCIFUqqQYElGP1GEN+Gslq0CbpqFB6HB-AiulgALIsClU3RsgQ4940gmk-hJYiqMiYxhwKD8AAyKlxonu5BgGBMtBE7HVIgwqCCBWhYFEkRESW1YnePzA5FEpGwMH4QLKeoIQygUkxXUSvD56Mk0lkQJ2tODuh2O1oJl07BU5GYlh0NlsFXxulERQgrAAguh2GCACSCWhlAszRg6Rg7XZKMC7cZAA}{Open in Shinylive} + \if{html}{\out{}} \if{html}{\out{}} } } From 6da1e04e64a2213e43ff462835e98ce52b468701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Fri, 14 Nov 2025 11:14:45 +0000 Subject: [PATCH 20/22] docs: improve documentation --- R/tm_rmarkdown.R | 50 ++++++++++++++++++++++++++++++--------------- man/tm_rmarkdown.Rd | 31 +++++++++++++++++++--------- 2 files changed, 54 insertions(+), 27 deletions(-) diff --git a/R/tm_rmarkdown.R b/R/tm_rmarkdown.R index bbaa9096f..d26a9b84a 100644 --- a/R/tm_rmarkdown.R +++ b/R/tm_rmarkdown.R @@ -2,24 +2,36 @@ #' #' Module to render R Markdown files using the data provided in the `teal_data` object. #' -#' The R Markdown file should be designed to accept parameters corresponding to the datasets. -#' See using `params` in R Markdown documentation: -#' [bookdown.org/yihui/rmarkdown/params-use.html](https://bookdown.org/yihui/rmarkdown/params-use.html) +#' The R Markdown file should be designed to accept variables available in the datanames of the module. #' #' For example, if the `teal_data` object contains datasets named "mtcars" and "iris", -#' the R Markdown file can define parameters as follows: -#' ```yaml +#' the R Markdown file can use these as variables as they will be available in the R Markdown environment. +#' +#' The libraries used in the R Markdown file must be available in the deployed shiny +#' app environment. +#' +#' When developing the R Markdown file, the working data can be simulated on a code chunk, +#' which in turn can look for the presence of `.raw_data` object to determine if it is being +#' run inside the `teal` module or not. +#' +#' Example R markdown file: +#' +#' ``````md #' --- #' title: "R Markdown Report" #' output: html_document -#' params: -#' mtcars: NULL -#' iris: NULL #' --- -#' ```` #' -#' The libraries used in the R Markdown file must be available in -#' the Shiny app environment. +#' ```{r eval=!exists(".raw_data")} +#' mtcars <- datasets::mtcars +#' iris <- datasets::iris +#' ``` +#' +#' ```{r} +#' summary(mtcars) |> print() +#' summary(iris) |> print() +#' ``` +#' `````` #' #' @inheritParams teal::module #' @inheritParams shared_params @@ -45,17 +57,23 @@ #' data <- teal_data() #' data <- within(data, { #' CO2 <- CO2 -#' CO2[["primary_key"]] <- seq_len(nrow(CO2)) #' }) -#' join_keys(data) <- join_keys(join_key("CO2", "CO2", "primary_key")) -#' #' #' app <- init( #' data = data, #' modules = modules( #' tm_rmarkdown( #' label = "RMarkdown Module", -#' rmd_content = readLines(system.file(file.path("sample_files", "test.Rmd"), package = "teal.modules.general")) +#' rmd_content = c( +#' "---", +#' "title: \"R Markdown Report\"", +#' "output: html_document", +#' "---", +#' "", +#' "```{r}", +#' "summary(CO2) |> print()", +#' "```" +#' ) #' ) #' ) #' ) @@ -99,7 +117,6 @@ #' ) #' ) |> shiny::runApp() #' @export -#' tm_rmarkdown <- function(label = "RMarkdown Module", rmd_content, datanames = "all", @@ -283,7 +300,6 @@ toHTML.markdown_internal <- function(block, ...) { NextMethod(unclass(block), ...) } -#' @method to_rmd markdown_internal #' @exportS3Method teal.reporter::to_rmd to_rmd.markdown_internal <- function(block, figures_dir = "figures", include_chunk_output = TRUE, ...) { old_base_path <- attr(block, "old_base_path", exact = TRUE) diff --git a/man/tm_rmarkdown.Rd b/man/tm_rmarkdown.Rd index 4d8a51da5..3bb03aa92 100644 --- a/man/tm_rmarkdown.Rd +++ b/man/tm_rmarkdown.Rd @@ -54,24 +54,35 @@ Object of class \code{teal_module} to be used in \code{teal} applications. Module to render R Markdown files using the data provided in the \code{teal_data} object. } \details{ -The R Markdown file should be designed to accept parameters corresponding to the datasets. -See using \code{params} in R Markdown documentation: -\href{https://bookdown.org/yihui/rmarkdown/params-use.html}{bookdown.org/yihui/rmarkdown/params-use.html} +The R Markdown file should be designed to accept variables available in the datanames of the module. For example, if the \code{teal_data} object contains datasets named "mtcars" and "iris", -the R Markdown file can define parameters as follows: +the R Markdown file can use these as variables as they will be available in the R Markdown environment. -\if{html}{\out{
}}\preformatted{--- +\if{html}{\out{
}}\preformatted{--- title: "R Markdown Report" output: html_document -params: - mtcars: NULL - iris: NULL --- + +```\{r\} +summary(mtcars) |> print() +summary(iris) |> print() +``` }\if{html}{\out{
}} -The libraries used in the R Markdown file must be available in -the Shiny app environment. +The libraries used in the R Markdown file must be available in the deployed shiny +app environment. + +When developing the R Markdown file, the working data can be simulated on a code chunk, +which in turn can look for the presence of \code{.raw_data} object to determine if it is being +run inside the \code{teal} module or not. +For example: + +\if{html}{\out{
}}\preformatted{```\{r eval=!exists(".raw_data")\} +mtcars <- datasets::mtcars +iris <- datasets::iris +``` +}\if{html}{\out{
}} } \examples{ From 198a462f0604f0aa034a45ad6c45934a79c442f1 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 14 Nov 2025 11:16:53 +0000 Subject: [PATCH 21/22] [skip style] [skip vbump] Restyle files --- R/tm_rmarkdown.R | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/R/tm_rmarkdown.R b/R/tm_rmarkdown.R index d26a9b84a..2815a63c3 100644 --- a/R/tm_rmarkdown.R +++ b/R/tm_rmarkdown.R @@ -66,14 +66,14 @@ #' label = "RMarkdown Module", #' rmd_content = c( #' "---", -#' "title: \"R Markdown Report\"", -#' "output: html_document", -#' "---", -#' "", -#' "```{r}", -#' "summary(CO2) |> print()", -#' "```" -#' ) +#' "title: \"R Markdown Report\"", +#' "output: html_document", +#' "---", +#' "", +#' "```{r}", +#' "summary(CO2) |> print()", +#' "```" +#' ) #' ) #' ) #' ) From 4b6d7df7c9cf668710217e72dc4747e1b938dbbf Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 14 Nov 2025 11:23:51 +0000 Subject: [PATCH 22/22] [skip roxygen] [skip vbump] Roxygen Man Pages Auto Update --- man/tm_rmarkdown.Rd | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/man/tm_rmarkdown.Rd b/man/tm_rmarkdown.Rd index 3bb03aa92..1054dd3ae 100644 --- a/man/tm_rmarkdown.Rd +++ b/man/tm_rmarkdown.Rd @@ -59,29 +59,29 @@ The R Markdown file should be designed to accept variables available in the data For example, if the \code{teal_data} object contains datasets named "mtcars" and "iris", the R Markdown file can use these as variables as they will be available in the R Markdown environment. -\if{html}{\out{
}}\preformatted{--- -title: "R Markdown Report" -output: html_document ---- - -```\{r\} -summary(mtcars) |> print() -summary(iris) |> print() -``` -}\if{html}{\out{
}} - The libraries used in the R Markdown file must be available in the deployed shiny app environment. When developing the R Markdown file, the working data can be simulated on a code chunk, which in turn can look for the presence of \code{.raw_data} object to determine if it is being run inside the \code{teal} module or not. -For example: -\if{html}{\out{
}}\preformatted{```\{r eval=!exists(".raw_data")\} +Example R markdown file: + +\if{html}{\out{
}}\preformatted{--- +title: "R Markdown Report" +output: html_document +--- + +```\{r eval=!exists(".raw_data")\} mtcars <- datasets::mtcars iris <- datasets::iris ``` + +```\{r\} +summary(mtcars) |> print() +summary(iris) |> print() +``` }\if{html}{\out{
}} } \examples{ @@ -90,17 +90,23 @@ iris <- datasets::iris data <- teal_data() data <- within(data, { CO2 <- CO2 - CO2[["primary_key"]] <- seq_len(nrow(CO2)) }) -join_keys(data) <- join_keys(join_key("CO2", "CO2", "primary_key")) - app <- init( data = data, modules = modules( tm_rmarkdown( label = "RMarkdown Module", - rmd_content = readLines(system.file(file.path("sample_files", "test.Rmd"), package = "teal.modules.general")) + rmd_content = c( + "---", + "title: \"R Markdown Report\"", + "output: html_document", + "---", + "", + "```{r}", + "summary(CO2) |> print()", + "```" + ) ) ) ) @@ -146,8 +152,8 @@ app <- init( \section{Examples in Shinylive}{ \describe{ \item{example-1}{ - \href{https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqajGIgEwCu1OAGcMAcwpxm1AJQAdCLTIyoBUrQBucAAQAeALS6AZoIgbaJdnN0AVLAFUAokrcQAxLqkQ11XfxQpFC6cAAesKgiSoHBBsZc1AD6sVA2MUEhRroA7rSkABYq7Km4uiBKuroAwgDyAEzxNQ2VzfXAwApgqIy0MCysSQDWcKxdALrjTaJwAI5JIhDsEIxEOex19XKKEAC+OwBWRCrDo6IlmbbZRycjrOc3EKccXZtdZa8teLpdPX0Dzy623cSjQqCaKny7FaqV0AF4AplcK0+EIRKJ4bpUcIxNCIFUqqQYElGP1GEN+Gslq0CbpqFB6HB-AiulgALIsClU3RsgQ4940gmk-hJYiqMiYxhwKD8AAyKlxonu5BgGBMtBE7HVIgwqCCBWhYFEkRESW1YnePzA5FEpGwMH4QLKeoIQygUkxXUSvD56Mk0lkQJ2tODuh2O1oJl07BU5GYlh0NlsFXxulERQgrAAguh2GCACSCWhlAszRg6Rg7XZKMC7cZAA}{Open in Shinylive} - \if{html}{\out{}} + \href{https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqajGIgEwCu1OAGcMAcwpxm1AJQAdCLTIyoBUrQBucAAQAeALS6AZoIgbaJdnN0AVLAFUAokrcQAxLqkQ11XfxQpFC6cAAesKgiSoHBBsZc1AD6sVA2MUEhRroA7rSkABYq7Km4uiBKuroAwgDyAEzxNQ1KAL6KEEpoqE0q+eyVAZm6ALxDwbiDfEIioqO608JiAxBVVaQwSYwwLADW-EQ5ECtra9RQ9HD+YwpgWACyewdHuvcCS7eTq6e62-xJxFUZHmBBOPyqt0MUM+g3Bt00pBEiF0ClRd1eT0OqywcFQREYpFRtxh3zhYCIglIqEpyIKG2SBwIgngZBJ4IhYChhjZ7OJeFhP1uAAMRSBGK0eWTRMydmx2HV6rYAD4APl0qEYKlI6X5pMFYBFQuJeqqHVOZt0HQ6tBMunYWrUlh0NlsFW+oiKEFYAEF0OxugASQS0MqB0QyHSMDoSiBgVoAXSAA}{Open in Shinylive} + \if{html}{\out{}} \if{html}{\out{}} } }