Skip to content

Commit 59736a7

Browse files
committed
pr: apply feedback from meeting with @gogonzo
1 parent d05971a commit 59736a7

File tree

2 files changed

+61
-80
lines changed

2 files changed

+61
-80
lines changed

NAMESPACE

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ S3method(create_sparklines,default)
88
S3method(create_sparklines,factor)
99
S3method(create_sparklines,logical)
1010
S3method(create_sparklines,numeric)
11-
S3method(tools::toHTML,markdown_teal_internal)
11+
S3method(teal.reporter::to_rmd,markdown_internal)
12+
S3method(tools::toHTML,markdown_internal)
1213
export(add_facet_labels)
1314
export(get_scatterplotmatrix_stats)
1415
export(tm_a_pca)

R/tm_rmarkdown.R

Lines changed: 59 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
#' @inheritParams teal::module
2525
#' @inheritParams shared_params
2626
#'
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.
27+
#' @param rmd_content (`character`) Content of the R Markdown file to be rendered.
28+
#' This can be the value of `readLines("path/to/file.Rmd")`.
2929
#' @param allow_download (`logical`) whether to allow downloading of the R Markdown file.
3030
#' Defaults to `TRUE`.
3131
#' @param extra_transform (`list`) of [teal::teal_transform_module()] that will be added in the module's UI.
@@ -55,7 +55,7 @@
5555
#' modules = modules(
5656
#' tm_rmarkdown(
5757
#' label = "RMarkdown Module",
58-
#' rmd_file = system.file(file.path("sample_files", "test.Rmd"), package = "teal.modules.general")
58+
#' rmd_content = readLines(system.file(file.path("sample_files", "test.Rmd"), package = "teal.modules.general"))
5959
#' )
6060
#' )
6161
#' )
@@ -92,7 +92,7 @@
9292
#' modules = modules(
9393
#' tm_rmarkdown(
9494
#' label = "RMarkdown Module",
95-
#' rmd_file = system.file(file.path("sample_files", "test.Rmd"), package = "teal.modules.general"),
95+
#' rmd_content = readLines(system.file(file.path("sample_files", "test.Rmd"), package = "teal.modules.general")),
9696
#' allow_download = FALSE,
9797
#' extra_transform = list(nrow_transform)
9898
#' )
@@ -101,7 +101,7 @@
101101
#' @export
102102
#'
103103
tm_rmarkdown <- function(label = "RMarkdown Module",
104-
rmd_file,
104+
rmd_content,
105105
datanames = "all",
106106
allow_download = TRUE,
107107
pre_output = NULL,
@@ -113,7 +113,7 @@ tm_rmarkdown <- function(label = "RMarkdown Module",
113113
# Start of assertions
114114

115115
checkmate::assert_string(label)
116-
checkmate::assert_file(rmd_file, access = "r")
116+
checkmate::assert_character(rmd_content)
117117
checkmate::assert_flag(allow_download)
118118

119119
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",
127127
ans <- module(
128128
label = label,
129129
server = srv_rmarkdown,
130-
server_args = list(rmd_file = rmd_file, allow_download = allow_download, extra_transform = extra_transform),
130+
server_args = list(rmd_content = rmd_content, allow_download = allow_download, extra_transform = extra_transform),
131131
ui = ui_rmarkdown,
132132
ui_args = args,
133133
transformators = transformators,
134134
datanames = datanames
135135
)
136136
# attr(ans, "teal_bookmarkable") <- TRUE
137-
ans
137+
disable_src(ans)
138138
}
139139

140140
# UI function for the rmarkdown module
141-
ui_rmarkdown <- function(id, rmd_file, allow_download, extra_transform, ...) {
141+
ui_rmarkdown <- function(id, rmd_content, allow_download, extra_transform, ...) {
142142
args <- list(...)
143143
ns <- NS(id)
144144

145145
teal.widgets::standard_layout(
146146
output = teal.widgets::white_small_well(
147147
tags$div(
148-
tags$h4(
149-
"Rendered report from: ",
150-
tags$code(basename(rmd_file))
151-
),
148+
tags$h4("Rendered report from Rmd"),
152149
if (allow_download) {
153150
downloadButton(
154151
ns("download_rmd"),
155-
sprintf("Download '%s'", basename(rmd_file)),
152+
sprintf("Download R Markdown file"),
156153
class = "btn-primary btn-sm"
157154
)
158155
},
@@ -168,7 +165,7 @@ ui_rmarkdown <- function(id, rmd_file, allow_download, extra_transform, ...) {
168165
}
169166

170167
# Server function for the rmarkdown module
171-
srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) {
168+
srv_rmarkdown <- function(id, data, rmd_content, allow_download, extra_transform) {
172169
checkmate::assert_class(data, "reactive")
173170
checkmate::assert_class(isolate(data()), "teal_data")
174171
moduleServer(id, function(input, output, session) {
@@ -189,36 +186,52 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) {
189186

190187
if (allow_download) {
191188
output$download_rmd <- downloadHandler(
192-
filename = function() basename(rmd_file),
189+
filename = function() "teal_module.Rmd", # TODO: find a better name
193190
content = function(file) {
194-
lines <- readLines(rmd_file)
195-
196191
# find the end of the YAML header or start of the file
197192
# and insert the contents of teal.code::get_code(q_r())
198-
yaml_end <- which(lines == "---")[2]
193+
yaml_end <- which(rmd_content == "---")[2]
199194
insert_pos <- if (!is.na(yaml_end)) yaml_end else 0
200195
note_lines <- c(
196+
"",
197+
"### Pre-processing data",
198+
"",
199+
"The following code chunk was automatically added by the teal markdown module.",
200+
"It shows how to generate the data used in this report.",
201201
"",
202202
"```{r}",
203-
"# The following code chunk was automatically added by the teal markdown module",
204-
"# It shows how to generate the data used in this report",
205203
teal.code::get_code(q_r()),
206204
"```",
207205
""
208206
)
209-
lines <- append(lines, note_lines, after = insert_pos)
210-
writeLines(lines, con = file)
207+
rmd_content <- append(rmd_content, note_lines, after = insert_pos)
208+
writeLines(rmd_content, con = file)
211209
},
212210
contentType = "text/plain"
213211
)
214212
}
215213

216-
temp_dir <- tempdir()
217-
temp_rmd <- tempfile(tmpdir = temp_dir, fileext = ".Rmd")
218-
file.copy(rmd_file, temp_rmd) # Use a copy of the Rmd file to avoid modifying the original
214+
clean_up_r <- shiny::reactiveVal(list())
215+
# Can only clean on sessionEnded as temporary files are needed for the reporter
216+
# during session
217+
onSessionEnded(function() {
218+
logger::log_debug("srv_rmarkdown: cleaning up temporary folders.")
219+
lapply(shiny::isolate(clean_up_r()), function(f) f())
220+
}, session)
219221

220222
rendered_path_r <- reactive({
221223
datasets <- req(q_r()) # Ensure data is available
224+
225+
temp_dir <- tempfile(pattern = "rmd_")
226+
dir.create(temp_dir, showWarnings = FALSE, recursive = TRUE)
227+
temp_rmd <- tempfile(pattern = "rmarkdown_module-", tmpdir = temp_dir, fileext = ".Rmd")
228+
# Schedule cleanup of temp files when reactive is re-executed
229+
shiny::isolate({
230+
old_clean_up <- clean_up_r()
231+
clean_up_r(c(old_clean_up, function() unlink(temp_dir, recursive = TRUE)))
232+
})
233+
writeLines(rmd_content, con = temp_rmd)
234+
222235
tryCatch(
223236
{
224237
rmarkdown::render(
@@ -252,20 +265,7 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) {
252265

253266
reactive({
254267
out_data <- q_r()
255-
256-
if (allow_download) {
257-
out_data <- eval_code(
258-
q_r(),
259-
paste(
260-
sep = "\n",
261-
sprintf("## R Markdown contents are generated from file, please download it from the module UI."),
262-
sprintf("# rmarkdown::render(%s, params = params)", shQuote(basename(rmd_file), type = "cmd"))
263-
)
264-
)
265-
out_data@verified <- FALSE # manual change verified status as code is being injected
266-
}
267-
268-
report_doc <- .markdown_internal(rendered_path_r(), temp_dir, rendered_html_r())
268+
report_doc <- .markdown_internal(rendered_path_r(), rendered_html_r())
269269
teal.reporter::teal_card(out_data) <- c(
270270
teal.reporter::teal_card(out_data), report_doc
271271
)
@@ -275,7 +275,7 @@ srv_rmarkdown <- function(id, data, rmd_file, allow_download, extra_transform) {
275275
}
276276

277277
#' @exportS3Method tools::toHTML
278-
toHTML.markdown_teal_internal <- function(block, ...) {
278+
toHTML.markdown_internal <- function(block, ...) {
279279
cached_html <- attr(block, "cached_html", exact = TRUE)
280280
if (!is.null(cached_html)) {
281281
return(cached_html)
@@ -284,50 +284,30 @@ toHTML.markdown_teal_internal <- function(block, ...) {
284284
}
285285

286286
#' @method to_rmd markdown_internal
287-
to_rmd.markdown_teal_internal <- function(block, figures_dir = "figures", include_chunk_output = TRUE, ...) {
288-
images_base64 <- attr(block, "images_base64", exact = TRUE)
289-
for (img_path in names(images_base64)) {
290-
img_data <- sub("^data:.*;base64,", "", images_base64[[img_path]])
291-
img_tag_pattern <- paste0("!\\[.*?\\]\\(", img_path, "\\)")
292-
dir.create(figures_dir, showWarnings = FALSE, recursive = TRUE)
293-
path <- file.path(
294-
figures_dir,
295-
sprintf(
296-
"markdown_img_%s.%s",
297-
substr(rlang::hash(img_data), 1, 6),
298-
sprintf("%s", tools::file_ext(img_path))
299-
)
300-
)
301-
writeBin(base64enc::base64decode(img_data), path)
302-
replacement_tag <- sprintf("![](%s)", path)
303-
block <- gsub(img_tag_pattern, replacement_tag, block, fixed = FALSE)
304-
}
287+
#' @exportS3Method teal.reporter::to_rmd
288+
to_rmd.markdown_internal <- function(block, figures_dir = "figures", include_chunk_output = TRUE, ...) {
289+
old_base_path <- attr(block, "old_base_path", exact = TRUE)
290+
parent_path <- attr(block, "parent_path", exact = TRUE)
291+
new_base_path <- file.path(figures_dir, old_base_path)
292+
293+
# Copy figures from old path to new location
294+
dir.create(figures_dir, showWarnings = FALSE, recursive = TRUE)
295+
file.copy(file.path(parent_path, old_base_path), figures_dir, recursive = TRUE)
296+
297+
# Change the image paths in the markdown content
298+
block <- gsub(pattern = old_base_path, replacement = new_base_path, x = block, fixed = TRUE)
305299
NextMethod(unclass(block), ...)
306300
}
307301

308-
.markdown_internal <- function(markdown_file, temp_dir, rendered_html) {
309-
# Read the markdown file
310-
lines <- readLines(markdown_file)
311-
images_base64 <- list()
312-
313-
# Extract images based on pattern ![](.*)
314-
img_pattern <- "!\\[.*?\\]\\((.*?)\\)"
315-
img_tags <- unlist(regmatches(lines, gregexpr(img_pattern, lines)))
316-
for (ix in seq_along(img_tags)) {
317-
img_tag <- img_tags[[ix]]
318-
img_path <- gsub("!\\[.*?\\]\\((.*?)\\)", "\\1", img_tag)
319-
full_img_path <- file.path(temp_dir, img_path)
320-
if (file.exists(full_img_path)) {
321-
img_data <- knitr::image_uri(full_img_path)
322-
images_base64[[img_path]] <- img_data
323-
}
324-
}
302+
.markdown_internal <- function(markdown_file, rendered_html) {
303+
base_file <- basename(markdown_file)
325304

326305
# Create new custom structure with contents and images in base64 as attribute
327306
structure(
328-
lines,
329-
class = c("markdown_teal_internal", "character"),
330-
images_base64 = images_base64,
307+
readLines(markdown_file),
308+
class = c("markdown_internal", "character"),
309+
parent_path = dirname(markdown_file),
310+
old_base_path = sprintf("%s_files/", tools::file_path_sans_ext(base_file)),
331311
cached_html = rendered_html
332312
)
333313
}

0 commit comments

Comments
 (0)