Skip to content

Conversation

@averissimo
Copy link
Contributor

@averissimo averissimo commented Apr 14, 2025

Pull Request

Fixes #1511

Changes description

  • Captures errors in teal_transform_data on initialization
    • Which should only be applicable to decorators
  • Error is propagated downstream
🤖 Example app
options(
  teal.log_level = "ERROR",
  teal.show_js_log = TRUE,
  # teal.bs_theme = bslib::bs_theme(version = 5),
  shiny.bookmarkStore = "server"
)

pkgload::load_all("teal")

tm_decorated_plot <- function(label = "module", transformators = list(), decorators = list(), datanames = "all") {
  checkmate::assert_list(decorators, "teal_transform_module")
  module(
    label = label,
    ui = function(id, decorators) {
      ns <- NS(id)
      div(
        selectInput(ns("dataname"), label = "select dataname", choices = NULL, multiple = TRUE),
        selectInput(ns("x"), label = "select x", choices = NULL, multiple = TRUE),
        selectInput(ns("y"), label = "select y", choices = NULL, multiple = TRUE),
        ui_transform_teal_data(ns("decorate"), transformators = decorators),
        plotOutput(ns("plot")),
        verbatimTextOutput(ns("text"))
      )
    },
    server = function(id, data, decorators) {
      moduleServer(id, function(input, output, session) {
        observeEvent(data(), {
          dataname <- if (length(input$dataname)) input$dataname else names(data())[1]
          updateSelectInput(inputId = "dataname", choices = names(data()), selected = dataname)
        })

        observeEvent(input$dataname, {
          req(input$dataname)
          updateSelectInput(inputId = "x", choices = colnames(data()[[input$dataname]]))
          updateSelectInput(inputId = "y", choices = colnames(data()[[input$dataname]]))
        })

        dataname <- reactive(req(input$dataname))
        x <- reactive({
          req(input$x, input$x %in% colnames(data()[[dataname()]]))
          input$x
        })

        y <- reactive({
          req(input$y, input$y %in% colnames(data()[[dataname()]]))
          input$y
        })
        plot_data <- reactive({
          # todo: make sure it triggers once on init
          #       and once on change of its input and once on change in previous stages
          req(dataname(), x(), y())
          Sys.sleep(5) # to mimic relatively long computation
          within(data(),
            {
              plot <- ggplot2::ggplot(dataname, ggplot2::aes(x = x, y = y)) +
                ggplot2::geom_point()
            },
            dataname = as.name(dataname()),
            x = as.name(x()),
            y = as.name(y())
          )
        })

        plot_data_decorated_no_print <- srv_transform_teal_data(
          "decorate",
          data = plot_data,
          transformators = decorators
        )
        plot_data_decorated <- reactive(
          within(req(plot_data_decorated_no_print()), expr = plot)
        )

        plot_r <- reactive({
          plot_data_decorated()[["plot"]]
        })

        output$plot <- renderPlot(plot_r())
        output$text <- renderText({
          teal.code::get_code(req(plot_data_decorated()))
        })
      })
    },
    ui_args = list(decorators = decorators),
    server_args = list(decorators = decorators),
    datanames = datanames,
    transformators = transformators
  )
}

make_data <- function(datanames = c("ADSL", "ADTTE")) {
  data_obj <- teal.data::teal_data()
  if ("ADSL" %in% datanames) {
    data_obj <- within(data_obj, ADSL <- teal.data::rADSL)
  }
  if ("ADTTE" %in% datanames) {
    data_obj <- within(data_obj, ADTTE <- teal.data::rADTTE)
  }
  join_keys(data_obj) <- default_cdisc_join_keys[datanames]
  data_obj
}

data <- teal_data_module(
  once = FALSE,
  ui = function(id) {
    ns <- NS(id)
    tagList(
      selectizeInput(
        ns("errortype"),
        label = "Error Type",
        choices = c(
          "ok", "insufficient datasets", "no data",
          "qenv.error", "error in reactive", "validate error", "silent.shiny.error", "not a reactive"
        )
      )
    )
  },
  server = function(id, ...) {
    moduleServer(id, function(input, output, session) {
      logger::log_trace("example_module_transform2 initializing.")
      reactive({
        switch(input$errortype,
          ok = make_data(),
          `insufficient datasets` = make_data(datanames = "ADSL"),
          `no data` = teal_data(),
          qenv.error = within(data(), stop("\nthis is qenv.error in teal_data_module\n")),
          `error in reactive` = stop("\nerror in a reactive in teal_data_module\n"),
          `validate error` = validate(need(FALSE, "\nvalidate error in teal_data_module\n")),
          `silent.shiny.error` = req(FALSE)
        )
      })
    })
  }
)

