-
-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
init Archiver module #177
init Archiver module #177
Conversation
Pretty cool feature, very nice work! Question: When the JSON file is downloaded, what's the actual content? Is it just the code/setting or the actual visualization? Asking because what if the input datasets changed between the download/upload? |
TL;DR; There are many possible improvements to this feature.
|
After some talks with @mhallal1 I want to leave a small comment. |
format name ? |
I remove the archiver class after our discussions. We think the YAML implementation where we use yaml file instead of json is not needed. Moreover we see a little probability of implementing Data Base (e.g. remote MySQL) solution with login and password so end users do not have to manage the zip files. @nikolas-burkoff pointed a security issues for such too. Main difference in the code is only in 2 functions. Without Archiver: #' @keywords internal
load_json_archiver <- function(reporter, zip_path, filename) {
tmp_dir <- tempdir()
output_dir <- file.path(tmp_dir, sprintf("archiver_load_%s", gsub("[.]", "", format(Sys.time(), "%Y%m%d%H%M%OS4"))))
dir.create(path = output_dir)
if (!is.null(zip_path) && grepl("archiver_", filename)) {
tryCatch(
expr = zip::unzip(zip_path, exdir = output_dir, junkpaths = TRUE),
...
)
reporter$from_jsondir(output_dir)
} else {
shiny::showNotification("Failed to load the archiver file.", type = "error")
}
}
#' @keywords internal
archiver_download_handler_engine <- function(reporter, type, file) {
switch(type,
JSON = {
tmp_dir <- tempdir()
output_dir <- file.path(
tmp_dir,
sprintf("archiver_%s", gsub("[.]", "", format(Sys.time(), "%Y%m%d%H%M%OS4")))
)
dir.create(path = output_dir)
archiver_dir <- reporter$to_jsondir(output_dir)
temp_zip_file <- tempfile(fileext = ".zip")
tryCatch(
expr = zip::zipr(temp_zip_file, archiver_dir),
...
)
tryCatch(
expr = file.copy(temp_zip_file, file),
...
)
}
)
} With Archiver: #' @keywords internal
load_json_archiver <- function(reporter, zip_path, filename) {
archiver <- JSONArchiver$new()
tmp_dir <- tempdir()
output_dir <- file.path(tmp_dir, sprintf("archive_%s", gsub("[.]", "", format(Sys.time(), "%Y%m%d%H%M%OS4"))))
dir.create(output_dir)
tryCatch(
expr = zip::unzip(zip_path, exdir = output_dir, junkpaths = TRUE),
...
)
reporter_new <- archiver$read(output_dir)
reporter$from_reporter(reporter_new)
}
#' @keywords internal
archiver_download_handler_engine <- function(reporter, type, file) {
switch(type,
JSON = {
archiver_dir <- JSONArchiver$new()$write(reporter)
temp_zip_file <- tempfile(fileext = ".zip")
tryCatch(
expr = zip::zipr(temp_zip_file, archiver_dir$get_output_dir()),
...
)
tryCatch(
expr = file.copy(temp_zip_file, file),
...
)
}
)
} |
I can recall essential things now. The To sum up, the whole problem of perceiving this item is how we think about other solutions for saving the report/previewer. Why the current could be better as the end user could end up with hundreds of files in his local Downloads folder. More than that, the end user is responsible for managing the files himself. @nikolas-burkoff I think you will be interested. |
Closing along with #81 |
closes #81 continuation of #177 linked to insightsengineering/teal#1120 Please install this teal branch when testing the code I created a new PR from the fork as I am no longer part of the insightengineering group. My work is done as a collaboration of UCB company with Roche. insightengineering developers can edit this PR. I followed a simple design, which was evaluated positively in [the discussion](#81). DONE: - New modules `report_load_srv` and `report_load_ui`; similar direct update for Previewer. - REMOVE the `Archiver` Class as we not need it for this simplified scenario. - Improve `to_list` and `from_list` `Reporter` methods - Add `set_id` and `get_id` `Reporter` methods. Optionally add id to a Report which will be compared when it is rebuilt from a list. To test it in the teal example app please download a report and then add a new module or dataset to the app and try to load it back. The report can be loaded back to teal app only with the same datasets and modules. The id is added to the downloaded file name if exists. - Improve `to_list` and `from_list` `ReportCard` methods (linked with insightsengineering/teal#1120) - Both already existing vignette apps are updated automatically. - `warning(cond)` everywhere to be consistent. We should send the error/warning to the R console when STH fails. - Add `testServer` tests for report_load_srv/report_load_ui modules. - UI tested with all 3 bootstrap versions. Points to consider: - The JSON format Report representation seems to be enough, so an Archiver is unnecessary. The DB solution to save/load seems overcomplex for the project. - No update will be required to introduce it into teal modules. Simple reporter is updated automatically and can be customized with a new teal.reporter option. - When reloading, the Report is validated by the "report_" file name prefix later by the slot name "teal Report" and optionally by ID if it is non-empty. Example Teal App (play with bootstrap versions, simple reporter modules, and add new data/module to confirm the report can not be then reloaded): ```r library(teal.modules.general) # one of c("3", "4", "5") options("teal.bs_theme" = bslib::bs_theme(version = "4")) data <- teal_data() data <- within(data, { library(nestcolor) ADSL <- teal.modules.general::rADSL }) datanames <- c("ADSL") datanames(data) <- datanames join_keys(data) <- default_cdisc_join_keys[datanames] app <- teal::init( data = data, modules = teal::modules( teal.modules.general::tm_a_regression( label = "Regression", response = teal.transform::data_extract_spec( dataname = "ADSL", select = teal.transform::select_spec( label = "Select variable:", choices = "BMRKR1", selected = "BMRKR1", multiple = FALSE, fixed = TRUE ) ), regressor = teal.transform::data_extract_spec( dataname = "ADSL", select = teal.transform::select_spec( label = "Select variables:", choices = teal.transform::variable_choices(data[["ADSL"]], c("AGE", "SEX", "RACE")), selected = "AGE", multiple = TRUE, fixed = FALSE ) ), ggplot2_args = teal.widgets::ggplot2_args( labs = list(subtitle = "Plot generated by Regression Module") ) ) ) ) runApp(app, launch.browser = TRUE) ``` Example general shiny app (play with bootstrap versions, simple reporter modules): ```r library(shiny) library(teal.reporter) library(ggplot2) library(rtables) library(DT) library(bslib) ui <- fluidPage( # please, specify specific bootstrap version and theme theme = bs_theme(version = "4"), titlePanel(""), tabsetPanel( tabPanel( "main App", tags$br(), sidebarLayout( sidebarPanel( uiOutput("encoding") ), mainPanel( tabsetPanel( id = "tabs", tabPanel("Plot", plotOutput("dist_plot")), tabPanel("Table", verbatimTextOutput("table")), tabPanel("Table DataFrame", verbatimTextOutput("table2")), tabPanel("Table DataTable", dataTableOutput("table3")) ) ) ) ), ### REPORTER tabPanel( "Previewer", reporter_previewer_ui("prev") ) ### ) ) server <- function(input, output, session) { output$encoding <- renderUI({ tagList( ### REPORTER teal.reporter::simple_reporter_ui("simple_reporter"), ### if (input$tabs == "Plot") { sliderInput( "binwidth", "binwidth", min = 2, max = 10, value = 8 ) } else if (input$tabs %in% c("Table", "Table DataFrame", "Table DataTable")) { selectInput( "stat", label = "Statistic", choices = c("mean", "median", "sd"), "mean" ) } else { NULL } ) }) plot <- reactive({ req(input$binwidth) x <- mtcars$mpg ggplot(data = mtcars, aes(x = mpg)) + geom_histogram(binwidth = input$binwidth) }) output$dist_plot <- renderPlot(plot()) table <- reactive({ req(input$stat) lyt <- basic_table() %>% split_rows_by("Month", label_pos = "visible") %>% analyze("Ozone", afun = eval(str2expression(input$stat))) build_table(lyt, airquality) }) output$table <- renderPrint(table()) table2 <- reactive({ req(input$stat) data <- aggregate( airquality[, c("Ozone"), drop = FALSE], list(Month = airquality$Month), get(input$stat), na.rm = TRUE ) colnames(data) <- c("Month", input$stat) data }) output$table2 <- renderPrint(print.data.frame(table2())) output$table3 <- renderDataTable(table2()) ### REPORTER reporter <- Reporter$new() card_fun <- function(card = ReportCard$new(), comment) { if (input$tabs == "Plot") { card$set_name("Plot Module") card$append_text("My plot", "header2") card$append_plot(plot()) card$append_rcode( paste( c( "x <- mtcars$mpg", "ggplot2::ggplot(data = mtcars, ggplot2::aes(x = mpg)) +", paste0("ggplot2::geom_histogram(binwidth = ", input$binwidth, ")") ), collapse = "\n" ), echo = TRUE, eval = FALSE ) } else if (input$tabs == "Table") { card$set_name("Table Module rtables") card$append_text("My rtables", "header2") card$append_table(table()) card$append_rcode( paste( c( "lyt <- rtables::basic_table() %>%", 'rtables::split_rows_by("Month", label_pos = "visible") %>%', paste0('rtables::analyze("Ozone", afun = ', input$stat, ")"), "rtables::build_table(lyt, airquality)" ), collapse = "\n" ), echo = TRUE, eval = FALSE ) } else if (input$tabs %in% c("Table DataFrame", "Table DataTable")) { card$set_name("Table Module DF") card$append_text("My Table DF", "header2") card$append_table(table2()) # Here r code added as a regular verbatim text card$append_text( paste0( c( 'data <- aggregate(airquality[, c("Ozone"), drop = FALSE], list(Month = airquality$Month), ', input$stat, ", na.rm = TRUE)\n", 'colnames(data) <- c("Month", ', paste0('"', input$stat, '"'), ")\n", "data" ), collapse = "" ), "verbatim" ) } if (!comment == "") { card$append_text("Comment", "header3") card$append_text(comment) } card } teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_fun) teal.reporter::reporter_previewer_srv("prev", reporter) ### } if (interactive()) shinyApp(ui = ui, server = server) ``` --------- Signed-off-by: Maciej Nasinski <[email protected]> Co-authored-by: Dawid Kałędkowski <[email protected]> Co-authored-by: Dawid Kałędkowski <[email protected]>
closes #81
Simply run the Previewer app from the vignette and test it yourself.
TODO
teal_report_archiver_demo.mov