Skip to content

Commit d082c16

Browse files
committed
feat: adds tm_rmarkdown module
1 parent 3fb1021 commit d082c16

File tree

2 files changed

+218
-1
lines changed

2 files changed

+218
-1
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Imports:
4848
lattice (>= 0.18-4),
4949
lifecycle (>= 0.2.0),
5050
MASS (>= 7.3-60),
51+
rmarkdown (>= 2.23),
5152
rtables (>= 0.6.11),
5253
scales (>= 1.3.0),
5354
shinyjs (>= 2.1.0),
@@ -73,7 +74,6 @@ Suggests:
7374
nestcolor (>= 0.1.0),
7475
pkgload,
7576
rlang (>= 1.0.0),
76-
rmarkdown (>= 2.23),
7777
roxy.shinylive,
7878
rvest,
7979
shinytest2,

R/tm_rmarkdown.R

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
#' `teal` module: Rmarkdown render
2+
#'
3+
#' Module to render R Markdown files using the data provided in the `teal_data` object.
4+
#'
5+
#' The R Markdown file should be designed to accept parameters corresponding to the datasets.
6+
#' See using `params` in R Markdown documentation:
7+
#' [bookdown.org/yihui/rmarkdown/params-use.html](https://bookdown.org/yihui/rmarkdown/params-use.html)
8+
#'
9+
#' For example, if the `teal_data` object contains datasets named "mtcars" and "iris",
10+
#' the R Markdown file can define parameters as follows:
11+
#' ```yaml
12+
#' ---
13+
#' title: "R Markdown Report"
14+
#' output: html_document
15+
#' params:
16+
#' mtcars: NULL
17+
#' iris: NULL
18+
#' ---
19+
#' ````
20+
#'
21+
#' The libraries used in the R Markdown file must be available in
22+
#' the Shiny app environment.
23+
#'
24+
#' @inheritParams teal::module
25+
#' @inheritParams shared_params
26+
#'
27+
#' @param rmd_file (`character`) Path to the R Markdown file to be rendered.
28+
#' The file must be accessible from the Shiny app environment.
29+
#' @param allow_download (`logical`) whether to allow downloading of the R Markdown file.
30+
#' Defaults to `TRUE`.
31+
#'
32+
#' @inherit shared_params return
33+
#'
34+
#' @inheritSection teal::example_module Reporting
35+
#'
36+
#' @examplesShinylive
37+
#' library(teal.modules.general)
38+
#' interactive <- function() TRUE
39+
#' {{ next_example }}
40+
#' @examples
41+
#'
42+
#' # general data example
43+
#' data <- teal_data()
44+
#' data <- within(data, {
45+
#' CO2 <- CO2
46+
#' CO2[["primary_key"]] <- seq_len(nrow(CO2))
47+
#' })
48+
#' join_keys(data) <- join_keys(join_key("CO2", "CO2", "primary_key"))
49+
#'
50+
#'
51+
#' app <- init(
52+
#' data = data,
53+
#' modules = modules(
54+
#' tm_rmarkdown(
55+
#' label = "RMarkdown Module",
56+
#' rmd_file = "test.Rmd"
57+
#' )
58+
#' )
59+
#' )
60+
#' if (interactive()) {
61+
#' shinyApp(app$ui, app$server)
62+
#' }
63+
#'
64+
#' @export
65+
#'
66+
tm_rmarkdown <- function(label = "Outliers Module",
67+
rmd_file,
68+
datanames = "all",
69+
allow_download = TRUE,
70+
pre_output = NULL,
71+
post_output = NULL,
72+
transformators = list()) {
73+
message("Initializing tm_rmarkdown")
74+
75+
# Start of assertions
76+
77+
checkmate::assert_string(label)
78+
checkmate::assert_file(rmd_file, access = "r")
79+
checkmate::assert_flag(allow_download)
80+
81+
checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE)
82+
checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE)
83+
84+
# End of assertions
85+
86+
# Make UI args
87+
args <- as.list(environment())
88+
89+
ans <- module(
90+
label = label,
91+
server = srv_rmarkdown,
92+
server_args = list(rmd_file = rmd_file, allow_download = allow_download),
93+
ui = ui_rmarkdown,
94+
ui_args = args,
95+
transformators = transformators,
96+
datanames = datanames
97+
)
98+
# attr(ans, "teal_bookmarkable") <- TRUE
99+
ans
100+
}
101+
102+
# UI function for the outliers module
103+
ui_rmarkdown <- function(id, rmd_file, allow_download, ...) {
104+
args <- list(...)
105+
ns <- NS(id)
106+
107+
teal.widgets::standard_layout(
108+
output = teal.widgets::white_small_well(
109+
tags$div(
110+
tags$h4(
111+
"Module from R Markdown file: ",
112+
tags$code(basename(rmd_file))
113+
),
114+
if (allow_download) {
115+
downloadButton(
116+
ns("download_rmd"),
117+
sprintf("Download '%s'", basename(rmd_file)),
118+
class = "btn-primary btn-sm"
119+
)
120+
}
121+
122+
),
123+
tags$hr(),
124+
uiOutput(ns("rmd_output"))
125+
),
126+
encoding = NULL,
127+
pre_output = args$pre_output,
128+
post_output = args$post_output
129+
)
130+
}
131+
132+
# Server function for the outliers module
133+
# Server function for the outliers module
134+
srv_rmarkdown <- function(id, data, rmd_file, allow_download) {
135+
checkmate::assert_class(data, "reactive")
136+
checkmate::assert_class(isolate(data()), "teal_data")
137+
moduleServer(id, function(input, output, session) {
138+
output$download_rmd <- downloadHandler(
139+
filename = function() basename(rmd_file),
140+
content = function(file) file.copy(rmd_file, file),
141+
contentType = "text/plain"
142+
)
143+
144+
q_r <- reactive({
145+
data_q <- req(data())
146+
teal.reporter::teal_card(data_q) <- c(
147+
teal.reporter::teal_card(data_q),
148+
teal.reporter::teal_card("## Module's output(s)")
149+
)
150+
eval_code(
151+
data_q,
152+
sprintf(
153+
"rmd_data <- list(%s)",
154+
toString(sprintf("%1$s = %1$s", sapply(names(data_q), as.name)))
155+
)
156+
)
157+
})
158+
159+
rendered_path_r <- reactive({
160+
datasets <- req(q_r()) # Ensure data is available
161+
162+
temp_dir <- tempdir()
163+
temp_rmd <- tempfile(tmpdir = temp_dir, fileext = ".Rmd")
164+
temp_html <- tempfile(tmpdir = temp_dir, fileext = ".md")
165+
file.copy(rmd_file, temp_rmd) # Use a copy of the Rmd file to avoid modifying the original
166+
167+
tryCatch({
168+
rmarkdown::render(
169+
temp_rmd,
170+
output_format = rmarkdown::md_document(
171+
variant = "gfm",
172+
toc = TRUE,
173+
preserve_yaml = TRUE
174+
),
175+
output_file = temp_html,
176+
params = datasets[["rmd_data"]],
177+
envir = new.env(parent = globalenv()),
178+
quiet = TRUE,
179+
runtime = "static"
180+
)
181+
temp_html
182+
}, error = function(e) {
183+
warning("Error rendering RMD file: ", e$message) # verbose error in logs
184+
e
185+
})
186+
})
187+
188+
rendered_html_r <- reactive({
189+
output_path <- req(rendered_path_r())
190+
validate(
191+
need(inherits(output_path, "character"), "Error rendering RMD file. Please contact the app developer.")
192+
)
193+
htmltools::includeMarkdown(output_path)
194+
})
195+
196+
output$rmd_output <- renderUI(rendered_html_r())
197+
198+
reactive({
199+
out_data <- eval_code(
200+
q_r(),
201+
paste(
202+
sep = "\n",
203+
sprintf("## R Markdown contents are generated from file, please download it from the module UI."),
204+
sprintf("# rmarkdown::render(%s, params = rmd_data)", shQuote(basename(rmd_file), type = "cmd"))
205+
)
206+
)
207+
208+
out_data@verified <- FALSE
209+
210+
teal.reporter::teal_card(out_data) <- c(
211+
teal.reporter::teal_card(out_data),
212+
rendered_html_r()
213+
)
214+
out_data
215+
})
216+
})
217+
}

0 commit comments

Comments
 (0)