trans <- teal_transform_module(
  ui = function(id) {
    ns <- NS(id)
    tagList(
      selectizeInput(
        ns("errortype"),
        label = "Error Type",
        choices = c(
          "ok", "insufficient datasets", "no data",
          "qenv.error", "error in reactive", "validate error", "silent.shiny.error", "not a reactive"
        )
      )
    )
  },
  server = function(id, data) {
    moduleServer(id, function(input, output, session) {
      logger::log_trace("example_module_transform2 initializing.")
      # to check if data with error causes problems
      data2 <- reactive(data())
      data3 <- eventReactive(data2(), data2())
      observeEvent(data3(), {
        # do nothing
      })
      reactive({
        # notes: make sure it:
        #        - triggers once on init
        #        - once on change of its input
        #        - once on change in data input
        new_data <- switch(input$errortype,
          ok = data3(),
          `insufficient datasets` = data3()["ADSL"],
          `no data` = teal_data(),
          qenv.error = within(teal_data(), stop("\nthis is qenv.error in teal_transform_module\n")),
          `error in reactive` = stop("\nerror in a reactive in teal_transform_module\n"),
          `validate error` = validate(need(FALSE, "\nvalidate error in teal_transform_module\n")),
          `silent.shiny.error` = req(FALSE)
        )
        new_data
      })
    })
  }
)

trans_empty <- teal_transform_module(
  server = function(id, data) {
    moduleServer(id, function(input, output, session) {
      reactive({
        validate(need(nrow(data()$ADSL) > 250, "ADSL needs 250 rows"))
        data()
      })
    })
  }
)

decor <- teal_transform_module(
  label = "X-axis decorator",
  ui = function(id) {
    ns <- NS(id)
    tagList(
      selectizeInput(
        ns("action"),
        label = "Action type",
        choices = c(
          "nothing", "decorate", "no data",
          "qenv.error", "error in reactive", "validate error", "silent.shiny.error", "not a reactive"
        )
      )
    )
  },
  server = function(id, data) {
    moduleServer(id, function(input, output, session) {
      logger::log_trace("example_module_transform2 initializing.")
      reactive({
        switch(input$action,
          nothing = data(),
          `decorate` = data() |> within(plot <- plot + ggplot2::ggtitle("Decorated Title")),
          `no data` = teal_data(),
          qenv.error = within(teal_data(), stop("\nthis is qenv.error in teal_transform_module\n")),
          `error in reactive` = stop("\nerror in a reactive in teal_transform_module\n"),
          `validate error` = validate(need(FALSE, "\nvalidate error in teal_transform_module\n")),
          `silent.shiny.error` = req(FALSE)
        )
      })
    })
  }
)

app <- teal::init(
  data = data,
  modules = modules(
    modules(
      label = "first tab",
      tm_decorated_plot(
        "mod-2",
        transformators = list(trans, trans, trans_empty),
        decorators = list(decor, decor),
        datanames = c("ADSL", "ADTTE")
      ),
      example_module()
    )
  ),
  filter = teal_slices(
    teal_slice("ADSL", "SEX"),
    teal_slice("ADSL", "AGE", selected = c(18L, 65L)),
    teal_slice("ADTTE", "PARAMCD", selected = "CRSD"),
    include_varnames = list(
      ADSL = c("SEX", "AGE")
    )
  )
)

runApp(app)

image

@averissimo averissimo marked this pull request as ready for review May 7, 2025 13:47
@averissimo
Copy link
Contributor Author

This PR will minimize the srv_decorate_teal_data function definition repeated in {tmc} and {tmg}.

@github-actions
Copy link
Contributor

github-actions bot commented May 7, 2025

Unit Test Performance Difference

