diff --git a/NAMESPACE b/NAMESPACE index 347f3e4f..23f0e3b5 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -8,6 +8,8 @@ export(add_card_button_ui) export(as_yaml_auto) export(download_report_button_srv) export(download_report_button_ui) +export(report_load_srv) +export(report_load_ui) export(reporter_previewer_srv) export(reporter_previewer_ui) export(reset_report_button_srv) diff --git a/NEWS.md b/NEWS.md index 6e482934..048a6e83 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,7 @@ ### Enhancements * Report cards are now included in bookmarks. When using the `shiny` bookmarking mechanism, present report cards will be available in the restored application. +* Report can be loaded back now. The zip file with the report can be loaded back which will restore Previewer state. # teal.reporter 0.3.1 diff --git a/R/Archiver.R b/R/Archiver.R deleted file mode 100644 index 9230b4cc..00000000 --- a/R/Archiver.R +++ /dev/null @@ -1,181 +0,0 @@ -#' @title `Archiver`: Base class for data archiving -#' @docType class -#' @description -#' A base `R6` class for implementing data archiving functionality. -#' -#' @keywords internal -Archiver <- R6::R6Class( # nolint: object_name_linter. - classname = "Archiver", - public = list( - #' @description Initialize an `Archiver` object. - #' - #' @return Object of class `Archiver`, invisibly. - #' @examples - #' Archiver <- getFromNamespace("Archiver", "teal.reporter") - #' Archiver$new() - initialize = function() { - invisible(self) - }, - #' @description Finalizes an `Archiver` object. - finalize = function() { - # destructor - }, - #' @description Reads data from the `Archiver`. - #' Pure virtual method that should be implemented by inherited classes. - read = function() { - # returns Reporter instance - stop("Pure virtual method.") - }, - #' @description Writes data to the `Archiver`. - #' Pure virtual method that should be implemented by inherited classes. - write = function() { - stop("Pure virtual method.") - } - ), - lock_objects = TRUE, - lock_class = TRUE -) - -#' @title `FileArchiver`: A File-based `Archiver` -#' @docType class -#' @description -#' Inherits from `Archiver` to provide file-based archiving functionality. -#' Manages an output directory for storing archived data. -#' -#' @keywords internal -FileArchiver <- R6::R6Class( # nolint: object_name_linter. - classname = "FileArchiver", - inherit = Archiver, - public = list( - #' @description Initialize a `FileArchiver` object with a unique output directory. - #' - #' @return Object of class `FileArchiver`, invisibly. - #' @examples - #' FileArchiver <- getFromNamespace("FileArchiver", "teal.reporter") - #' FileArchiver$new() - initialize = function() { - tmp_dir <- tempdir() - output_dir <- file.path(tmp_dir, sprintf("archive_%s", gsub("[.]", "", format(Sys.time(), "%Y%m%d%H%M%OS4")))) - dir.create(path = output_dir) - private$output_dir <- output_dir - invisible(self) - }, - #' @description Finalizes a `FileArchiver` object. - #' Cleans up by removing the output directory and its contents. - finalize = function() { - unlink(private$output_dir, recursive = TRUE) - }, - #' @description Get `output_dir` field. - #' - #' @return `character` a `output_dir` field path. - #' @examples - #' FileArchiver <- getFromNamespace("FileArchiver", "teal.reporter") - #' FileArchiver$new()$get_output_dir() - get_output_dir = function() { - private$output_dir - } - ), - private = list( - output_dir = character(0) - ) -) - -#' @title `JSONArchiver`: A `JSON`-based `Archiver` -#' @docType class -#' @description -#' Inherits from `FileArchiver` to implement `JSON`-based archiving functionality. -#' Convert `Reporter` instances to and from `JSON` format. -#' -#' @keywords internal -JSONArchiver <- R6::R6Class( # nolint: object_name_linter. - classname = "JSONArchiver", - inherit = FileArchiver, - public = list( - #' @description Write a `Reporter` instance in `JSON` file. - #' Serializes a given `Reporter` instance and saves it in the `Archiver`'s output directory, - #' to this `JSONArchiver` object. - #' - #' @param reporter (`Reporter`) instance. - #' - #' @return `self`. - #' @examples - #' library(ggplot2) - #' - #' ReportCard <- getFromNamespace("ReportCard", "teal.reporter") - #' card1 <- ReportCard$new() - #' - #' card1$append_text("Header 2 text", "header2") - #' card1$append_text("A paragraph of default text", "header2") - #' card1$append_plot( - #' ggplot(iris, aes(x = Petal.Length)) + geom_histogram() - #' ) - #' - #' Reporter <- getFromNamespace("Reporter", "teal.reporter") - #' reporter <- Reporter$new() - #' reporter$append_cards(list(card1)) - #' - #' JSONArchiver <- getFromNamespace("JSONArchiver", "teal.reporter") - #' archiver <- JSONArchiver$new() - #' archiver$write(reporter) - #' archiver$get_output_dir() - write = function(reporter) { - checkmate::assert_class(reporter, "Reporter") - unlink(list.files(private$output_dir, recursive = TRUE, full.names = TRUE)) - reporter$to_jsondir(private$output_dir) - self - }, - #' @description Read a `Reporter` instance from a `JSON` file. - #' Converts a `Reporter` instance from the `JSON` file in the `JSONArchiver`'s output directory. - #' - #' @param path (`character(1)`) a path to the directory with all proper files. - #' - #' @return `Reporter` instance. - #' @examples - #' library(ggplot2) - #' - #' ReportCard <- getFromNamespace("ReportCard", "teal.reporter") - #' card1 <- ReportCard$new() - #' - #' card1$append_text("Header 2 text", "header2") - #' card1$append_text("A paragraph of default text", "header2") - #' card1$append_plot( - #' ggplot(iris, aes(x = Petal.Length)) + geom_histogram() - #' ) - #' - #' Reporter <- getFromNamespace("Reporter", "teal.reporter") - #' reporter <- Reporter$new() - #' reporter$append_cards(list(card1)) - #' - #' JSONArchiver <- getFromNamespace("JSONArchiver", "teal.reporter") - #' archiver <- JSONArchiver$new() - #' archiver$write(reporter) - #' archiver$get_output_dir() - #' - #' archiver$read()$get_cards()[[1]]$get_content() - #' Reporter <- getFromNamespace("Reporter", "teal.reporter") - #' blocks <- Reporter$new() - #' blocks <- blocks$from_reporter(archiver$read())$get_blocks() - #' Renderer <- getFromNamespace("Renderer", "teal.reporter") - #' doc <- Renderer$new()$render(blocks) - read = function(path = NULL) { - checkmate::assert( - checkmate::check_null(path), - checkmate::check_directory_exists(path) - ) - - if (!is.null(path) && !identical(path, private$output_dir)) { - unlink(list.files(private$output_dir, recursive = TRUE, full.names = TRUE)) - file.copy(list.files(path, full.names = TRUE), private$output_dir) - } - - if (length(list.files(private$output_dir))) { - Reporter$new()$from_jsondir(private$output_dir) - } else { - warning("The directory provided to the Archiver is empty.") - Reporter$new() - } - } - ), - lock_objects = TRUE, - lock_class = TRUE -) diff --git a/R/DownloadModule.R b/R/DownloadModule.R index 578fc777..9548b8dd 100644 --- a/R/DownloadModule.R +++ b/R/DownloadModule.R @@ -137,7 +137,12 @@ download_report_button_srv <- function(id, output$download_data <- shiny::downloadHandler( filename = function() { - paste("report_", format(Sys.time(), "%y%m%d%H%M%S"), ".zip", sep = "") + paste0( + "report_", + if (reporter$get_id() == "") NULL else paste0(reporter$get_id(), "_"), + format(Sys.time(), "%y%m%d%H%M%S"), + ".zip" + ) }, content = function(file) { shiny::showNotification("Rendering and Downloading the document.") @@ -189,6 +194,7 @@ report_render_and_compress <- function(reporter, input_list, global_knitr, file tryCatch( renderer$render(reporter$get_blocks(), yaml_header, global_knitr), warning = function(cond) { + print(cond) shiny::showNotification( ui = "Render document warning!", action = "Please contact app developer", @@ -196,6 +202,7 @@ report_render_and_compress <- function(reporter, input_list, global_knitr, file ) }, error = function(cond) { + print(cond) shiny::showNotification( ui = "Render document error!", action = "Please contact app developer", @@ -204,10 +211,33 @@ report_render_and_compress <- function(reporter, input_list, global_knitr, file } ) + output_dir <- renderer$get_output_dir() + + tryCatch( + archiver_dir <- reporter$to_jsondir(output_dir), + warning = function(cond) { + print(cond) + shiny::showNotification( + ui = "Archive document warning!", + action = "Please contact app developer", + type = "warning" + ) + }, + error = function(cond) { + print(cond) + shiny::showNotification( + ui = "Archive document error!", + action = "Please contact app developer", + type = "error" + ) + } + ) + temp_zip_file <- tempfile(fileext = ".zip") tryCatch( - expr = zip::zipr(temp_zip_file, renderer$get_output_dir()), + expr = zip::zipr(temp_zip_file, output_dir), warning = function(cond) { + print(cond) shiny::showNotification( ui = "Zipping folder warning!", action = "Please contact app developer", @@ -215,6 +245,7 @@ report_render_and_compress <- function(reporter, input_list, global_knitr, file ) }, error = function(cond) { + print(cond) shiny::showNotification( ui = "Zipping folder error!", action = "Please contact app developer", @@ -226,6 +257,7 @@ report_render_and_compress <- function(reporter, input_list, global_knitr, file tryCatch( expr = file.copy(temp_zip_file, file), warning = function(cond) { + print(cond) shiny::showNotification( ui = "Copying file warning!", action = "Please contact app developer", @@ -233,6 +265,7 @@ report_render_and_compress <- function(reporter, input_list, global_knitr, file ) }, error = function(cond) { + print(cond) shiny::showNotification( ui = "Copying file error!", action = "Please contact app developer", diff --git a/R/LoadReporterModule.R b/R/LoadReporterModule.R new file mode 100644 index 00000000..2e8a1ff7 --- /dev/null +++ b/R/LoadReporterModule.R @@ -0,0 +1,139 @@ +#' User Interface to Load `Reporter` +#' @description `r lifecycle::badge("experimental")` +#' Button to upload `ReporterCard`(s) to the `Reporter`. +#' +#' For more details see the vignette: `vignette("simpleReporter", "teal.reporter")`. +#' @param id `character(1)` this `shiny` module's id. +#' @return `shiny::tagList` +#' @export +report_load_ui <- function(id) { + ns <- shiny::NS(id) + + shiny::tagList( + shiny::singleton( + shiny::tags$head(shiny::includeCSS(system.file("css/custom.css", package = "teal.reporter"))) + ), + shiny::tags$button( + id = ns("reporter_load"), + type = "button", + class = "simple_report_button btn btn-primary action-button", + title = "Load", + NULL, + shiny::tags$span( + shiny::icon("upload") + ) + ) + ) +} + +#' Server to Load `Reporter` +#' @description `r lifecycle::badge("experimental")` +#' Server to load `ReporterCard`(s) to the `Reporter` +#' +#' For more details see the vignette: `vignette("simpleReporter", "teal.reporter")`. +#' +#' @param id `character(1)` this `shiny` module's id. +#' @param reporter [`Reporter`] instance. +#' +#' @return `shiny::moduleServer` +#' @export +report_load_srv <- function(id, reporter) { + checkmate::assert_class(reporter, "Reporter") + + shiny::moduleServer( + id, + function(input, output, session) { + shiny::setBookmarkExclude(c("reporter_load_main", "reporter_load")) + ns <- session$ns + + archiver_modal <- function() { + nr_cards <- length(reporter$get_cards()) + shiny::modalDialog( + easyClose = TRUE, + shiny::tags$h3("Load the Report"), + shiny::tags$hr(), + shiny::fileInput(ns("archiver_zip"), "Choose saved Reporter file to Load (a zip file)", + multiple = FALSE, + accept = c(".zip") + ), + footer = shiny::div( + shiny::tags$button( + type = "button", + class = "btn btn-danger", + `data-dismiss` = "modal", + `data-bs-dismiss` = "modal", + NULL, + "Cancel" + ), + shiny::tags$button( + id = ns("reporter_load_main"), + type = "button", + class = "btn btn-primary action-button", + NULL, + "Load" + ) + ) + ) + } + + shiny::observeEvent(input$reporter_load, { + shiny::showModal(archiver_modal()) + }) + + shiny::observeEvent(input$reporter_load_main, { + load_json_report(reporter, input$archiver_zip[["datapath"]], input$archiver_zip[["name"]]) + shiny::removeModal() + }) + } + ) +} + +#' @keywords internal +load_json_report <- function(reporter, zip_path, filename) { + tmp_dir <- tempdir() + output_dir <- file.path(tmp_dir, sprintf("report_load_%s", gsub("[.]", "", format(Sys.time(), "%Y%m%d%H%M%OS4")))) + dir.create(path = output_dir) + if (!is.null(zip_path) && grepl("report_", filename)) { + tryCatch( + expr = zip::unzip(zip_path, exdir = output_dir, junkpaths = TRUE), + warning = function(cond) { + print(cond) + shiny::showNotification( + ui = "Unzipping folder warning!", + action = "Please contact app developer", + type = "warning" + ) + }, + error = function(cond) { + print(cond) + shiny::showNotification( + ui = "Unzipping folder error!", + action = "Please contact app developer", + type = "error" + ) + } + ) + tryCatch( + reporter$from_jsondir(output_dir), + warning = function(cond) { + print(cond) + shiny::showNotification( + ui = "Loading reporter warning!", + action = "Please contact app developer", + type = "warning" + ) + }, + error = function(cond) { + print(cond) + shiny::showNotification( + ui = "Loading reporter error!", + action = "Please contact app developer", + type = "error" + ) + } + ) + + } else { + shiny::showNotification("Failed to load the Reporter file.", type = "error") + } +} diff --git a/R/Previewer.R b/R/Previewer.R index 45fce4c7..ab3b4e33 100644 --- a/R/Previewer.R +++ b/R/Previewer.R @@ -18,6 +18,9 @@ #' @param reporter (`Reporter`) instance. #' @param global_knitr (`list`) of `knitr` parameters (passed to `knitr::opts_chunk$set`) #' for customizing the rendering process. +#' @param previewer_buttons (`character`) set of modules to include with `c("download", "load", "reset")` possible +#' values and `"download"` is required. +#' Default `c("download", "load", "reset")` #' @inheritParams reporter_download_inputs #' #' @return `NULL`. @@ -56,11 +59,15 @@ reporter_previewer_srv <- function(id, "html" = "html_document", "pdf" = "pdf_document", "powerpoint" = "powerpoint_presentation", "word" = "word_document" - ), rmd_yaml_args = list( + ), + rmd_yaml_args = list( author = "NEST", title = "Report", date = as.character(Sys.Date()), output = "html_document", toc = FALSE - )) { + ), + previewer_buttons = c("download", "load", "reset")) { + checkmate::assert_subset(previewer_buttons, c("download", "load", "reset"), empty.ok = FALSE) + checkmate::assert_true("download" %in% previewer_buttons) checkmate::assert_class(reporter, "Reporter") checkmate::assert_subset(names(global_knitr), names(knitr::opts_chunk$get())) checkmate::assert_subset( @@ -81,13 +88,18 @@ reporter_previewer_srv <- function(id, shiny::moduleServer(id, function(input, output, session) { shiny::setBookmarkExclude(c( - "card_remove_id", "card_down_id", "card_up_id", "remove_card_ok", "showrcode", "download_data_prev" + "card_remove_id", "card_down_id", "card_up_id", "remove_card_ok", "showrcode", "download_data_prev", + "load_reporter_previewer", "load_reporter" )) + session$onBookmark(function(state) { - state$values$report_cards <- reporter$get_cards() + reporterdir <- file.path(state$dir, "reporter") + dir.create(reporterdir) + reporter$to_jsondir(reporterdir) }) session$onRestored(function(state) { - reporter$append_cards(state$values$report_cards) + reporterdir <- file.path(state$dir, "reporter") + reporter$from_jsondir(reporterdir) }) ns <- session$ns @@ -96,27 +108,48 @@ reporter_previewer_srv <- function(id, output$encoding <- shiny::renderUI({ reporter$get_reactive_add_card() - shiny::tagList( - shiny::tags$h3("Download the Report"), - shiny::tags$hr(), - reporter_download_inputs( - rmd_yaml_args = rmd_yaml_args, - rmd_output = rmd_output, - showrcode = any_rcode_block(reporter), - session = session - ), - htmltools::tagAppendAttributes( + nr_cards <- length(reporter$get_cards()) + + previewer_buttons_list <- list( + download = htmltools::tagAppendAttributes( shiny::tags$a( id = ns("download_data_prev"), - class = "btn btn-primary shiny-download-link", + class = "btn btn-primary shiny-download-link simple_report_button", href = "", target = "_blank", download = NA, shiny::tags$span("Download Report", shiny::icon("download")) ), - class = if (length(reporter$get_cards())) "" else "disabled" + class = if (nr_cards) "" else "disabled" ), - reset_report_button_ui(ns("resetButtonPreviewer"), label = "Reset Report") + load = shiny::tags$button( + id = ns("load_reporter_previewer"), + type = "button", + class = "btn btn-primary action-button simple_report_button", + `data-val` = shiny::restoreInput(id = ns("load_reporter_previewer"), default = NULL), + NULL, + shiny::tags$span( + "Load Report", shiny::icon("upload") + ) + ), + reset = reset_report_button_ui(ns("resetButtonPreviewer"), label = "Reset Report") + ) + + shiny::tags$div( + id = "previewer_reporter_encoding", + shiny::tags$h3("Download the Report"), + shiny::tags$hr(), + reporter_download_inputs( + rmd_yaml_args = rmd_yaml_args, + rmd_output = rmd_output, + showrcode = any_rcode_block(reporter), + session = session + ), + shiny::tags$div( + id = "previewer_reporter_buttons", + class = "previewer_buttons_line", + previewer_buttons_list[previewer_buttons] + ) ) }) @@ -147,6 +180,48 @@ reporter_previewer_srv <- function(id, } }) + shiny::observeEvent(input$load_reporter_previewer, { + nr_cards <- length(reporter$get_cards()) + shiny::showModal( + shiny::modalDialog( + easyClose = TRUE, + shiny::tags$h3("Load the Reporter"), + shiny::tags$hr(), + shiny::fileInput(ns("archiver_zip"), "Choose Reporter File to Load (a zip file)", + multiple = FALSE, + accept = c(".zip") + ), + footer = shiny::div( + shiny::tags$button( + type = "button", + class = "btn btn-danger", + `data-dismiss` = "modal", + `data-bs-dismiss` = "modal", + NULL, + "Cancel" + ), + shiny::tags$button( + id = ns("load_reporter"), + type = "button", + class = "btn btn-primary action-button", + `data-val` = shiny::restoreInput(id = ns("load_reporter"), default = NULL), + NULL, + "Load" + ) + ) + ) + ) + }) + + shiny::observeEvent(input$load_reporter, { + switch("JSON", + JSON = load_json_report(reporter, input$archiver_zip[["datapath"]], input$archiver_zip[["name"]]), + stop("The provided Reporter file format is not supported") + ) + + shiny::removeModal() + }) + shiny::observeEvent(input$card_remove_id, { shiny::showModal( shiny::modalDialog( @@ -199,7 +274,12 @@ reporter_previewer_srv <- function(id, output$download_data_prev <- shiny::downloadHandler( filename = function() { - paste("report_", format(Sys.time(), "%y%m%d%H%M%S"), ".zip", sep = "") + paste0( + "report_", + if (reporter$get_id() == "") NULL else paste0(reporter$get_id(), "_"), + format(Sys.time(), "%y%m%d%H%M%S"), + ".zip" + ) }, content = function(file) { shiny::showNotification("Rendering and Downloading the document.") diff --git a/R/ReportCard.R b/R/ReportCard.R index d906f739..b8f08a21 100644 --- a/R/ReportCard.R +++ b/R/ReportCard.R @@ -162,7 +162,7 @@ ReportCard <- R6::R6Class( # nolint: object_name_linter. #' @examples #' ReportCard$new()$set_name("NAME")$get_name() set_name = function(name) { - checkmate::assert_string(name) + checkmate::assert_character(name) private$name <- name invisible(self) }, @@ -184,12 +184,11 @@ ReportCard <- R6::R6Class( # nolint: object_name_linter. new_blocks <- list() for (block in self$get_content()) { block_class <- class(block)[1] - cblock <- if (inherits(block, "FileBlock")) { + formal_args <- formalArgs(block$to_list) + cblock <- if ("output_dir" %in% formal_args) { block$to_list(output_dir) - } else if (inherits(block, "ContentBlock")) { - block$to_list() } else { - list() + block$to_list() } new_block <- list() new_block[[block_class]] <- cblock @@ -198,6 +197,7 @@ ReportCard <- R6::R6Class( # nolint: object_name_linter. new_card <- list() new_card[["blocks"]] <- new_blocks new_card[["metadata"]] <- self$get_metadata() + new_card[["name"]] <- self$get_name() new_card }, #' @description Reconstructs the `ReportCard` from a list representation. @@ -219,24 +219,28 @@ ReportCard <- R6::R6Class( # nolint: object_name_linter. self$reset() blocks <- card$blocks metadata <- card$metadata + name <- card$name + if (length(name) == 0) name <- character(0) blocks_names <- names(blocks) blocks_names <- gsub("[.][0-9]*$", "", blocks_names) for (iter_b in seq_along(blocks)) { block_class <- blocks_names[iter_b] block <- blocks[[iter_b]] - cblock <- eval(str2lang(sprintf("%s$new()", block_class))) - if (inherits(cblock, "FileBlock")) { - cblock$from_list(block, output_dir) - } else if (inherits(cblock, "ContentBlock")) { - cblock$from_list(block) + instance <- private$dispatch_block(block_class) + formal_args <- formalArgs(instance$new()$from_list) + cblock <- if (all(c("x", "output_dir") %in% formal_args)) { + instance$new()$from_list(block, output_dir) + } else if ("x" %in% formal_args) { + instance$new()$from_list(block) } else { - NULL + instance$new()$from_list() } self$append_content(cblock) } for (meta in names(metadata)) { self$append_metadata(meta, metadata[[meta]]) } + self$set_name(name) invisible(self) } ), @@ -244,6 +248,9 @@ ReportCard <- R6::R6Class( # nolint: object_name_linter. content = list(), metadata = list(), name = character(0), + dispatch_block = function(block_class) { + eval(str2lang(block_class)) + }, # @description The copy constructor. # # @param name the name of the field diff --git a/R/Reporter.R b/R/Reporter.R index 118ec180..394531c9 100644 --- a/R/Reporter.R +++ b/R/Reporter.R @@ -208,7 +208,7 @@ Reporter <- R6::R6Class( # nolint: object_name_linter. #' @description #' Reinitializes a `Reporter` instance by copying the report cards and metadata from another `Reporter`. #' @param reporter (`Reporter`) instance to copy from. - #' @return `self`, invisibly. + #' @return invisibly self #' @examples #' reporter <- Reporter$new() #' reporter$from_reporter(reporter) @@ -223,7 +223,6 @@ Reporter <- R6::R6Class( # nolint: object_name_linter. #' @param output_dir (`character(1)`) a path to the directory where files will be copied. #' @return `named list` representing the `Reporter` instance, including version information, #' metadata, and report cards. - #' #' @examples #' reporter <- Reporter$new() #' tmp_dir <- file.path(tempdir(), "testdir") @@ -231,7 +230,7 @@ Reporter <- R6::R6Class( # nolint: object_name_linter. #' reporter$to_list(tmp_dir) to_list = function(output_dir) { checkmate::assert_directory_exists(output_dir) - rlist <- list(version = "1", cards = list()) + rlist <- list(name = "teal Reporter", version = "1", id = self$get_id(), cards = list()) rlist[["metadata"]] <- self$get_metadata() for (card in self$get_cards()) { # we want to have list names being a class names to indicate the class for $from_list @@ -246,6 +245,7 @@ Reporter <- R6::R6Class( # nolint: object_name_linter. #' @param rlist (`named list`) representing a `Reporter` instance. #' @param output_dir (`character(1)`) a path to the directory from which files will be copied. #' @return `self`, invisibly. + #' @note if Report has an id when converting to JSON then It will be compared to the currently available one. #' @examples #' reporter <- Reporter$new() #' tmp_dir <- file.path(tempdir(), "testdir") @@ -253,23 +253,32 @@ Reporter <- R6::R6Class( # nolint: object_name_linter. #' dir.create(tmp_dir) #' reporter$from_list(reporter$to_list(tmp_dir), tmp_dir) from_list = function(rlist, output_dir) { + id <- self$get_id() checkmate::assert_list(rlist) checkmate::assert_directory_exists(output_dir) - if (rlist$version == "1") { + stopifnot("Report JSON has to have name slot equal to teal Reporter" = rlist$name == "teal Reporter") + stopifnot("Loaded Report id has to match the current instance one" = rlist$id == id) + if (rlist$version %in% c("1")) { new_cards <- list() cards_names <- names(rlist$cards) cards_names <- gsub("[.][0-9]*$", "", cards_names) for (iter_c in seq_along(rlist$cards)) { card_class <- cards_names[iter_c] card <- rlist$cards[[iter_c]] - new_card <- eval(str2lang(sprintf("%s$new()", card_class))) + new_card <- eval(str2lang(card_class))$new() new_card$from_list(card, output_dir) new_cards <- c(new_cards, new_card) } } else { - stop("The provided version is not supported") + stop( + sprintf( + "The provided %s reporter version is not supported.", + rlist$version + ) + ) } self$reset() + self$set_id(rlist$id) self$append_cards(new_cards) self$append_metadata(rlist$metadata) invisible(self) @@ -285,7 +294,8 @@ Reporter <- R6::R6Class( # nolint: object_name_linter. to_jsondir = function(output_dir) { checkmate::assert_directory_exists(output_dir) json <- self$to_list(output_dir) - cat(jsonlite::toJSON(json, auto_unbox = TRUE, force = TRUE), + cat( + jsonlite::toJSON(json, auto_unbox = TRUE, force = TRUE), file = file.path(output_dir, "Report.json") ) output_dir @@ -293,6 +303,7 @@ Reporter <- R6::R6Class( # nolint: object_name_linter. #' @description Reinitializes a `Reporter` from a `JSON ` file and files in a specified directory. #' @param output_dir (`character(1)`) a path to the directory with files, `JSON` and statics. #' @return `self`, invisibly. + #' @note if Report has an id when converting to JSON then It will be compared to the currently available one. #' @examples #' reporter <- Reporter$new() #' tmp_dir <- file.path(tempdir(), "jsondir") @@ -302,16 +313,31 @@ Reporter <- R6::R6Class( # nolint: object_name_linter. #' reporter$from_jsondir(tmp_dir) from_jsondir = function(output_dir) { checkmate::assert_directory_exists(output_dir) - checkmate::assert_true(length(list.files(output_dir)) > 0) dir_files <- list.files(output_dir) - which_json <- grep("json$", dir_files) - json <- jsonlite::read_json(file.path(output_dir, dir_files[which_json])) + stopifnot("There has to be at least one file in the loaded directory" = length(dir_files) > 0) + stopifnot("Report.json file has to be in the loaded directory" = "Report.json" %in% basename(dir_files)) + json <- jsonlite::read_json(file.path(output_dir, "Report.json")) self$reset() self$from_list(json, output_dir) invisible(self) + }, + #' @description Set the `Reporter` id + #' Optionally add id to a `Reporter` which will be compared when it is rebuilt from a list. + #' The id is added to the downloaded file name. + #' @param id (`character(1)`) a Report id. + #' @return `self`, invisibly. + set_id = function(id) { + private$id <- id + invisible(self) + }, + #' @description Get the `Reporter` id + #' @return `character(1)` the `Reporter` id. + get_id = function() { + private$id } ), private = list( + id = "", cards = list(), metadata = list(), reactive_add_card = NULL, diff --git a/R/SimpleReporter.R b/R/SimpleReporter.R index ce11071e..c912da38 100644 --- a/R/SimpleReporter.R +++ b/R/SimpleReporter.R @@ -36,7 +36,9 @@ NULL #' @rdname simple_reporter #' @export -simple_reporter_ui <- function(id) { +simple_reporter_ui <- function( + id +) { ns <- shiny::NS(id) shiny::tagList( shiny::singleton( @@ -49,6 +51,7 @@ simple_reporter_ui <- function(id) { class = "simple_reporter_container", add_card_button_ui(ns("add_report_card_simple")), download_report_button_ui(ns("download_button_simple")), + report_load_ui(ns("archive_load_simple")), reset_report_button_ui(ns("reset_button_simple")) ) ) @@ -57,19 +60,21 @@ simple_reporter_ui <- function(id) { #' @rdname simple_reporter #' @export -simple_reporter_srv <- function(id, - reporter, - card_fun, - global_knitr = getOption("teal.reporter.global_knitr"), - rmd_output = c( - "html" = "html_document", "pdf" = "pdf_document", - "powerpoint" = "powerpoint_presentation", "word" = "word_document" - ), - rmd_yaml_args = list( - author = "NEST", title = "Report", - date = as.character(Sys.Date()), output = "html_document", - toc = FALSE - )) { +simple_reporter_srv <- function( + id, + reporter, + card_fun, + global_knitr = getOption("teal.reporter.global_knitr"), + rmd_output = c( + "html" = "html_document", "pdf" = "pdf_document", + "powerpoint" = "powerpoint_presentation", "word" = "word_document" + ), + rmd_yaml_args = list( + author = "NEST", title = "Report", + date = as.character(Sys.Date()), output = "html_document", + toc = FALSE + ) +) { shiny::moduleServer( id, function(input, output, session) { @@ -81,6 +86,7 @@ simple_reporter_srv <- function(id, rmd_output = rmd_output, rmd_yaml_args = rmd_yaml_args ) + report_load_srv("archive_load_simple", reporter = reporter) reset_report_button_srv("reset_button_simple", reporter = reporter) } ) diff --git a/_pkgdown.yml b/_pkgdown.yml index a842a830..f52a8f87 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -37,6 +37,8 @@ reference: - download_report_button_ui - reset_report_button_srv - reset_report_button_ui + - report_load_srv + - report_load_ui - title: "`yaml` and rmd utility functions" contents: - as_yaml_auto diff --git a/inst/WORDLIST b/inst/WORDLIST index 52b6a5b1..15c5a3b6 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -10,3 +10,4 @@ cloneable funder repo rmarkdown +JSON diff --git a/inst/css/Previewer.css b/inst/css/Previewer.css index dc491249..c5df266a 100644 --- a/inst/css/Previewer.css +++ b/inst/css/Previewer.css @@ -5,6 +5,11 @@ span.preview_card_control i:hover { color: blue; } +.previewer_buttons_line { + display: flex; + justify-content: end; + margin-right: 10px; +} /* Disable any anchor with disabled class */ a.disabled { diff --git a/inst/css/custom.css b/inst/css/custom.css index 826a7957..481596e5 100644 --- a/inst/css/custom.css +++ b/inst/css/custom.css @@ -16,6 +16,10 @@ display: flex; } +.float-right { + float: right; +} + .justify-start { justify-content: flex-start; } diff --git a/man/Archiver.Rd b/man/Archiver.Rd deleted file mode 100644 index d5231ea9..00000000 --- a/man/Archiver.Rd +++ /dev/null @@ -1,101 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/Archiver.R -\docType{class} -\name{Archiver} -\alias{Archiver} -\title{\code{Archiver}: Base class for data archiving} -\description{ -A base \code{R6} class for implementing data archiving functionality. -} -\examples{ - -## ------------------------------------------------ -## Method `Archiver$new` -## ------------------------------------------------ - -Archiver <- getFromNamespace("Archiver", "teal.reporter") -Archiver$new() -} -\keyword{internal} -\section{Methods}{ -\subsection{Public methods}{ -\itemize{ -\item \href{#method-Archiver-new}{\code{Archiver$new()}} -\item \href{#method-Archiver-finalize}{\code{Archiver$finalize()}} -\item \href{#method-Archiver-read}{\code{Archiver$read()}} -\item \href{#method-Archiver-write}{\code{Archiver$write()}} -\item \href{#method-Archiver-clone}{\code{Archiver$clone()}} -} -} -\if{html}{\out{