Skip to content
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

Simple - Load Reporter - Inbuild #251

Merged
merged 37 commits into from
Apr 24, 2024

Conversation

Polkas
Copy link
Contributor

@Polkas Polkas commented Feb 19, 2024

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.

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 Support Load Reporter 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):

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):

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)

@Polkas Polkas mentioned this pull request Feb 19, 2024
@Polkas Polkas changed the title Archiver - Simple Archiver - Simple - Inbuild Feb 19, 2024
@Polkas Polkas changed the title Archiver - Simple - Inbuild Simple - Load Reporter - Inbuild Mar 8, 2024
@Polkas Polkas marked this pull request as ready for review March 9, 2024 19:23
@donyunardi donyunardi added this to the Release 0.4.0 milestone Mar 22, 2024
@gogonzo gogonzo self-assigned this Apr 10, 2024
@Polkas
Copy link
Contributor Author

Polkas commented Apr 12, 2024

@gogonzo done:)

Copy link
Contributor

@gogonzo gogonzo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice solution, we will need to make some changes on our side but in general it is fine. Please check out bookmark-restore of an app which had a Report loaded

Image shows an app which has been restored from a bookmarking

image

R/LoadReporterModule.R Outdated Show resolved Hide resolved
R/LoadReporterModule.R Outdated Show resolved Hide resolved
R/DownloadModule.R Show resolved Hide resolved
R/DownloadModule.R Show resolved Hide resolved
R/Reporter.R Outdated Show resolved Hide resolved
R/Reporter.R Outdated Show resolved Hide resolved
R/SimpleReporter.R Outdated Show resolved Hide resolved
R/LoadReporterModule.R Outdated Show resolved Hide resolved
R/LoadReporterModule.R Outdated Show resolved Hide resolved
R/LoadReporterModule.R Show resolved Hide resolved
@Polkas
Copy link
Contributor Author

Polkas commented Apr 14, 2024

I have to leave all print(cond) as the warning(cond) passes the error and the Shiny app crashes.

@gogonzo
Copy link
Contributor

gogonzo commented Apr 15, 2024

I have to leave all print(cond) as the warning(cond) passes the error and the Shiny app crashes.

What about message()?

@Polkas
Copy link
Contributor Author

Polkas commented Apr 15, 2024

I have to leave all print(cond) as the warning(cond) passes the error and the Shiny app crashes.

What about message()?

Unfortunately, a message() materializes the error and causes an app crash.

Copy link
Contributor

@gogonzo gogonzo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't render load button for now - proposed solution in the comment. Please update NEWS file and we will be very close to merge

R/Previewer.R Outdated Show resolved Hide resolved
@Polkas
Copy link
Contributor Author

Polkas commented Apr 18, 2024

Load is not included in the teal previewer now. Please consider additional teal.option for that, as my team will be happy to have the ability to add a load feature to teal.

I fixed bookmarking with the code below; please write if you do not want to merge it here

    session$onBookmark(function(state) {
      reporterdir <- file.path(state$dir, "reporter")
      dir.create(reporterdir)
      reporter$to_jsondir(reporterdir)
    })
    session$onRestored(function(state) {
      reporterdir <- file.path(state$dir, "reporter")
      reporter$from_jsondir(reporterdir)
    })

@gogonzo
Copy link
Contributor

gogonzo commented Apr 18, 2024

Load is not included in the teal previewer now. Please consider additional teal.option for that, as my team will be happy to have the ability to add a load feature to teal.

We don't yet decided about the recommended way in teal to load reporter this is why we don't want to have another "restore" functionality (after snapshot manager and bookmarking). teal::previewer_module will have load disabled but you will be able to create own previewer module and include it into teal::init(modules = list(..., <your previewer module>).

I fixed bookmarking with the code below; please write if you do not want to merge it here

    session$onBookmark(function(state) {
      reporterdir <- file.path(state$dir, "reporter")
      dir.create(reporterdir)
      reporter$to_jsondir(reporterdir)
    })
    session$onRestored(function(state) {
      reporterdir <- file.path(state$dir, "reporter")
      reporter$from_jsondir(reporterdir)
    })

Thanks, this is good 👍


I will make a final review next week as I have few days off. PR looks good both here and teal 👍 Thanks!

R/LoadReporterModule.R Outdated Show resolved Hide resolved
Co-authored-by: Dawid Kałędkowski <[email protected]>
Signed-off-by: Maciej Nasinski <[email protected]>
Copy link
Contributor

@gogonzo gogonzo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just these two and we are fine here

R/Previewer.R Outdated Show resolved Hide resolved
R/LoadReporterModule.R Outdated Show resolved Hide resolved
Co-authored-by: Dawid Kałędkowski <[email protected]>
Signed-off-by: Maciej Nasinski <[email protected]>
@Polkas
Copy link
Contributor Author

Polkas commented Apr 22, 2024

Danke, great collabo:)

@gogonzo
Copy link
Contributor

gogonzo commented Apr 24, 2024

@Polkas do the honors ;)

@Polkas
Copy link
Contributor Author

Polkas commented Apr 24, 2024

@Polkas do the honors ;)

Thanks:D but you have to be the one as “Only those with write access to this repository can merge pull requests.”
Once more thanks for the collab:)

@gogonzo gogonzo merged commit df1cade into insightsengineering:main Apr 24, 2024
23 checks passed
gogonzo added a commit to insightsengineering/teal that referenced this pull request Apr 24, 2024
connected with
insightsengineering/teal.reporter#251

My work is done as a collaboration of UCB company with Roche.
insightengineering developers can edit this PR.

Objective: Support the Load Report functionality for `TealRerportCard`
and `TealSliceBlock`

- Update the `to_list` and `from_list` `TealSliceBlock` methods. They
operate on a string output.
- Export `TealSliceBlock` which is needed for the teal.reporter
`ReportCard`.
- `set_id` for the `Reporter` so we protect that it can be reloaded only
for the same teal app.
- Fix the wrong docs.

---------

Signed-off-by: Maciej Nasinski <[email protected]>
Signed-off-by: Dawid Kałędkowski <[email protected]>
Co-authored-by: Dawid Kałędkowski <[email protected]>
Co-authored-by: Dawid Kałędkowski <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Create and implement a shiny module for Archiver class
3 participants