diff --git a/.lintr b/.lintr index 113ca30c1..87688e231 100644 --- a/.lintr +++ b/.lintr @@ -1,6 +1,5 @@ linters: linters_with_defaults( line_length_linter = line_length_linter(120), - cyclocomp_linter = NULL, object_usage_linter = NULL, object_name_linter = object_name_linter(styles = c("snake_case", "symbols"), regexes = c(ANL = "^ANL_?[0-9A-Z_]*$", ADaM = "^r?AD[A-Z]{2,3}_?[0-9]*$")), indentation_linter = NULL diff --git a/NEWS.md b/NEWS.md index d0008ce93..c22cfb6bb 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,14 @@ # teal.modules.general 0.4.1.9015 +### Breaking changes + +- Removed the `table` object decoration in `tm_missing_data` and `tm_outliers` (#899). +- Removed the `summary_table` and `test_table` object decoration in `tm_g_distribution` (#897). + +### Bug fixes + +- Fixes "Add to Report" functionality in `tm_outliers`, `tm_missing_data` and `tm_g_distribution` modules (#899 and #897). + # teal.modules.general 0.4.1 ### Bug fixes diff --git a/R/tm_g_bivariate.R b/R/tm_g_bivariate.R index 2700684e0..ced291b6f 100644 --- a/R/tm_g_bivariate.R +++ b/R/tm_g_bivariate.R @@ -561,7 +561,7 @@ srv_g_bivariate <- function(id, datasets = data ) qenv <- reactive( - teal.code::eval_code(data(), 'library("ggplot2");library("dplyr");library("teal.modules.general")') # nolint quotes + teal.code::eval_code(data(), 'library("ggplot2");library("dplyr")') # nolint: quotes. ) anl_merged_q <- reactive({ diff --git a/R/tm_g_distribution.R b/R/tm_g_distribution.R index 8b43453e3..4796d5669 100644 --- a/R/tm_g_distribution.R +++ b/R/tm_g_distribution.R @@ -31,8 +31,6 @@ #' This module generates the following objects, which can be modified in place using decorators:: #' - `histogram_plot` (`ggplot`) #' - `qq_plot` (`ggplot`) -#' - `summary_table` (`datatables` created with [DT::datatable()]) -#' - `test_table` (`datatables` created with [DT::datatable()]) #' #' A Decorator is applied to the specific output using a named list of `teal_transform_module` objects. #' The name of this list corresponds to the name of the output to which the decorator is applied. @@ -43,9 +41,7 @@ #' ..., # arguments for module #' decorators = list( #' histogram_plot = teal_transform_module(...), # applied only to `histogram_plot` output -#' qq_plot = teal_transform_module(...), # applied only to `qq_plot` output -#' summary_table = teal_transform_module(...), # applied only to `summary_table` output -#' test_table = teal_transform_module(...) # applied only to `test_table` output +#' qq_plot = teal_transform_module(...) # applied only to `qq_plot` output #' ) #' ) #' ``` @@ -194,8 +190,7 @@ tm_g_distribution <- function(label = "Distribution Module", checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) - available_decorators <- c("histogram_plot", "qq_plot", "test_table", "summary_table") - assert_decorators(decorators, names = available_decorators) + assert_decorators(decorators, names = c("histogram_plot", "qq_plot")) # End of assertions @@ -322,14 +317,6 @@ ui_distribution <- function(id, ...) { collapsed = FALSE ) ), - ui_decorate_teal_data( - ns("d_summary"), - decorators = select_decorators(args$decorators, "summary_table") - ), - ui_decorate_teal_data( - ns("d_test"), - decorators = select_decorators(args$decorators, "test_table") - ), conditionalPanel( condition = paste0("input['", ns("main_type"), "'] == 'Density'"), bslib::accordion_panel( @@ -1284,24 +1271,31 @@ srv_distribution <- function(id, # Summary table listing has to be created separately to allow for qenv join output_summary_q <- reactive({ if (iv_r()$is_valid()) { - within(common_q(), summary_table <- DT::datatable(summary_table_data)) + within(common_q(), { + summary_table <- rtables::df_to_tt(summary_table_data) + summary_table + }) } else { - within(common_q(), summary_table <- DT::datatable(summary_table_data[0L, ])) + within( + common_q(), + summary_table <- rtables::rtable(header = rtables::rheader(colnames(summary_table_data))) + ) } }) output_test_q <- reactive({ # wrapped in if since could lead into validate error - we do want to continue test_q_out <- try(test_q(), silent = TRUE) - if (!inherits(test_q_out, c("try-error", "error"))) { - c( + if (inherits(test_q_out, c("try-error", "error"))) { + within( common_q(), - within(test_q_out, { - test_table <- DT::datatable(test_table_data) - }) + test_table <- rtables::rtable(header = rtables::rheader("No data available in table"), rtables::rrow()) ) } else { - within(common_q(), test_table <- DT::datatable(data.frame(missing = character(0L)))) + within(c(common_q(), test_q_out), { + test_table <- rtables::df_to_tt(test_table_data) + test_table + }) } }) @@ -1319,54 +1313,47 @@ srv_distribution <- function(id, expr = print(qq_plot) ) - decorated_output_summary_q <- srv_decorate_teal_data( - "d_summary", - data = output_summary_q, - decorators = select_decorators(decorators, "summary_table"), - expr = summary_table - ) - - decorated_output_test_q <- srv_decorate_teal_data( - "d_test", - data = output_test_q, - decorators = select_decorators(decorators, "test_table"), - expr = test_table - ) - decorated_output_q <- reactive({ tab <- req(input$tabs) # tab is NULL upon app launch, hence will crash without this statement test_q_out <- try(test_q(), silent = TRUE) - decorated_test_q_out <- if (inherits(test_q_out, c("try-error", "error"))) { - teal.code::qenv() - } else { - decorated_output_test_q() - } + test_q_out <- output_test_q() out_q <- switch(tab, Histogram = decorated_output_dist_q(), QQplot = decorated_output_qq_q() ) - c(out_q, decorated_output_summary_q(), decorated_test_q_out) + c(out_q, output_summary_q(), test_q_out) }) dist_r <- reactive(req(decorated_output_dist_q())[["histogram_plot"]]) qq_r <- reactive(req(decorated_output_qq_q())[["qq_plot"]]) - output$summary_table <- DT::renderDataTable( - expr = decorated_output_summary_q()[["summary_table"]], - options = list( - autoWidth = TRUE, - columnDefs = list(list(width = "200px", targets = "_all")) - ), - rownames = FALSE - ) + summary_r <- reactive({ + q <- req(output_summary_q()) + + list( + html = DT::datatable( + q[["summary_table_data"]], + options = list( + autoWidth = TRUE, + columnDefs = list(list(width = "200px", targets = "_all")) + ), + rownames = FALSE + ), + report = q[["summary_table"]] + ) + }) + + output$summary_table <- DT::renderDataTable(summary_r()[["html"]]) tests_r <- reactive({ - req(iv_r()$is_valid()) - teal::validate_inputs(iv_r_dist()) - req(test_q()) # Ensure original errors are displayed - decorated_output_test_q()[["test_table"]] + q <- req(output_test_q()) + + list( + html = DT::datatable(q[["test_table_data"]]), + report = q[["test_table"]] + ) }) pws1 <- teal.widgets::plot_with_settings_srv( @@ -1385,7 +1372,7 @@ srv_distribution <- function(id, brushing = FALSE ) - output$t_stats <- DT::renderDataTable(expr = tests_r()) + output$t_stats <- DT::renderDataTable(tests_r()[["html"]]) # Render R code. source_code_r <- reactive(teal.code::get_code(req(decorated_output_q()))) @@ -1412,11 +1399,11 @@ srv_distribution <- function(id, card$append_plot(qq_r(), dim = pws2$dim()) } card$append_text("Statistics table", "header3") - card$append_table(decorated_output_summary_q()[["summary_table"]]) + card$append_table(summary_r()[["report"]]) tests_error <- tryCatch(expr = tests_r(), error = function(e) "error") - if (inherits(tests_error, "data.frame")) { + if (!identical(tests_error, "error")) { card$append_text("Tests table", "header3") - card$append_table(tests_r()) + card$append_table(tests_r()[["report"]]) } if (!comment == "") { diff --git a/R/tm_missing_data.R b/R/tm_missing_data.R index 42f2fc846..6e484ee04 100644 --- a/R/tm_missing_data.R +++ b/R/tm_missing_data.R @@ -23,7 +23,6 @@ #' - `summary_plot` (`grob` created with [ggplot2::ggplotGrob()]) #' - `combination_plot` (`grob` created with [ggplot2::ggplotGrob()]) #' - `by_subject_plot` (`ggplot`) -#' - `table` (`datatables` created with [DT::datatable()]) #' #' A Decorator is applied to the specific output using a named list of `teal_transform_module` objects. #' The name of this list corresponds to the name of the output to which the decorator is applied. @@ -35,8 +34,7 @@ #' decorators = list( #' summary_plot = teal_transform_module(...), # applied only to `summary_plot` output #' combination_plot = teal_transform_module(...), # applied only to `combination_plot` output -#' by_subject_plot = teal_transform_module(...), # applied only to `by_subject_plot` output -#' table = teal_transform_module(...) # applied only to `table` output +#' by_subject_plot = teal_transform_module(...) # applied only to `by_subject_plot` output #' ) #' ) #' ``` @@ -148,8 +146,7 @@ tm_missing_data <- function(label = "Missing data", checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) - available_decorators <- c("summary_plot", "combination_plot", "by_subject_plot", "table") - assert_decorators(decorators, names = available_decorators) + assert_decorators(decorators, names = c("summary_plot", "combination_plot", "by_subject_plot")) # End of assertions datanames_module <- if (identical(datanames, "all") || is.null(datanames)) { @@ -429,8 +426,7 @@ encoding_missing_data <- function(id, summary_per_patient = FALSE, ggtheme, data choices = c("counts", "proportions"), selected = "counts", inline = TRUE - ), - ui_decorate_teal_data(ns("dec_summary_table"), decorators = select_decorators(decorators, "table")) + ) ), bslib::accordion( bslib::accordion_panel( @@ -1143,7 +1139,10 @@ srv_missing_data <- function(id, ) } - within(qenv, table <- DT::datatable(summary_data)) + within(qenv, { + table <- rtables::df_to_tt(summary_data) + table + }) }) by_subject_plot_q <- reactive({ @@ -1285,13 +1284,6 @@ srv_missing_data <- function(id, } ) - decorated_summary_table_q <- srv_decorate_teal_data( - id = "dec_summary_table", - data = summary_table_q, - decorators = select_decorators(decorators, "table"), - expr = table - ) - decorated_by_subject_plot_q <- srv_decorate_teal_data( id = "dec_by_subject_plot", data = by_subject_plot_q, @@ -1310,18 +1302,24 @@ srv_missing_data <- function(id, }) summary_table_r <- reactive({ - req(decorated_summary_table_q()) - - if (length(input$variables_select) == 0) { - # so that zeroRecords message gets printed - # using tibble as it supports weird column names, such as " " - DT::datatable( - tibble::tibble(` ` = logical(0)), - options = list(language = list(zeroRecords = "No variable selected."), pageLength = input$levels_table_rows) - ) - } else { - decorated_summary_table_q()[["table"]] - } + q <- req(summary_table_q()) + + list( + html = if (length(input$variables_select) == 0) { + # so that zeroRecords message gets printed + # using tibble as it supports weird column names, such as " " + DT::datatable( + tibble::tibble(` ` = logical(0)), + options = list( + language = list(zeroRecords = "No variable selected."), + pageLength = input$levels_table_rows + ) + ) + } else { + DT::datatable(q[["summary_data"]]) + }, + report = q[["table"]] + ) }) by_subject_plot_r <- reactive({ @@ -1343,7 +1341,7 @@ srv_missing_data <- function(id, width = plot_width ) - output$levels_table <- DT::renderDataTable(summary_table_r()) + output$levels_table <- DT::renderDataTable(summary_table_r()[["html"]]) pws3 <- teal.widgets::plot_with_settings_srv( id = "by_subject_plot", @@ -1359,7 +1357,7 @@ srv_missing_data <- function(id, } else if (sum_type == "Combinations") { decorated_combination_plot_q() } else if (sum_type == "By Variable Levels") { - decorated_summary_table_q() + summary_table_q() } else if (sum_type == "Grouped by Subject") { decorated_by_subject_plot_q() } @@ -1397,11 +1395,10 @@ srv_missing_data <- function(id, card$append_plot(combination_plot_r(), dim = pws2$dim()) } else if (sum_type == "By Variable Levels") { card$append_text("Table", "header3") - table <- decorated_summary_table_q()[["table"]] - if (nrow(table) == 0L) { + if (nrow(summary_table_q()[["summary_data"]]) == 0L) { card$append_text("No data available for table.") } else { - card$append_table(table) + card$append_table(summary_table_r()[["report"]]) } } else if (sum_type == "Grouped by Subject") { card$append_text("Plot", "header3") diff --git a/R/tm_outliers.R b/R/tm_outliers.R index 20d7a72f4..8dedf4927 100644 --- a/R/tm_outliers.R +++ b/R/tm_outliers.R @@ -21,7 +21,6 @@ #' - `box_plot` (`ggplot`) #' - `density_plot` (`ggplot`) #' - `cumulative_plot` (`ggplot`) -#' - `table` (`datatables` created with [DT::datatable()]) #' #' A Decorator is applied to the specific output using a named list of `teal_transform_module` objects. #' The name of this list corresponds to the name of the output to which the decorator is applied. @@ -33,8 +32,7 @@ #' decorators = list( #' box_plot = teal_transform_module(...), # applied only to `box_plot` output #' density_plot = teal_transform_module(...), # applied only to `density_plot` output -#' cumulative_plot = teal_transform_module(...), # applied only to `cumulative_plot` output -#' table = teal_transform_module(...) # applied only to `table` output +#' cumulative_plot = teal_transform_module(...) # applied only to `cumulative_plot` output #' ) #' ) #' ``` @@ -197,8 +195,7 @@ tm_outliers <- function(label = "Outliers Module", checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) - available_decorators <- c("box_plot", "density_plot", "cumulative_plot", "table") - assert_decorators(decorators, names = available_decorators) + assert_decorators(decorators, names = c("box_plot", "density_plot", "cumulative_plot")) # End of assertions # Make UI args @@ -368,7 +365,6 @@ ui_outliers <- function(id, ...) { decorators = select_decorators(args$decorators, "cumulative_plot") ) ), - ui_decorate_teal_data(ns("d_table"), decorators = select_decorators(args$decorators, "table")), bslib::accordion_panel( title = "Plot settings", selectInput( @@ -661,7 +657,7 @@ srv_outliers <- function(id, data, reporter, filter_panel_api, outlier_var, qenv <- teal.code::eval_code( qenv, substitute( - expr = summary_table_pre <- ANL_OUTLIER %>% + expr = summary_data_pre <- ANL_OUTLIER %>% dplyr::filter(is_outlier_selected) %>% dplyr::select(outlier_var_name, categorical_var_name) %>% dplyr::group_by(categorical_var_name) %>% @@ -706,9 +702,9 @@ srv_outliers <- function(id, data, reporter, filter_panel_api, outlier_var, qenv <- teal.code::eval_code( qenv, quote( - summary_table_pre <- summary_table_pre %>% + summary_data_pre <- summary_data_pre %>% dplyr::arrange(desc(n_outliers / total_in_cat)) %>% - dplyr::mutate(order = seq_len(nrow(summary_table_pre))) + dplyr::mutate(order = seq_len(nrow(summary_data_pre))) ) ) } @@ -722,17 +718,17 @@ srv_outliers <- function(id, data, reporter, filter_panel_api, outlier_var, # In this case, the column used for reordering is `order`. ANL_OUTLIER <- dplyr::left_join( ANL_OUTLIER, - summary_table_pre[, c("order", categorical_var)], + summary_data_pre[, c("order", categorical_var)], by = categorical_var ) # so that x axis of plot aligns with columns of summary table, from most outliers to least by percentage ANL <- ANL %>% dplyr::left_join( - dplyr::select(summary_table_pre, categorical_var_name, order), + dplyr::select(summary_data_pre, categorical_var_name, order), by = categorical_var ) %>% dplyr::arrange(order) - summary_table <- summary_table_pre %>% + summary_data <- summary_data_pre %>% dplyr::select( categorical_var_name, Outliers = display_str, Missings = display_str_na, Total = total_in_cat @@ -749,19 +745,13 @@ srv_outliers <- function(id, data, reporter, filter_panel_api, outlier_var, ) ) } else { - within(qenv, summary_table <- data.frame()) + within(qenv, summary_data <- data.frame()) } # Generate decoratable object from data qenv <- within(qenv, { - table <- DT::datatable( - summary_table, - options = list( - dom = "t", - autoWidth = TRUE, - columnDefs = list(list(width = "200px", targets = "_all")) - ) - ) + table <- rtables::df_to_tt(summary_data) + table }) if (length(categorical_var) > 0 && nrow(qenv[["ANL_OUTLIER"]]) > 0) { @@ -1066,25 +1056,28 @@ srv_outliers <- function(id, data, reporter, filter_panel_api, outlier_var, c(box_plot_q, density_plot_q, cumulative_plot_q) ) - decorated_final_q_no_table <- reactive(decorated_q[[req(current_tab_r())]]()) + decorated_final_q <- reactive(decorated_q[[req(current_tab_r())]]()) - decorated_final_q <- srv_decorate_teal_data( - "d_table", - data = decorated_final_q_no_table, - decorators = select_decorators(decorators, "table"), - expr = table - ) + summary_table_r <- reactive({ + q <- req(decorated_final_q()) - output$summary_table <- DT::renderDataTable( - expr = { - if (iv_r()$is_valid()) { - categorical_var <- as.vector(merged$anl_input_r()$columns_source$categorical_var) - if (!is.null(categorical_var)) { - decorated_final_q()[["table"]] - } - } - } - ) + list( + html = DT::datatable( + data = if (iv_r()$is_valid()) { + categorical_var <- as.vector(merged$anl_input_r()$columns_source$categorical_var) + if (!is.null(categorical_var)) q[["summary_data"]] + }, + option = list( + dom = "t", + autoWidth = TRUE, + columnDefs = list(list(width = "200px", targets = "_all")) + ) + ), + report = q[["table"]] + ) + }) + + output$summary_table <- DT::renderDataTable(summary_table_r()[["html"]]) # slider text output$ui_outlier_help <- renderUI({ @@ -1355,9 +1348,8 @@ srv_outliers <- function(id, data, reporter, filter_panel_api, outlier_var, ) categorical_var <- as.vector(merged$anl_input_r()$columns_source$categorical_var) if (length(categorical_var) > 0) { - summary_table <- decorated_final_q()[["table"]] card$append_text("Summary Table", "header3") - card$append_table(summary_table) + card$append_table(summary_table_r()[["report"]]) } card$append_text("Plot", "header3") if (tab_type == "Boxplot") { diff --git a/man/tm_g_distribution.Rd b/man/tm_g_distribution.Rd index a064a26fe..cf3884a16 100644 --- a/man/tm_g_distribution.Rd +++ b/man/tm_g_distribution.Rd @@ -90,8 +90,6 @@ This module generates the following objects, which can be modified in place usin \itemize{ \item \code{histogram_plot} (\code{ggplot}) \item \code{qq_plot} (\code{ggplot}) -\item \code{summary_table} (\code{datatables} created with \code{\link[DT:datatable]{DT::datatable()}}) -\item \code{test_table} (\code{datatables} created with \code{\link[DT:datatable]{DT::datatable()}}) } A Decorator is applied to the specific output using a named list of \code{teal_transform_module} objects. @@ -102,9 +100,7 @@ See code snippet below: ..., # arguments for module decorators = list( histogram_plot = teal_transform_module(...), # applied only to `histogram_plot` output - qq_plot = teal_transform_module(...), # applied only to `qq_plot` output - summary_table = teal_transform_module(...), # applied only to `summary_table` output - test_table = teal_transform_module(...) # applied only to `test_table` output + qq_plot = teal_transform_module(...) # applied only to `qq_plot` output ) ) }\if{html}{\out{}} diff --git a/man/tm_missing_data.Rd b/man/tm_missing_data.Rd index 6d2f03824..29d3ea548 100644 --- a/man/tm_missing_data.Rd +++ b/man/tm_missing_data.Rd @@ -85,7 +85,6 @@ This module generates the following objects, which can be modified in place usin \item \code{summary_plot} (\code{grob} created with \code{\link[ggplot2:ggplotGrob]{ggplot2::ggplotGrob()}}) \item \code{combination_plot} (\code{grob} created with \code{\link[ggplot2:ggplotGrob]{ggplot2::ggplotGrob()}}) \item \code{by_subject_plot} (\code{ggplot}) -\item \code{table} (\code{datatables} created with \code{\link[DT:datatable]{DT::datatable()}}) } A Decorator is applied to the specific output using a named list of \code{teal_transform_module} objects. @@ -97,8 +96,7 @@ See code snippet below: decorators = list( summary_plot = teal_transform_module(...), # applied only to `summary_plot` output combination_plot = teal_transform_module(...), # applied only to `combination_plot` output - by_subject_plot = teal_transform_module(...), # applied only to `by_subject_plot` output - table = teal_transform_module(...) # applied only to `table` output + by_subject_plot = teal_transform_module(...) # applied only to `by_subject_plot` output ) ) }\if{html}{\out{}} diff --git a/man/tm_outliers.Rd b/man/tm_outliers.Rd index f8c15278d..2a4120b2b 100644 --- a/man/tm_outliers.Rd +++ b/man/tm_outliers.Rd @@ -75,7 +75,6 @@ This module generates the following objects, which can be modified in place usin \item \code{box_plot} (\code{ggplot}) \item \code{density_plot} (\code{ggplot}) \item \code{cumulative_plot} (\code{ggplot}) -\item \code{table} (\code{datatables} created with \code{\link[DT:datatable]{DT::datatable()}}) } A Decorator is applied to the specific output using a named list of \code{teal_transform_module} objects. @@ -87,8 +86,7 @@ See code snippet below: decorators = list( box_plot = teal_transform_module(...), # applied only to `box_plot` output density_plot = teal_transform_module(...), # applied only to `density_plot` output - cumulative_plot = teal_transform_module(...), # applied only to `cumulative_plot` output - table = teal_transform_module(...) # applied only to `table` output + cumulative_plot = teal_transform_module(...) # applied only to `cumulative_plot` output ) ) }\if{html}{\out{}}