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("", 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("", 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{
}}
}
}