diff --git a/DESCRIPTION b/DESCRIPTION index 8f9c206..e4fba23 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -2,7 +2,7 @@ Package: uteals Title: Shared Utilities to Extend the 'teal' Modules Language: en-US Version: 0.0.4.9000 -Date: 2026-04-13 +Date: 2026-06-30 URL: https://github.com/phuse-org/uteals Authors@R: c( diff --git a/NAMESPACE b/NAMESPACE index de2b5c9..3c06297 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -55,6 +55,8 @@ importFrom(gridify,gridifyCells) importFrom(gridify,gridifyLayout) importFrom(gridify,gridifyObject) importFrom(gridify,set_cell) +importFrom(jsonlite,base64_enc) +importFrom(jsonlite,read_json) importFrom(methods,new) importFrom(openxlsx,read.xlsx) importFrom(patchwork,plot_annotation) @@ -102,6 +104,8 @@ importFrom(teal,teal_transform_module) importFrom(teal.code,eval_code) importFrom(teal.modules.clinical,add_expr) importFrom(teal.modules.clinical,bracket_expr) +importFrom(teal.reporter,Reporter) importFrom(tern,rtable2gg) importFrom(utils,write.csv) importFrom(yaml,as.yaml) +importFrom(zip,zip) diff --git a/NEWS.md b/NEWS.md index cee6d48..806b730 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,14 +6,15 @@ - Fixed ID conflict in `or_filtering_transformator` that caused errors when multiple instances were used in the same Shiny app. Removed a `shinyBS::bsModal()` block with fixed, non-module-scoped IDs (dead code — preview is handled by `shiny::showModal()`), resolving duplicate element IDs across instances. - Added `updateOn = "blur"` to all `textInput` controls so that reactive updates are only triggered when the user leaves the field, reducing unnecessary re-renders while typing. Requires `shiny >= 1.11.0`. - Refactored `title_footer_decorator` not to overwrite the first row of the TABLE.ID column in the imported file. This change allows for importing files that have meaningful data in the first row. +- Allowed to deselect the title in `title_footer_decorator`. - Added a button for CSV download for `tm_report_manager` module. #46 # Version 0.0.3 -- Refactored the `merge_levels_transformator` to handle the predefined inputs to the transformator ([#25](https://github.com/phuse-org/uteals/pull/25)). -- Added new `watermark_decorator` ([#23](https://github.com/phuse-org/uteals/pull/23)). -- Changed the example to a working one in `create_rel_risk_transformator` ([#27](https://github.com/phuse-org/uteals/pull/27)) -- Changed the example to a working one in `ggplot_decorator` ([#28](https://github.com/phuse-org/uteals/pull/28)) +- Refactored the `merge_levels_transformator` to handle the predefined inputs to the transformator (#25). +- Added new `watermark_decorator` (#23). +- Changed the example to a working one in `create_rel_risk_transformator` (#27) +- Changed the example to a working one in `ggplot_decorator` (#28) # Version 0.0.2 diff --git a/R/title_footer_decorator.R b/R/title_footer_decorator.R index 398962c..e9105c8 100644 --- a/R/title_footer_decorator.R +++ b/R/title_footer_decorator.R @@ -110,7 +110,9 @@ title_footer_decorator <- function(output_name, titles_file, choices = NULL, sel titles <- openxlsx::read.xlsx(titles_file, "Sheet1") titles <- titles |> dplyr::filter(!grepl("delete", .data$TABLE.ID, ignore.case = TRUE)) + selected <- `if`(is.null(selected), "", selected) choices <- `if`(is.null(choices), unique(titles$TABLE.ID), intersect(choices, titles$TABLE.ID)) + choices <- c(choices, "") checkmate::assert( checkmate::check_null(selected), checkmate::check_choice(selected, choices) @@ -122,7 +124,17 @@ title_footer_decorator <- function(output_name, titles_file, choices = NULL, sel ns <- NS(id) tagList( div( - selectInput(ns("selectTitle"), label = "Select Title", choices = choices, selected = selected), + selectizeInput( + ns("selectTitle"), + label = "Select Title", + choices = choices, + selected = selected, + options = list( + allowEmptyOption = TRUE, + placeholder = "Select a value...", + plugins = list("clear_button") + ) + ), checkboxInput(ns("customize"), label = "Customize Title and Footer", value = FALSE), uiOutput(ns("customInputs")) ) @@ -174,7 +186,7 @@ title_footer_decorator <- function(output_name, titles_file, choices = NULL, sel customTitle = input$customTitle, customFooter = input$customFooter ) - } else if (input$selectTitle != "blank") { + } else if (input$selectTitle != "blank" && !is.null(input$selectTitle) && input$selectTitle != "") { res <- within( res, { diff --git a/R/uteals.R b/R/uteals.R index 7a841a9..103453f 100644 --- a/R/uteals.R +++ b/R/uteals.R @@ -2,4 +2,7 @@ #' #' Provides decorator and transformator modules #' for `teal` modules. +#' @importFrom jsonlite base64_enc read_json +#' @importFrom teal.reporter Reporter +#' @importFrom zip zip "_PACKAGE" diff --git a/tests/testthat/test-title_footer_decorator.R b/tests/testthat/test-title_footer_decorator.R index bc4ac01..bb4816a 100644 --- a/tests/testthat/test-title_footer_decorator.R +++ b/tests/testthat/test-title_footer_decorator.R @@ -15,3 +15,43 @@ testthat::test_that("title_footer_decorator errors when selected is not in choic title_footer_decorator("plot", titles_file, choices = c("TSFLAB01", "TSFLAB01b"), selected = "TBL99") ) }) + +testthat::test_that("server logic handles empty selection correctly for plots", { + decorator <- title_footer_decorator("plot", titles_file, choices = c("TSFLAB01", "TSFLAB01b"), selected = "") + + mock_data <- shiny::reactive({ + q <- teal.code::qenv() + teal.code::eval_code(q, "plot <- ggplot2::ggplot()") + }) + + shiny::testServer(decorator$server, args = list(data = mock_data), { + session$setInputs(customize = FALSE, selectTitle = "") + res_reactive <- session$getReturned() + res <- withr::with_options(list(device = function() pdf(file = NULL)), { + res_reactive() + }) + + testthat::expect_equal(res[["plot"]]$labels$title, "") + testthat::expect_equal(res[["plot"]]$labels$caption, "") + }) +}) + +testthat::test_that("server logic handles empty selection correctly for tables", { + decorator <- title_footer_decorator("table", titles_file, choices = c("TSFLAB01", "TSFLAB01b"), selected = "") + + mock_data <- shiny::reactive({ + q <- teal.code::qenv() + teal.code::eval_code(q, "table <- rtables::rtable(header = 'a', rtables::rrow('1', 1))") + }) + + shiny::testServer(decorator$server, args = list(data = mock_data), { + session$setInputs(customize = FALSE, selectTitle = "") + res_reactive <- session$getReturned() + res <- withr::with_options(list(device = function() pdf(file = NULL)), { + res_reactive() + }) + + testthat::expect_equal(formatters::main_title(res[["table"]]), "") + testthat::expect_equal(formatters::main_footer(res[["table"]]), "") + }) +})