Test Suite $Status$ Time on main $±Time$ $±Tests$ $±Skipped$ $±Failures$ $±Errors$
module_teal 💚 $119.22$ $-1.10$ $+1$ $0$ $0$ $0$
shinytest2-disable_report 💀 $0.39$ $-0.39$ $-4$ $-4$ $0$ $0$
shinytest2-disable_src 💀 $0.20$ $-0.20$ $-2$ $-2$ $0$ $0$
shinytest2-show-rcode 💀 $0.28$ $-0.28$ $-3$ $-3$ $0$ $0$
shinytest2-teal_modifiers 💀 $0.56$ $-0.56$ $-7$ $-7$ $0$ $0$
Additional test case details
Test Suite $Status$ Time on main $±Time$ Test Case
shinytest2-decorators 👶 $+0.03$ unnamed
shinytest2-disable_report 💀 $0.10$ $-0.10$ Add_to_report_button_is_not_disabled_by_default.
shinytest2-disable_report 💀 $0.10$ $-0.10$ Report_button_is_active_on_a_nested_module_by_default
shinytest2-disable_report 💀 $0.10$ $-0.10$ Report_button_is_disabled_on_a_module_changed_by_disable_report_
shinytest2-disable_report 💀 $0.09$ $-0.09$ Report_button_is_disabled_on_nested_modules_changed_by_disable_report_
shinytest2-disable_src 💀 $0.10$ $-0.10$ Show_R_Code_button_is_disabled_on_a_module
shinytest2-disable_src 💀 $0.11$ $-0.11$ Show_R_Code_is_disabled_on_nested_modules_changed_with_disable_src
shinytest2-reporter 👶 $+0.01$ unnamed
shinytest2-show-rcode 💀 $0.09$ $-0.09$ e2e_Module_with_Show_R_Code_has_code
shinytest2-show-rcode 💀 $0.09$ $-0.09$ e2e_Module_with_Show_R_Code_has_modal_with_two_dismiss_and_two_copy_to_clipboard_buttons
shinytest2-show-rcode 💀 $0.10$ $-0.10$ e2e_Module_with_Show_R_Code_initializes_with_visible_button
shinytest2-teal_data_module 👶 $+0.02$ unnamed
shinytest2-teal_modifiers 💀 $0.08$ $-0.08$ e2e_add_landing_modal_displays_landing_modal_on_app_startup
shinytest2-teal_modifiers 💀 $0.08$ $-0.08$ e2e_add_landing_modal_modal_can_be_dismissed
shinytest2-teal_modifiers 💀 $0.09$ $-0.09$ e2e_combined_modifiers_displays_all_customizations_when_chained_together
shinytest2-teal_modifiers 💀 $0.08$ $-0.08$ e2e_modify_footer_displays_custom_footer_in_the_app
shinytest2-teal_modifiers 💀 $0.08$ $-0.08$ e2e_modify_header_displays_custom_header_in_the_app
shinytest2-teal_modifiers 💀 $0.08$ $-0.08$ e2e_modify_title_sets_custom_title_in_the_page_title_head_title_displays_custom_favicon_in_the_app
shinytest2-teal_modifiers 💀 $0.08$ $-0.08$ e2e_modify_title_sets_custom_title_in_the_page_title_head_title_displays_custom_title_in_the_app

Results for commit 2b15f07

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Contributor

github-actions bot commented May 7, 2025

badge

Code Coverage Summary

Filename                          Stmts    Miss  Cover    Missing
------------------------------  -------  ------  -------  --------------------------------------------------------------------------------------------------------------------------------------
R/after.R                            59      21  64.41%   42-52, 64, 69, 77-79, 81-89, 100, 104-105
R/checkmate.R                        24       0  100.00%
R/dummy_functions.R                  61       2  96.72%   44, 46
R/include_css_js.R                   11       0  100.00%
R/init.R                            152       1  99.34%   299
R/landing_popup_module.R             34      10  70.59%   44-53
R/module_bookmark_manager.R         153     117  23.53%   54-58, 78-133, 138-139, 151, 198, 233-310
R/module_data_summary.R             177       8  95.48%   40, 50, 205, 236-240
R/module_filter_data.R               64       0  100.00%
R/module_filter_manager.R           229      50  78.17%   72-81, 89-94, 107-111, 116-117, 290-313, 339, 366, 378, 385-386
R/module_init_data.R                 84       6  92.86%   38-43
R/module_nested_tabs.R              364      37  89.84%   163, 267-282, 302-306, 324, 361, 472-475, 479-482, 486-489, 534
R/module_session_info.R              18       0  100.00%
R/module_snapshot_manager.R         271     194  28.41%   103-112, 120-144, 163-164, 181-210, 214-229, 231-238, 245-275, 279, 283-287, 289-295, 298-311, 314-322, 352-366, 369-380, 383-397, 410
R/module_source_code.R               69       0  100.00%
R/module_teal_data.R                149      76  48.99%   43-149
R/module_teal_lockfile.R            131      53  59.54%   45-57, 60-62, 76, 86-88, 100-102, 110-119, 122, 124, 126-127, 142-146, 161-162, 177-186
R/module_teal_reporter.R            122       9  92.62%   60, 77-78, 81, 98, 128, 142, 144, 158
R/module_teal_with_splash.R          33      33  0.00%    24-61
R/module_teal.R                     213      28  86.85%   130, 134-135, 145-146, 186, 204-220, 222, 255-256, 263-264
R/module_transform_data.R           134       7  94.78%   56, 116, 149-153
R/modules.R                         291      51  82.47%   170-174, 229-232, 356-376, 384, 390, 567-573, 586-594, 609-624
R/reporter_previewer_module.R        41      41  0.00%    22-85
R/show_rcode_modal.R                 31      31  0.00%    17-49
R/tdata.R                            14      14  0.00%    19-61
R/teal_data_module-eval_code.R       23       0  100.00%
R/teal_data_module-within.R           7       0  100.00%
R/teal_data_module.R                 20       0  100.00%
R/teal_data_utils.R                  49       0  100.00%
R/teal_modifiers.R                   57       0  100.00%
R/teal_slices-store.R                29       0  100.00%
R/teal_slices.R                      63       0  100.00%
R/teal_transform_module.R            45       0  100.00%
R/TealAppDriver.R                   297     297  0.00%    50-620
R/utils.R                           291      48  83.51%   402-451, 539-548
R/validate_inputs.R                  32       0  100.00%
R/validations.R                      58      37  36.21%   114-392
R/zzz.R                              19      15  21.05%   4-22
TOTAL                              3919    1186  69.74%

Diff against main

Filename                     Stmts    Miss  Cover
-------------------------  -------  ------  -------
R/module_nested_tabs.R          -7       0  -0.19%
R/module_transform_data.R      +18      +1  -0.05%
TOTAL                          +11      +1  +0.06%

Results for commit: 925f581

Minimum allowed coverage is 80%

♻️ This comment has been updated with latest results

@github-actions
Copy link
Contributor

github-actions bot commented May 7, 2025

Unit Tests Summary

  1 files   25 suites   2m 25s ⏱️
315 tests 265 ✅ 50 💤 0 ❌
522 runs  468 ✅ 54 💤 0 ❌

Results for commit 925f581.

♻️ This comment has been updated with latest results.

averissimo added a commit that referenced this pull request May 8, 2025
@averissimo
Copy link
Contributor Author

averissimo commented May 9, 2025

📑 Journal (rolling update):

  1. (non-documented)
  2. Use validate(need(...)) instead of stop() for better shiny handling in module
  3. Q: Keep "trigger on success" in summary data?
    • Seems to me that this should be less reactive to errors in transforms

Note: UI changes about placement and what should show where should be discussed #1509

Latest screenshot

image

Older screenshots _(nothing to see here yet)_

)

summary_table <- srv_data_summary("data_summary", module_teal_data)
summary_table <- srv_data_summary("data_summary", summary_data) # Only updates on success
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this should not be in error state or hidden when transformators introduce an error.

The alternative is to use module_teal_data and remove the (newly) introduced summary_data as below.

Q: WDYT?

image

Copy link
Contributor

Choose a reason for hiding this comment

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

I like that there is not data summary as it cannot be provided, but maybe it was hidden because this makes the different order pop up the error message more for the users. But I prefer to not have hidding elements messing with the layout of the app and leave the section even if empty.

Copy link
Contributor

@llrs-roche llrs-roche left a comment

Choose a reason for hiding this comment

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

I left a couple of comments as I couldn't run yet the branch. The most important is to make it easier to pull the branch. I had messed my git config but I fixed it.

It would also great if the example app at the beginning of the PR could be updated to really open an app (to avoid me messing it and test different things)

)

summary_table <- srv_data_summary("data_summary", module_teal_data)
summary_table <- srv_data_summary("data_summary", summary_data) # Only updates on success
Copy link
Contributor

Choose a reason for hiding this comment

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

I like that there is not data summary as it cannot be provided, but maybe it was hidden because this makes the different order pop up the error message more for the users. But I prefer to not have hidding elements messing with the layout of the app and leave the section even if empty.

@llrs-roche llrs-roche self-assigned this May 20, 2025
Copy link
Contributor

@llrs-roche llrs-roche left a comment

Choose a reason for hiding this comment

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

Code looks good I left a couple of comments but I think this resolves the issue:

2025-05-23_15-00-44.mp4

I think the code on "example app" is not the one that generates the leading screenshots. But if there is further testing to be done let me know, the checks locally (and remotely) run well

Comment on lines +346 to +347
!inherits(try(transformed_teal_data(), silent = TRUE), "teal_data") &&
inherits(filtered_teal_data(), "teal_data")
Copy link
Contributor

Choose a reason for hiding this comment

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

As transformed_teal_data depends on the output of filtered_teal_data it can be omitted and I think we would get the same result (at least on the app on the issue it works as before).

Suggested change
!inherits(try(transformed_teal_data(), silent = TRUE), "teal_data") &&
inherits(filtered_teal_data(), "teal_data")
!inherits(try(transformed_teal_data(), silent = TRUE), "teal_data")

# Disable all elements if original data is not yet a teal_data
observeEvent(data_original_handled(), {
shinyjs::toggleState(
"wrapper_panel",
Copy link
Contributor

Choose a reason for hiding this comment

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

I think there is a div with id = "wrapper" for modules tabs, but where does this _panel come from? Would it work on teal modules used directly on shiny? Just thinking aloud after resolving an issue coming from an id not matching between different parts.

Comment on lines +111 to +112
data_handled()
data_original_handled()
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice trick, never thought about adding two expressions as a single event. Would be the same as to have only data_handled()? Or in reversed order?

@averissimo
Copy link
Contributor Author

@llrs-roche this is not forgotten, @gogonzo wants to add some functionality on top after we get an understanding of teal_report new data structure

@llrs-roche
Copy link
Contributor

I was checking the board and I don't know were we stay on this PR/feature. The coments I left on the review are minor and at the time I thought it could be merged. Do we merge this @gogonzo ?

@gogonzo
Copy link
Contributor

gogonzo commented Oct 14, 2025

I was checking the board and I don't know were we stay on this PR/feature. The comments I left on the review are minor and at the time I thought it could be merged. Do we merge this @gogonzo ?

PR was not enough as reason of failure lays deeper. I'd expect making a solution which can standardize error handling and would rather remove lines than add. There was one PR at that time I raised but I'm sceptical if anybody will review that anytime soon. I don't have capacity to handle this topic now :/

@gogonzo
Copy link
Contributor

gogonzo commented Oct 14, 2025

I was checking the board and I don't know were we stay on this PR/feature. The comments I left on the review are minor and at the time I thought it could be merged. Do we merge this @gogonzo ?

PR was not enough as reason of failure lays deeper. I'd expect making a solution which can standardize error handling and would rather remove lines than add. There was one PR at that time I raised but I'm sceptical if anybody will review that anytime soon. I don't have capacity to handle this topic now :/

Sorry @llrs-roche I confused this PR with #1509
I'm fine with your merge decision if everything works. Please check-out an app below where you can test various failures config.

teal-app-failures
options(
  teal.log_level = "ERROR",
  teal.show_js_log = TRUE,
  # teal.bs_theme = bslib::bs_theme(version = 5),
  shiny.bookmarkStore = "server"
)

pkgload::load_all("teal")

tm_decorated_plot <- function(label = "module", transformators = list(), decorators = list(), datanames = "all") {
  checkmate::assert_list(decorators, "teal_transform_module")
  module(
    label = label,
    ui = function(id, decorators) {
      ns <- NS(id)
      div(
        selectInput(ns("dataname"), label = "select dataname", choices = NULL, multiple = TRUE),
        selectInput(ns("x"), label = "select x", choices = NULL, multiple = TRUE),
        selectInput(ns("y"), label = "select y", choices = NULL, multiple = TRUE),
        ui_transform_teal_data(ns("decorate"), transformators = decorators),
        plotOutput(ns("plot")),
        verbatimTextOutput(ns("text"))
      )
    },
    server = function(id, data, decorators) {
      moduleServer(id, function(input, output, session) {
        observeEvent(data(), {
          dataname <- if (length(input$dataname)) input$dataname else names(data())[1]
          updateSelectInput(inputId = "dataname", choices = names(data()), selected = dataname)
        })

        observeEvent(input$dataname, {
          req(input$dataname)
          updateSelectInput(inputId = "x", choices = colnames(data()[[input$dataname]]))
          updateSelectInput(inputId = "y", choices = colnames(data()[[input$dataname]]))
        })

        dataname <- reactive(req(input$dataname))
        x <- reactive({
          req(input$x, input$x %in% colnames(data()[[dataname()]]))
          input$x
        })

        y <- reactive({
          req(input$y, input$y %in% colnames(data()[[dataname()]]))
          input$y
        })
        plot_data <- reactive({
          # todo: make sure it triggers once on init
          #       and once on change of its input and once on change in previous stages
          req(dataname(), x(), y())
          Sys.sleep(5) # to mimic relatively long computation
          within(data(),
            {
              plot <- ggplot2::ggplot(dataname, ggplot2::aes(x = x, y = y)) +
                ggplot2::geom_point()
            },
            dataname = as.name(dataname()),
            x = as.name(x()),
            y = as.name(y())
          )
        })

        plot_data_decorated_no_print <- srv_transform_teal_data(
          "decorate",
          data = plot_data,
          transformators = decorators
        )
        plot_data_decorated <- reactive(
          within(req(plot_data_decorated_no_print()), expr = plot)
        )

        plot_r <- reactive({
          plot_data_decorated()[["plot"]]
        })

        output$plot <- renderPlot(plot_r())
        output$text <- renderText({
          teal.code::get_code(req(plot_data_decorated()))
        })
      })
    },
    ui_args = list(decorators = decorators),
    server_args = list(decorators = decorators),
    datanames = datanames,
    transformators = transformators
  )
}

make_data <- function(datanames = c("ADSL", "ADTTE")) {
  data_obj <- teal.data::teal_data()
  if ("ADSL" %in% datanames) {
    data_obj <- within(data_obj, ADSL <- teal.data::rADSL)
  }
  if ("ADTTE" %in% datanames) {
    data_obj <- within(data_obj, ADTTE <- teal.data::rADTTE)
  }
  join_keys(data_obj) <- default_cdisc_join_keys[datanames]
  data_obj
}

data <- teal_data_module(
  once = FALSE,
  ui = function(id) {
    ns <- NS(id)
    tagList(
      selectizeInput(
        ns("errortype"),
        label = "Error Type",
        choices = c(
          "ok", "insufficient datasets", "no data",
          "qenv.error", "error in reactive", "validate error", "silent.shiny.error", "not a reactive"
        )
      )
    )
  },
  server = function(id, ...) {
    moduleServer(id, function(input, output, session) {
      logger::log_trace("example_module_transform2 initializing.")
      reactive({
        switch(input$errortype,
          ok = make_data(),
          `insufficient datasets` = make_data(datanames = "ADSL"),
          `no data` = teal_data(),
          qenv.error = within(data(), stop("\nthis is qenv.error in teal_data_module\n")),
          `error in reactive` = stop("\nerror in a reactive in teal_data_module\n"),
          `validate error` = validate(need(FALSE, "\nvalidate error in teal_data_module\n")),
          `silent.shiny.error` = req(FALSE)
        )
      })
    })
  }
)

trans <- teal_transform_module(
  ui = function(id) {
    ns <- NS(id)
    tagList(
      selectizeInput(
        ns("errortype"),
        label = "Error Type",
        choices = c(
          "ok", "insufficient datasets", "no data",
          "qenv.error", "error in reactive", "validate error", "silent.shiny.error", "not a reactive"
        )
      )
    )
  },
  server = function(id, data) {
    moduleServer(id, function(input, output, session) {
      logger::log_trace("example_module_transform2 initializing.")
      # to check if data with error causes problems
      data2 <- reactive(data())
      data3 <- eventReactive(data2(), data2())
      observeEvent(data3(), {
        # do nothing
      })
      reactive({
        # notes: make sure it:
        #        - triggers once on init
        #        - once on change of its input
        #        - once on change in data input
        new_data <- switch(input$errortype,
          ok = data3(),
          `insufficient datasets` = data3()["ADSL"],
          `no data` = teal_data(),
          qenv.error = within(teal_data(), stop("\nthis is qenv.error in teal_transform_module\n")),
          `error in reactive` = stop("\nerror in a reactive in teal_transform_module\n"),
          `validate error` = validate(need(FALSE, "\nvalidate error in teal_transform_module\n")),
          `silent.shiny.error` = req(FALSE)
        )
        new_data
      })
    })
  }
)

trans_empty <- teal_transform_module(
  server = function(id, data) {
    moduleServer(id, function(input, output, session) {
      reactive({
        validate(need(nrow(data()$ADSL) > 250, "ADSL needs 250 rows"))
        data()
      })
    })
  }
)

decor <- teal_transform_module(
  label = "X-axis decorator",
  ui = function(id) {
    ns <- NS(id)
    tagList(
      selectizeInput(
        ns("action"),
        label = "Action type",
        choices = c(
          "nothing", "decorate", "no data",
          "qenv.error", "error in reactive", "validate error", "silent.shiny.error", "not a reactive"
        )
      )
    )
  },
  server = function(id, data) {
    moduleServer(id, function(input, output, session) {
      logger::log_trace("example_module_transform2 initializing.")
      reactive({
        switch(input$action,
          nothing = data(),
          `decorate` = data() |> within(plot <- plot + ggplot2::ggtitle("Decorated Title")),
          `no data` = teal_data(),
          qenv.error = within(teal_data(), stop("\nthis is qenv.error in teal_transform_module\n")),
          `error in reactive` = stop("\nerror in a reactive in teal_transform_module\n"),
          `validate error` = validate(need(FALSE, "\nvalidate error in teal_transform_module\n")),
          `silent.shiny.error` = req(FALSE)
        )
      })
    })
  }
)

app <- teal::init(
  data = data,
  modules = modules(
    modules(
      label = "first tab",
      tm_decorated_plot(
        "mod-2",
        transformators = list(trans, trans, trans_empty),
        decorators = list(decor, decor),
        datanames = c("ADSL", "ADTTE")
      ),
      example_module()
    )
  ),
  filter = teal_slices(
    teal_slice("ADSL", "SEX"),
    teal_slice("ADSL", "AGE", selected = c(18L, 65L)),
    teal_slice("ADTTE", "PARAMCD", selected = "CRSD"),
    include_varnames = list(
      ADSL = c("SEX", "AGE")
    )
  )
)

runApp(app)

@llrs-roche
Copy link
Contributor

Thanks to the app, I reviewed this again:

Data summary is present even when there is an error on qenv:

Screenshot

image

Errors are duplicated for each transform module:

Screenshot

image

Error messages are different on the UI between the transform Data panel and the main panel:

Screenshot

image

Initial error pops a modal and then the summary data is different from what the error says:

Screenshots

image

In conclusion. it fixes the second issue (Last state is shown, be it an error or a plot) but fails to provide "consistent error messages in decorators".
Not merging for now.

Copy link
Contributor

@llrs-roche llrs-roche left a comment

Choose a reason for hiding this comment

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

See comment above

@averissimo
Copy link
Contributor Author

averissimo commented Oct 31, 2025

Summary of the meeting with @llrs-roche

Data summary is present even when there is an error on qenv:

It makes sense, the summary table should be reactive

Action: grey out the "no datasets to show" in summary table

Errors are duplicated for each transform module:

Plan: only show in transformators if the error occurred there

Error messages are different on the UI between the transform Data panel and the main panel:

The error should also appear in the main module UI

note from Lluis: see if namespace is wrong

Initial error pops a modal and then the summary data is different from what the error says:

Bug when there is no UI for the transfomators and it enters an error state

Expected result is for the pseudo-UI to be visible with the error showing


Extra mile would be to modify the notification when filters are removed due to incompatible datanames.

  • "Some filters..." only when they are partially removed
  • "All filters..." when all are removed
image

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.

[Bug]: decoration error state changes with first good run

4 participants