-
-
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
Create and implement a shiny module for Archiver class #81
Comments
@donyunardi in my opinion one of the priorities. |
@lcd2yyz |
It's a neat little feature for sure, but I don't feel it would add as much value as upload/restore filter settings, or ultimately, the encoding panel settings. Once user add a card to the reporter, the analysis settings and contents are not editable anymore. Also this feature should not be needed, if we can tackle the saving app state at some point (wishful thinking). |
Hey @lcd2yyz and @donyunardi :) |
Great to hear from you @Polkas! Thanks for the offer and showing your passion towards |
Thank you for a quick reply. I am delighted to hear you are open to collaboration. I will work in my free time so It will take me a few weeks. Thus, the NEST team review will not be needed in the close time. I understand you have complete control over the feature shape and design and I am ready for discussions and adjustments. I will try to make a few alternative solutions for my proposition, or even 2 different PRs. |
I found something exciting. Screen.Recording.2024-02-21.at.23.07.53.movDetails: PR with the raw code for the raised solution. Still a lot of work ahead;p |
Hey @Polkas. I liked this. I have one comment though before looking into the code, I feel that the "upload" functionality belongs more into the |
@pawelru, thanks for a quick replay. I am happy to hear you like the solution. Now, I will wait for others' opinions. @pawelru, your concerns about simple reporter buttons visible for each teal module are meaningful; you do not want to overcomplicate the teal UI. |
Hey @gogonzo and @donyunardi. |
Thanks for the update! |
@lcd2yyz thank you for your comments. @pawelru and @lcd2yyz, I think I found an excellent solution to make the simple reporter suited for teal without the need to influence the teal.reporter itself.
|
The isn't any 😉 What happens is we create an application hash based on the data and modules here and stamp snapshots with it, and then, when a snapshot is uploaded, we compare the stamp to the current app's id. It's not the be all and end all because we didn't figure out a way to properly incorporate the contents of remote data into the hash but it's quite simple. |
The update is ready for the review. I followed a simple design, which was evaluated positively in this issue. How it works, videos. teal: teal_load_reporter.movgeneral shiny app: general_load_reporter.mov |
I want to leave here an additional comment that my work is done as a collaboration of UCB company with Roche. |
We are not planning to load the Reporter or any other individual teal-component from the file. So far we enabled server-bookmarking which is able to restore previewer cards. To do so, just use |
teal.reporter is independent of teal. So I am surprised the teal feature impact the teal.reporter. Somebody can use teal.reporter in their regular shiny app. I am open to a discussion. I introduced many improvements not connected with loading reporter too. |
please consider that webassembly is more and more popular then a server side solution like proposed teal one will be not working. Please read about shinylive https://shiny.posit.co/py/docs/shinylive.html |
Yes, this is why we are going to discuss this again. Currently codelibrary(shiny)
library(teal.reporter)
library(ggplot2)
library(rtables)
library(DT)
library(bslib)
# any of c("add", "download", "load", "reset")
options("teal.reporter.simple_reporter_modules" = c("add", "download", "load", "reset"))
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(
bookmarkButton(),
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)
###
}
shinyApp(ui = ui, server = server, enableBookmarking = "server") In the next step we will research how app user can restore the whole app state using fileupload in the running session (which should cover web-assembly case) @Polkas We are having complex design discussions and we are happy to include you at some moment. Let's discuss this soon. |
Thank you for the invitation and the app example. I am very happy you want to invest more time in this topic. |
The possible solution to what I raised ( the TEMP files problem in the proposed server proposition) is to rewrite onBookmark and onRestored to save the Report (JSON + files) representation in |
Partially good point. Having just an "add button" is not enough because you need a process which access Reporter object (either download button or previewer module or any other module which access Reporter object). I disagree that teal_module generating card should have a download button to download the whole report (including other teal_module-s). Global actions should be performed globally (through previewer or other module exposed globally for all modules) There are multiple scenarios we can discuss:
|
You are welcome. I was happy to identify for you the server proposition is faulty:) |
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]>
linked to #80
We want to create a shiny module for the Archiver class and implement it in the vignettes.
The issue assume the Archiver class have to be adjusted when any additional functionalities are needed.
Remember it has to be applied for Previewer and single button functionality at the same time.
The best if the button functionality (like add card module) could be simply used in the Previewer directly.
I imagine something similar to Add Card module.
In future there will be possible many ways for storing the data please take it into account, now we will work with zip file with JSON and static files (png and Rds).
The text was updated successfully, but these errors were encountered: