From fea60e20c5ac619a35d21419add4b7cb893c95f9 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Wed, 30 Jul 2025 05:40:55 -0700 Subject: [PATCH 001/139] - Added table state return value --- R/downloadableReactTable.R | 10 +++++++--- man/downloadableReactTable.Rd | 7 +++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/R/downloadableReactTable.R b/R/downloadableReactTable.R index 0ae803da..73d5972a 100644 --- a/R/downloadableReactTable.R +++ b/R/downloadableReactTable.R @@ -139,7 +139,9 @@ downloadableReactTableUI <- function(id, #' Also see example below to see how to pass options (default = list()) #' @param logger logger to use (default = NULL) #' -#' @return Rendered react table +#' @return A named list of current rendered table state values. The list keys are +#' ("page", "pageSize", "pages", "sorted" and "selected"). +#' Review \code{?reactable::getReactableState} for more info. #' #' @section Shiny Usage: #' This function is not called directly by consumers - it is accessed in @@ -169,7 +171,7 @@ downloadableReactTableUI <- function(id, #' downloadtypes = c("csv", "tsv"), #' hovertext = "Download the data here!")))), #' server = function(input, output) { -#' downloadableReactTable( +#' table_state <- downloadableReactTable( #' id = "object_id1", #' table_data = reactiveVal(iris), #' download_data_fxns = list(csv = reactiveVal(iris), tsv = reactiveVal(iris)), @@ -185,6 +187,7 @@ downloadableReactTableUI <- function(id, #' "&:hover[aria-sort]" = list(background = "hsl(0, 0%, 96%)"), #' "&[aria-sort='ascending'], &[aria-sort='descending']" = list(background = "hsl(0, 0%, 96%)"), #' borderColor = "#'555")))) +#' observeEvent(table_state(), { print(table_state()) }) #' }) #' } #' @@ -338,7 +341,8 @@ downloadableReactTable <- function(id, } table_output }) - } + } + shiny::reactive({reactable::getReactableState("reactTableOutputID")}) } ) } diff --git a/man/downloadableReactTable.Rd b/man/downloadableReactTable.Rd index bb83cf5a..0951bf1b 100644 --- a/man/downloadableReactTable.Rd +++ b/man/downloadableReactTable.Rd @@ -64,7 +64,9 @@ Also see example below to see how to pass options (default = list())} \item{logger}{logger to use (default = NULL)} } \value{ -Rendered react table +A named list of current rendered table state values. The list keys are +("page", "pageSize", "pages", "sorted" and "selected"). +Review \code{?reactable::getReactableState} for more info. } \description{ Server-side function for the downloadableReactTableUI. @@ -91,7 +93,7 @@ if (interactive()) { downloadtypes = c("csv", "tsv"), hovertext = "Download the data here!")))), server = function(input, output) { - downloadableReactTable( + table_state <- downloadableReactTable( id = "object_id1", table_data = reactiveVal(iris), download_data_fxns = list(csv = reactiveVal(iris), tsv = reactiveVal(iris)), @@ -107,6 +109,7 @@ if (interactive()) { "&:hover[aria-sort]" = list(background = "hsl(0, 0\%, 96\%)"), "&[aria-sort='ascending'], &[aria-sort='descending']" = list(background = "hsl(0, 0\%, 96\%)"), borderColor = "#'555")))) + observeEvent(table_state(), { print(table_state()) }) }) } From 0a536ec9a2b0f3e030b42cd640c1d205df050583 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Wed, 30 Jul 2025 05:41:22 -0700 Subject: [PATCH 002/139] - Updated package version --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index dde48596..10d05336 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: periscope2 Type: Package Title: Enterprise Streamlined 'shiny' Application Framework Using 'bs4Dash' -Version: 0.3.0.9005 +Version: 0.3.0.9006 Authors@R: c( person("Mohammed", "Ali", role = c("aut", "cre"), email = "mohammed@aggregate-genius.com"), person("Constance", "Brett", role = c("ctb")), From 330d327035c3405bb1279d2d192509b199603d20 Mon Sep 17 00:00:00 2001 From: Mennahtullah Mabrouk Date: Wed, 30 Jul 2025 07:17:29 -0700 Subject: [PATCH 003/139] Adding filter log levels --- R/logger.R | 28 ++++++++++++++++++++++++---- R/ui_helpers.R | 3 +-- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/R/logger.R b/R/logger.R index 73dd8630..273371fe 100644 --- a/R/logger.R +++ b/R/logger.R @@ -667,6 +667,16 @@ updateOptions.Logger <- function(container, ...) { ## to color messages. The coloring can be switched off by means of configuring ## the handler with \var{color_output} option set to FALSE. ## +logging_level <- function(level_name, current_log_level) { + switch(current_log_level, + "DEBUG" = TRUE, + "INFO" = level_name %in% c("INFO", "WARN", "ERROR"), + "WARN" = level_name %in% c("WARN", "ERROR"), + "ERROR" = level_name == "ERROR", + FALSE + ) +} + writeToConsole <- function(msg, handler, ...) { if (length(list(...)) && "dry" %in% names(list(...))) { if (!is.null(handler$color_output) && handler$color_output == FALSE) { @@ -679,9 +689,13 @@ writeToConsole <- function(msg, handler, ...) { stopifnot(length(list(...)) > 0) - level_name <- list(...)[[1]]$levelname - msg <- handler$color_msg(msg, level_name) - cat(paste0(msg, "\n")) + current_log_level <- fw_get_loglevel() + level_name <- list(...)[[1]]$levelname + + if (logging_level(level_name, current_log_level)) { + msg <- handler$color_msg(msg, level_name) + cat(paste0(msg, "\n")) + } } .build_msg_coloring <- function() { @@ -727,7 +741,13 @@ writeToConsole <- function(msg, handler, ...) { writeToFile <- function(msg, handler, ...) { if (length(list(...)) && "dry" %in% names(list(...))) return(exists("file", envir = handler)) - cat(paste0(msg, "\n"), file = with(handler, file), append = TRUE) + + current_log_level <- fw_get_loglevel() + level_name <- list(...)[[1]]$levelname + + if (logging_level(level_name, current_log_level)) { + cat(paste0(msg, "\n"), file = with(handler, file), append = TRUE) + } } ## the single predefined formatter diff --git a/R/ui_helpers.R b/R/ui_helpers.R index 858989f7..afc2138e 100644 --- a/R/ui_helpers.R +++ b/R/ui_helpers.R @@ -606,7 +606,7 @@ ui_tooltip <- function(id, #' @export set_app_parameters <- function(title = NULL, app_info = NULL, - log_level = "DEBUG", + log_level = c("DEBUG", "INFO", "WARN", "ERROR"), app_version = "1.0.0", loading_indicator = NULL, announcements_file = NULL) { @@ -634,7 +634,6 @@ set_app_parameters <- function(title = NULL, .g_opts$announcements_file <- announcements_file } - #' Parse application passed URL parameters #' #' This function returns any url parameters passed to the application as From b0de4737d3d0bdb36afd6e5a0dca1a61197b69f3 Mon Sep 17 00:00:00 2001 From: Mennahtullah Mabrouk Date: Wed, 30 Jul 2025 07:28:07 -0700 Subject: [PATCH 004/139] Add cases to test filtering in writeToConsole and writeToFile --- tests/testthat/test_logger.R | 73 ++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/tests/testthat/test_logger.R b/tests/testthat/test_logger.R index c4821953..e0641cdb 100644 --- a/tests/testthat/test_logger.R +++ b/tests/testthat/test_logger.R @@ -207,3 +207,76 @@ test_that("MsgComposer function - defaultMsgCompose()",{ expect_equal(periscope2:::defaultMsgCompose(msg = paste(rep(LETTERS, 316), collapse = "")), paste(rep(LETTERS, 316), collapse = "")) }) + +# Testing log_levels +test_that("writeToConsole DEBUG level", { + periscope2::set_app_parameters(log_level = "DEBUG") + expect_output(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) + expect_output(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) + expect_output(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) + expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) +}) + +test_that("writeToConsole INFO level", { + periscope2::set_app_parameters(log_level = "INFO") + expect_silent(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) + expect_output(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) + expect_output(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) + expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) +}) + +test_that("writeToConsole WARN level", { + periscope2::set_app_parameters(log_level = "WARN") + expect_silent(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) + expect_silent(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) + expect_output(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) + expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) +}) + +test_that("writeToConsole ERROR level", { + periscope2::set_app_parameters(log_level = "ERROR") + expect_silent(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) + expect_silent(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) + expect_silent(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) + expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) +}) + +test_that("writeToFile DEBUG level", { + unlink(test_file_name, force = TRUE) + periscope2::set_app_parameters(log_level = "DEBUG") + writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) + writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) + writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) + writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) + expect_equal(readLines(test_file_name[[1]]), c("debug", "info", "warn", "error")) +}) + +test_that("writeToFile INFO level", { + unlink(test_file_name, force = TRUE) + periscope2::set_app_parameters(log_level = "INFO") + writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) + writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) + writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) + writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) + expect_equal(readLines(test_file_name[[1]]), c("info", "warn", "error")) +}) + +test_that("writeToFile WARN level", { + unlink(test_file_name, force = TRUE) + periscope2::set_app_parameters(log_level = "WARN") + writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) + writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) + writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) + writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) + expect_equal(readLines(test_file_name[[1]]), c("warn", "error")) +}) + +test_that("writeToFile ERROR level", { + unlink(test_file_name, force = TRUE) + periscope2::set_app_parameters(log_level = "ERROR") + writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) + writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) + writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) + writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) + expect_equal(readLines(test_file_name[[1]]), "error") +}) From f951ad70f2214cb6ff6fd30d2cd48ff5fa82201a Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Wed, 30 Jul 2025 08:06:41 -0700 Subject: [PATCH 005/139] - Added module return unit tests --- tests/testthat/test_downloadable_react_table.R | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test_downloadable_react_table.R b/tests/testthat/test_downloadable_react_table.R index 7d234db8..900645ba 100644 --- a/tests/testthat/test_downloadable_react_table.R +++ b/tests/testthat/test_downloadable_react_table.R @@ -205,7 +205,7 @@ test_that("downloadableReactTable - pre_selected_rows", { selection_mode = "multiple", pre_selected_rows = c(1, 3)), expr = { - output$reactTableOutputID + print(output$reactTableOutputID) })) expect_true(grepl("'pre_selected_rows' parameter must be a function or reactive expression. Setting default value NULL.", server_error)) @@ -385,3 +385,19 @@ test_that("downloadableReactTable - table_options", { expect_true(grepl(warn_msg2, server_warning, fixed = TRUE)) }) + + +test_that("downloadableReactTable - module return", { + local_mocked_bindings( + getReactableState = function(...) list(showSortable = TRUE), + .package = "reactable") + + testServer( + downloadableReactTable, + args = list(table_data = function() { "test" }, + table_options = list(showSortable = TRUE)), + expr = { + result <- session$returned() + expect_true(result$showSortable) + }) +}) From 10a976c579a1728eacc9ee1af6bd49bc9001989f Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Wed, 30 Jul 2025 08:16:44 -0700 Subject: [PATCH 006/139] - Removed unneeded print --- tests/testthat/test_downloadable_react_table.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test_downloadable_react_table.R b/tests/testthat/test_downloadable_react_table.R index 900645ba..93244ca1 100644 --- a/tests/testthat/test_downloadable_react_table.R +++ b/tests/testthat/test_downloadable_react_table.R @@ -205,8 +205,8 @@ test_that("downloadableReactTable - pre_selected_rows", { selection_mode = "multiple", pre_selected_rows = c(1, 3)), expr = { - print(output$reactTableOutputID) - })) + output$reactTableOutputID + })) expect_true(grepl("'pre_selected_rows' parameter must be a function or reactive expression. Setting default value NULL.", server_error)) server_error <- testServer(downloadableReactTable, From bbb1bbcde61d42249d35d93248d38ba6fed51b21 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Wed, 30 Jul 2025 08:43:29 -0700 Subject: [PATCH 007/139] - skipped failing unit tests for R 4.0.5 --- tests/testthat/test_downloadable_react_table.R | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/testthat/test_downloadable_react_table.R b/tests/testthat/test_downloadable_react_table.R index 93244ca1..9e39c81a 100644 --- a/tests/testthat/test_downloadable_react_table.R +++ b/tests/testthat/test_downloadable_react_table.R @@ -388,6 +388,7 @@ test_that("downloadableReactTable - table_options", { test_that("downloadableReactTable - module return", { + skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") local_mocked_bindings( getReactableState = function(...) list(showSortable = TRUE), .package = "reactable") From 1220487895b10e24805e46d7636b6c1e6af9ec3d Mon Sep 17 00:00:00 2001 From: Mennahtullah Mabrouk Date: Wed, 30 Jul 2025 09:02:14 -0700 Subject: [PATCH 008/139] Update news and vignettes --- NEWS.md | 1 + vignettes/logViewer-module.Rmd | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/NEWS.md b/NEWS.md index 70f30e08..cd9dd998 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,7 @@ - Updated `set_app_parameters` method documentation to displayed html tags correctly - Updated package documentation to fix `docType` deprecation warning - Updated `logger` module internal documentation +- Added loglevel filtering according to level chosen ----- diff --git a/vignettes/logViewer-module.Rmd b/vignettes/logViewer-module.Rmd index 506f3a57..6d001ab3 100644 --- a/vignettes/logViewer-module.Rmd +++ b/vignettes/logViewer-module.Rmd @@ -25,6 +25,11 @@ This *Shiny Module* displays recorded session logs in tabular format * time: display action time * The log files are kept in the /log directory and named 'actions.log'. ONE old copy of the log is kept as 'actions.log.last * Many actions are automatically logged by the framework and it is easy for developers to add additional items as they see fit. +* Filtering logs by setting log_level as desired + * "DEBUG" will log logdebug, loginfo, logwarn, and logerror messages + * "INFO" will log loginfo, logwarn, and logerror messages + * "WARN" will log logwarn and logerror messages + * "ERROR" will log logerror messages * It is important to note that the log rolls over for each session and is reset if using the appReset module.
From 62ea19d2bc82f40a1c34ca800419a8616d5e0fa3 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 31 Jul 2025 04:37:15 -0700 Subject: [PATCH 009/139] - Added debug notes to debug circleci openxlsx2 version issue --- tests/testthat/test_download_file.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test_download_file.R b/tests/testthat/test_download_file.R index 0760a838..ee9cf834 100644 --- a/tests/testthat/test_download_file.R +++ b/tests/testthat/test_download_file.R @@ -35,15 +35,15 @@ download_char_data <- function() { } create_openxlsx2_wb <- function() { - wb <- openxlsx2::wb_workbook()$add_worksheet("openxlsx2_workbook")$add_data(x = download_data()) + openxlsx2::wb_workbook()$add_worksheet("openxlsx2_workbook")$add_data(x = download_data()) } create_openxlsx_wb <- function() { - wb <- openxlsx::createWorkbook() + wb <- openxlsx::createWorkbook() openxlsx::addWorksheet(wb, "openxlsx_workbook") data <- as.data.frame(download_data()) openxlsx::writeData(wb, "openxlsx_workbook", data) - return(wb) + wb } # UI Testing From b2d0f838eb9db88179df88f52d4b4e78363e799e Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 31 Jul 2025 04:53:14 -0700 Subject: [PATCH 010/139] - Added debug notes to debug circleci openxlsx2 version issue --- .circleci/config.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index d061ca40..6736927c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -30,6 +30,20 @@ jobs: sudo apt-get update -y sudo apt-get install -yq texlive-fonts-recommended sudo apt-get install -yq texlive-fonts-extra + - run: + name: Debug package availability + command: | + Rscript -e "cat('R version:', R.version.string, '\n') + cat('Available repos:\n') + print(getOption('repos')) + # Check if package exists in current repo + available_pkgs <- available.packages() + if ('openxlsx2' %in% available_pkgs[,'Package']) { + pkg_info <- available_pkgs[available_pkgs[,'Package'] == 'openxlsx2', ] + cat('Package found. Depends:', pkg_info['Depends'], '\n') + } else { + cat('Package not found in current repository\n') + }" - run: name: Install R dependencies From 82aded1648cc719efc5f9e4a5a3397ade75ecc56 Mon Sep 17 00:00:00 2001 From: Mennahtullah Mabrouk Date: Thu, 31 Jul 2025 04:58:00 -0700 Subject: [PATCH 011/139] Update example apps documentation --- inst/fw_templ/p_example/announce.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/inst/fw_templ/p_example/announce.yaml b/inst/fw_templ/p_example/announce.yaml index 35755b1f..2ff6fcc3 100644 --- a/inst/fw_templ/p_example/announce.yaml +++ b/inst/fw_templ/p_example/announce.yaml @@ -51,3 +51,11 @@ title: "Welcome to Periscope2" ### text # The announcement text. Text can contain html tags and is a mandatory value text: "This message will be closed automatically in 30s" + +### log_level +# Controls which log messages are shown in the application: +# - "DEBUG": All messages +# - "INFO" : Info, warnings, and errors +# - "WARN" : Only warnings and errors +# - "ERROR": Only errors +log_level: "DEBUG" From 6db454ca8e701f37461daabadb2030e43a1febb3 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 31 Jul 2025 05:15:29 -0700 Subject: [PATCH 012/139] Use a later snapshot that includes openxlsx2 --- .circleci/config.yml | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6736927c..670c3254 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -30,20 +30,6 @@ jobs: sudo apt-get update -y sudo apt-get install -yq texlive-fonts-recommended sudo apt-get install -yq texlive-fonts-extra - - run: - name: Debug package availability - command: | - Rscript -e "cat('R version:', R.version.string, '\n') - cat('Available repos:\n') - print(getOption('repos')) - # Check if package exists in current repo - available_pkgs <- available.packages() - if ('openxlsx2' %in% available_pkgs[,'Package']) { - pkg_info <- available_pkgs[available_pkgs[,'Package'] == 'openxlsx2', ] - cat('Package found. Depends:', pkg_info['Depends'], '\n') - } else { - cat('Package not found in current repository\n') - }" - run: name: Install R dependencies @@ -51,9 +37,17 @@ jobs: R -e 'install.packages("shiny", repos = "https://cran.rstudio.com/")' R -e 'install.packages("bs4Dash", repos = "https://cran.rstudio.com/")' R -e 'install.packages(c("shinyWidgets", "yaml", "DT", "writexl", "fresh", "miniUI", "shinyFeedback"))' - R -e 'install.packages(c("canvasXpress", "waiter", "shinyjs", "openxlsx", "openxlsx2", "spelling", "colourpicker", "lifecycle"))' + R -e 'install.packages(c("canvasXpress", "waiter", "shinyjs", "openxlsx", "spelling", "colourpicker", "lifecycle"))' R -e 'install.packages("reactable")' + - run: + name: Install openxlsx2 with updated snapshot + command: | + Rscript -e " + # Use a later snapshot that includes openxlsx2 + options(repos = c(CRAN = 'https://packagemanager.posit.co/cran/__linux__/focal/2022-01-01')) + install.packages('openxlsx2')" + - run: name: Session information and installed package versions command: | From 30a9ecab2394ebe2f4ddcaef170f52c5baceeeab Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 31 Jul 2025 05:27:18 -0700 Subject: [PATCH 013/139] - Try to install openxlsx2 from standard cran or GH --- .circleci/config.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 670c3254..47e98256 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -39,14 +39,17 @@ jobs: R -e 'install.packages(c("shinyWidgets", "yaml", "DT", "writexl", "fresh", "miniUI", "shinyFeedback"))' R -e 'install.packages(c("canvasXpress", "waiter", "shinyjs", "openxlsx", "spelling", "colourpicker", "lifecycle"))' R -e 'install.packages("reactable")' + R -e 'install.packages("openxlsx2", repos = "https://cloud.r-project.org'")' - run: - name: Install openxlsx2 with updated snapshot + name: Install openxlsx2 from GitHub command: | Rscript -e " - # Use a later snapshot that includes openxlsx2 - options(repos = c(CRAN = 'https://packagemanager.posit.co/cran/__linux__/focal/2022-01-01')) - install.packages('openxlsx2')" + if (!require('remotes')) { + options(repos = c(CRAN = 'https://cloud.r-project.org')) + install.packages('remotes') + } + remotes::install_github('JanMarvin/openxlsx2@v0.7.0')" - run: name: Session information and installed package versions From c1ba29c5dc7c5636339a9e7b3613851ded0b7f23 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 31 Jul 2025 05:42:57 -0700 Subject: [PATCH 014/139] - Fixing yaml parsing error --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 47e98256..4b71ece1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -46,9 +46,9 @@ jobs: command: | Rscript -e " if (!require('remotes')) { - options(repos = c(CRAN = 'https://cloud.r-project.org')) + options(repos = c(CRAN = 'https://cloud.r-project.org')); install.packages('remotes') - } + }; remotes::install_github('JanMarvin/openxlsx2@v0.7.0')" - run: From 3be14b61c949cf2564ed53d04fc9caef91e23cd5 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 31 Jul 2025 05:46:30 -0700 Subject: [PATCH 015/139] - Wrote install package commands in separate lines to avoid parsing issues --- .circleci/config.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4b71ece1..d2d70529 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,15 +41,15 @@ jobs: R -e 'install.packages("reactable")' R -e 'install.packages("openxlsx2", repos = "https://cloud.r-project.org'")' + - run: + name: Install remotes package + command: | + Rscript -e 'if (!require("remotes")) install.packages("remotes", repos = "https://cloud.r-project.org")' + - run: name: Install openxlsx2 from GitHub command: | - Rscript -e " - if (!require('remotes')) { - options(repos = c(CRAN = 'https://cloud.r-project.org')); - install.packages('remotes') - }; - remotes::install_github('JanMarvin/openxlsx2@v0.7.0')" + Rscript -e 'remotes::install_github("JanMarvin/openxlsx2@v0.7.0")' - run: name: Session information and installed package versions From 82f1b9fd716c7621990842925db79f0d1420d171 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 31 Jul 2025 05:53:31 -0700 Subject: [PATCH 016/139] - Removed extra comma --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d2d70529..83e5845b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -39,7 +39,7 @@ jobs: R -e 'install.packages(c("shinyWidgets", "yaml", "DT", "writexl", "fresh", "miniUI", "shinyFeedback"))' R -e 'install.packages(c("canvasXpress", "waiter", "shinyjs", "openxlsx", "spelling", "colourpicker", "lifecycle"))' R -e 'install.packages("reactable")' - R -e 'install.packages("openxlsx2", repos = "https://cloud.r-project.org'")' + R -e 'install.packages("openxlsx2", repos = "https://cloud.r-project.org")' - run: name: Install remotes package From e6ab2c1aa1fb1611e2f2a6e7e0d9af1ed2b1044e Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 31 Jul 2025 06:02:14 -0700 Subject: [PATCH 017/139] install openxlsx2 from cran standard mirror only --- .circleci/config.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 83e5845b..741d3bfb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,16 +41,6 @@ jobs: R -e 'install.packages("reactable")' R -e 'install.packages("openxlsx2", repos = "https://cloud.r-project.org")' - - run: - name: Install remotes package - command: | - Rscript -e 'if (!require("remotes")) install.packages("remotes", repos = "https://cloud.r-project.org")' - - - run: - name: Install openxlsx2 from GitHub - command: | - Rscript -e 'remotes::install_github("JanMarvin/openxlsx2@v0.7.0")' - - run: name: Session information and installed package versions command: | From 8c696de450ea45be842ddc94c971536132fa2fd5 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 31 Jul 2025 06:28:27 -0700 Subject: [PATCH 018/139] - Skipped openxlsx2 tests for old R versions --- DESCRIPTION | 2 +- tests/testthat/test_download_file.R | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 4e3df26a..a066e18c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: periscope2 Type: Package Title: Enterprise Streamlined 'shiny' Application Framework Using 'bs4Dash' -Version: 0.3.0.9006 +Version: 0.3.0.9007 Authors@R: c( person("Mohammed", "Ali", role = c("aut", "cre"), email = "mohammed@aggregate-genius.com"), person("Constance", "Brett", role = c("ctb")), diff --git a/tests/testthat/test_download_file.R b/tests/testthat/test_download_file.R index ee9cf834..2f3f1791 100644 --- a/tests/testthat/test_download_file.R +++ b/tests/testthat/test_download_file.R @@ -197,6 +197,7 @@ test_that("downloadFile - invalid type", { # Testing for xlsx downloads test_that("Testing workbook openxlsx2", { + skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") skip_if_not_installed("openxlsx2") testServer(downloadFile, args = list(logger = periscope2:::fw_get_user_log(), @@ -220,6 +221,7 @@ test_that("Testing workbook openxlsx", { }) test_that("Dataframe xlsx download works with openxlsx2", { + skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") skip_if_not_installed("openxlsx2") local_mocked_bindings(check_openxlsx_availability = function() FALSE) testServer(downloadFile, From b4e9a1e602aa1877c40d66bc96a861c1752d9354 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 31 Jul 2025 06:45:52 -0700 Subject: [PATCH 019/139] - Updated more openxlsx tests --- tests/testthat/test_download_file.R | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/testthat/test_download_file.R b/tests/testthat/test_download_file.R index 2f3f1791..4f9a72de 100644 --- a/tests/testthat/test_download_file.R +++ b/tests/testthat/test_download_file.R @@ -209,6 +209,7 @@ test_that("Testing workbook openxlsx2", { }) test_that("Testing workbook openxlsx", { + skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") skip_if_not_installed("openxlsx") local_mocked_bindings(check_openxlsx2_availability = function() FALSE) testServer(downloadFile, @@ -234,6 +235,7 @@ test_that("Dataframe xlsx download works with openxlsx2", { }) test_that("Dataframe xlsx download works with openxlsx", { + skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") skip_if_not_installed("openxlsx") local_mocked_bindings(check_openxlsx2_availability = function() FALSE) testServer(downloadFile, @@ -246,6 +248,7 @@ test_that("Dataframe xlsx download works with openxlsx", { }) test_that("Dataframe xlsx download works with writexl", { + skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") local_mocked_bindings(check_openxlsx2_availability = function() FALSE) local_mocked_bindings(check_openxlsx_availability = function() FALSE) testServer(downloadFile, From 3646786a7a9913d741e5c9e4cf818de5f849ccf8 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 31 Jul 2025 07:45:44 -0700 Subject: [PATCH 020/139] - Updated module documentation example app to fix long lines CMD checks issue --- R/downloadableReactTable.R | 21 ++++++++++++--------- man/downloadableReactTable.Rd | 9 +++++---- man/downloadableReactTableUI.Rd | 12 +++++++----- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/R/downloadableReactTable.R b/R/downloadableReactTable.R index 73d5972a..53e50d6f 100644 --- a/R/downloadableReactTable.R +++ b/R/downloadableReactTable.R @@ -67,7 +67,7 @@ #' downloadtypes = c("csv", "tsv"), #' hovertext = "Download the data here!")))), #' server = function(input, output) { -#' downloadableReactTable( +#' table_state <- downloadableReactTable( #' id = "object_id1", #' table_data = reactiveVal(iris), #' download_data_fxns = list(csv = reactiveVal(iris), tsv = reactiveVal(iris)), @@ -80,10 +80,12 @@ #' Petal.Width = colDef(defaultSortOrder = "desc")), #' showSortable = TRUE, #' theme = reactableTheme( -#' headerStyle = list( -#' "&:hover[aria-sort]" = list(background = "hsl(0, 0%, 96%)"), -#' "&[aria-sort='ascending'], &[aria-sort='descending']" = list(background = "hsl(0, 0%, 96%)"), -#' borderColor = "#'555")))) +#' borderColor = "#dfe2e5", +#' stripedColor = "#f6f8fa", +#' highlightColor = "#f0f5f9", +#' cellPadding = "8px 12px"))) +#' +#' observeEvent(table_state(), { print(table_state()) }) #' }) #' } #' @@ -183,10 +185,11 @@ downloadableReactTableUI <- function(id, #' Petal.Length = colDef(show = FALSE), #' Petal.Width = colDef(defaultSortOrder = "desc")), #' theme = reactableTheme( -#' headerStyle = list( -#' "&:hover[aria-sort]" = list(background = "hsl(0, 0%, 96%)"), -#' "&[aria-sort='ascending'], &[aria-sort='descending']" = list(background = "hsl(0, 0%, 96%)"), -#' borderColor = "#'555")))) +#' borderColor = "#dfe2e5", +#' stripedColor = "#f6f8fa", +#' highlightColor = "#f0f5f9", +#' cellPadding = "8px 12px"))) +#' #' observeEvent(table_state(), { print(table_state()) }) #' }) #' } diff --git a/man/downloadableReactTable.Rd b/man/downloadableReactTable.Rd index 0951bf1b..32b8f23d 100644 --- a/man/downloadableReactTable.Rd +++ b/man/downloadableReactTable.Rd @@ -105,10 +105,11 @@ if (interactive()) { Petal.Length = colDef(show = FALSE), Petal.Width = colDef(defaultSortOrder = "desc")), theme = reactableTheme( - headerStyle = list( - "&:hover[aria-sort]" = list(background = "hsl(0, 0\%, 96\%)"), - "&[aria-sort='ascending'], &[aria-sort='descending']" = list(background = "hsl(0, 0\%, 96\%)"), - borderColor = "#'555")))) + borderColor = "#dfe2e5", + stripedColor = "#f6f8fa", + highlightColor = "#f0f5f9", + cellPadding = "8px 12px"))) + observeEvent(table_state(), { print(table_state()) }) }) } diff --git a/man/downloadableReactTableUI.Rd b/man/downloadableReactTableUI.Rd index 30234c0f..bd0a0690 100644 --- a/man/downloadableReactTableUI.Rd +++ b/man/downloadableReactTableUI.Rd @@ -74,7 +74,7 @@ if (interactive()) { downloadtypes = c("csv", "tsv"), hovertext = "Download the data here!")))), server = function(input, output) { - downloadableReactTable( + table_state <- downloadableReactTable( id = "object_id1", table_data = reactiveVal(iris), download_data_fxns = list(csv = reactiveVal(iris), tsv = reactiveVal(iris)), @@ -87,10 +87,12 @@ if (interactive()) { Petal.Width = colDef(defaultSortOrder = "desc")), showSortable = TRUE, theme = reactableTheme( - headerStyle = list( - "&:hover[aria-sort]" = list(background = "hsl(0, 0\%, 96\%)"), - "&[aria-sort='ascending'], &[aria-sort='descending']" = list(background = "hsl(0, 0\%, 96\%)"), - borderColor = "#'555")))) + borderColor = "#dfe2e5", + stripedColor = "#f6f8fa", + highlightColor = "#f0f5f9", + cellPadding = "8px 12px"))) + + observeEvent(table_state(), { print(table_state()) }) }) } From c5889ad666b920c61fdbfc76e5cce62cb53b73ff Mon Sep 17 00:00:00 2001 From: Mennahtullah Mabrouk Date: Thu, 31 Jul 2025 08:11:07 -0700 Subject: [PATCH 021/139] Modify DEBUG default --- R/logger.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/R/logger.R b/R/logger.R index 273371fe..7fab7b81 100644 --- a/R/logger.R +++ b/R/logger.R @@ -669,11 +669,10 @@ updateOptions.Logger <- function(container, ...) { ## logging_level <- function(level_name, current_log_level) { switch(current_log_level, - "DEBUG" = TRUE, "INFO" = level_name %in% c("INFO", "WARN", "ERROR"), "WARN" = level_name %in% c("WARN", "ERROR"), "ERROR" = level_name == "ERROR", - FALSE + TRUE ) } From af8efca2537f7122f1ecf5eea3cb647f3dfd94a6 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 31 Jul 2025 08:37:56 -0700 Subject: [PATCH 022/139] - Updated module return type to include both selected rows and table state --- R/downloadableReactTable.R | 19 ++++++++++++++++--- man/downloadableReactTable.Rd | 6 +++++- man/downloadableReactTableUI.Rd | 2 +- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/R/downloadableReactTable.R b/R/downloadableReactTable.R index 53e50d6f..c8bc499b 100644 --- a/R/downloadableReactTable.R +++ b/R/downloadableReactTable.R @@ -8,7 +8,7 @@ #' downloadableReactTable module is extending \code{?reactable} package table functions by creating #' a custom high-functionality table paired with \link[periscope2]{downloadFile} button. #' The table has the following default functionality:search, highlight functionality, infinite scrolling, sorting by columns and -#' returns a reactive dataset of selected items. +#' returns a reactive dataset of selected items and table current state. #' #' \link[periscope2]{downloadFile} button will be hidden if \code{downloadableReactTableUI} parameter #' \code{downloadtypes} is empty @@ -141,9 +141,14 @@ downloadableReactTableUI <- function(id, #' Also see example below to see how to pass options (default = list()) #' @param logger logger to use (default = NULL) #' -#' @return A named list of current rendered table state values. The list keys are +#' @return A named list of two elements: +#' \itemize{ +#' \item selected_rows: data.frame of current selected rows +#' \item table_state: a list of current rendered table state values. The list keys are #' ("page", "pageSize", "pages", "sorted" and "selected"). #' Review \code{?reactable::getReactableState} for more info. +#' } +#' #' #' @section Shiny Usage: #' This function is not called directly by consumers - it is accessed in @@ -345,7 +350,15 @@ downloadableReactTable <- function(id, table_output }) } - shiny::reactive({reactable::getReactableState("reactTableOutputID")}) + shiny::reactive({ + table_state <- reactable::getReactableState("reactTableOutputID") + selected_rows <- NULL + + if (!is.null(table_state) && !is.null(table_state$selected)) { + selected_rows <- table_data()[table_state$selected, ] + } + list(selected_rows = selected_rows, table_state = table_state) + }) } ) } diff --git a/man/downloadableReactTable.Rd b/man/downloadableReactTable.Rd index 32b8f23d..35997c63 100644 --- a/man/downloadableReactTable.Rd +++ b/man/downloadableReactTable.Rd @@ -64,10 +64,14 @@ Also see example below to see how to pass options (default = list())} \item{logger}{logger to use (default = NULL)} } \value{ -A named list of current rendered table state values. The list keys are +A named list of two elements: +\itemize{ +\item selected_rows: data.frame of current selected rows +\item table_state: a list of current rendered table state values. The list keys are ("page", "pageSize", "pages", "sorted" and "selected"). Review \code{?reactable::getReactableState} for more info. } +} \description{ Server-side function for the downloadableReactTableUI. } diff --git a/man/downloadableReactTableUI.Rd b/man/downloadableReactTableUI.Rd index bd0a0690..0cfe4fa5 100644 --- a/man/downloadableReactTableUI.Rd +++ b/man/downloadableReactTableUI.Rd @@ -20,7 +20,7 @@ list of downloadFileButton UI and reactable table and hidden inputs for contentH downloadableReactTable module is extending \code{?reactable} package table functions by creating a custom high-functionality table paired with \link[periscope2]{downloadFile} button. The table has the following default functionality:search, highlight functionality, infinite scrolling, sorting by columns and -returns a reactive dataset of selected items. +returns a reactive dataset of selected items and table current state. } \details{ \link[periscope2]{downloadFile} button will be hidden if \code{downloadableReactTableUI} parameter From f114beb1205aeefa87c808eb691ac72c1a686a7b Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 31 Jul 2025 10:03:24 -0700 Subject: [PATCH 023/139] - Fixed failing unit test --- tests/testthat/test_downloadable_react_table.R | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/testthat/test_downloadable_react_table.R b/tests/testthat/test_downloadable_react_table.R index 9e39c81a..0f1a76b6 100644 --- a/tests/testthat/test_downloadable_react_table.R +++ b/tests/testthat/test_downloadable_react_table.R @@ -390,15 +390,19 @@ test_that("downloadableReactTable - table_options", { test_that("downloadableReactTable - module return", { skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") local_mocked_bindings( - getReactableState = function(...) list(showSortable = TRUE), + getReactableState = function(...) { + list(showSortable = TRUE, + defaultSelected = c(2, 3)) + }, .package = "reactable") + testServer( downloadableReactTable, - args = list(table_data = function() { "test" }, - table_options = list(showSortable = TRUE)), + args = list(table_data = function() { "test" }), expr = { result <- session$returned() - expect_true(result$showSortable) + expect_equal(length(result), 2) + expect_true(all(c("selected_rows", "table_state") %in% names(result))) }) }) From aeb50c2c9e510e104d08e5e8aabdd3f62547ed30 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Fri, 1 Aug 2025 02:11:43 -0700 Subject: [PATCH 024/139] - Updated unit tests to cover check return values --- R/downloadableReactTable.R | 3 +- .../testthat/test_downloadable_react_table.R | 29 +++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/R/downloadableReactTable.R b/R/downloadableReactTable.R index c8bc499b..7b00204c 100644 --- a/R/downloadableReactTable.R +++ b/R/downloadableReactTable.R @@ -353,8 +353,7 @@ downloadableReactTable <- function(id, shiny::reactive({ table_state <- reactable::getReactableState("reactTableOutputID") selected_rows <- NULL - - if (!is.null(table_state) && !is.null(table_state$selected)) { + if (!is.null(table_state) && !is.null(table_state$selected) && is.data.frame(table_data())) { selected_rows <- table_data()[table_state$selected, ] } list(selected_rows = selected_rows, table_state = table_state) diff --git a/tests/testthat/test_downloadable_react_table.R b/tests/testthat/test_downloadable_react_table.R index 0f1a76b6..bfe2f217 100644 --- a/tests/testthat/test_downloadable_react_table.R +++ b/tests/testthat/test_downloadable_react_table.R @@ -392,11 +392,11 @@ test_that("downloadableReactTable - module return", { local_mocked_bindings( getReactableState = function(...) { list(showSortable = TRUE, - defaultSelected = c(2, 3)) + defaultSelected = c(2, 3), + selected = c(2, 3)) }, .package = "reactable") - testServer( downloadableReactTable, args = list(table_data = function() { "test" }), @@ -404,5 +404,30 @@ test_that("downloadableReactTable - module return", { result <- session$returned() expect_equal(length(result), 2) expect_true(all(c("selected_rows", "table_state") %in% names(result))) + expect_true(is.null(result$selected_rows)) + }) + + local_mocked_bindings( + getReactableState = function(...) { + list(data = get_mtcars_data(), + showSortable = TRUE, + defaultSelected = c(2, 3), + selected = c(2, 3)) + }, + .package = "reactable") + + + testServer( + downloadableReactTable, + args = list(table_data = get_mtcars_data), + expr = { + result <- session$returned() + expect_equal(length(result), 2) + expect_true(all(c("selected_rows", "table_state") %in% names(result))) + expect_true(NROW(result$selected_rows) == 2) + expect_true(all(c("Mazda RX4 Wag", "Datsun 710") %in% rownames(result$selected_rows))) }) + }) + + From 821977bb5a1cc5c59b4bf7fbb8731bc98da41b4f Mon Sep 17 00:00:00 2001 From: Mennahtullah Mabrouk Date: Fri, 1 Aug 2025 08:10:30 -0700 Subject: [PATCH 025/139] Relocate logging_level --- R/logger.R | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/R/logger.R b/R/logger.R index 7fab7b81..bf8fa2f6 100644 --- a/R/logger.R +++ b/R/logger.R @@ -622,6 +622,17 @@ updateOptions.Logger <- function(container, ...) { updateOptions.environment(container, ...) } +## Filtering log messages according to chosen level +## +logging_level <- function(level_name, current_log_level) { + switch(current_log_level, + "INFO" = level_name %in% c("INFO", "WARN", "ERROR"), + "WARN" = level_name %in% c("WARN", "ERROR"), + "ERROR" = level_name == "ERROR", + TRUE + ) +} + ## ## Predefined(sample) handler actions ## @@ -667,15 +678,6 @@ updateOptions.Logger <- function(container, ...) { ## to color messages. The coloring can be switched off by means of configuring ## the handler with \var{color_output} option set to FALSE. ## -logging_level <- function(level_name, current_log_level) { - switch(current_log_level, - "INFO" = level_name %in% c("INFO", "WARN", "ERROR"), - "WARN" = level_name %in% c("WARN", "ERROR"), - "ERROR" = level_name == "ERROR", - TRUE - ) -} - writeToConsole <- function(msg, handler, ...) { if (length(list(...)) && "dry" %in% names(list(...))) { if (!is.null(handler$color_output) && handler$color_output == FALSE) { From 6c674af7c66bfd3851f13f90d7ee4b5d61587330 Mon Sep 17 00:00:00 2001 From: Mennahtullah Mabrouk Date: Fri, 1 Aug 2025 08:24:36 -0700 Subject: [PATCH 026/139] remove log level --- inst/fw_templ/p_example/announce.yaml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/inst/fw_templ/p_example/announce.yaml b/inst/fw_templ/p_example/announce.yaml index 2ff6fcc3..35755b1f 100644 --- a/inst/fw_templ/p_example/announce.yaml +++ b/inst/fw_templ/p_example/announce.yaml @@ -51,11 +51,3 @@ title: "Welcome to Periscope2" ### text # The announcement text. Text can contain html tags and is a mandatory value text: "This message will be closed automatically in 30s" - -### log_level -# Controls which log messages are shown in the application: -# - "DEBUG": All messages -# - "INFO" : Info, warnings, and errors -# - "WARN" : Only warnings and errors -# - "ERROR": Only errors -log_level: "DEBUG" From 352e8acd2dd9eff78b61be74c3a6ecec2a70ec0e Mon Sep 17 00:00:00 2001 From: Mennahtullah Mabrouk Date: Sun, 3 Aug 2025 23:06:16 -0700 Subject: [PATCH 027/139] Modify set_app_parameters --- R/ui_helpers.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/ui_helpers.R b/R/ui_helpers.R index afc2138e..858989f7 100644 --- a/R/ui_helpers.R +++ b/R/ui_helpers.R @@ -606,7 +606,7 @@ ui_tooltip <- function(id, #' @export set_app_parameters <- function(title = NULL, app_info = NULL, - log_level = c("DEBUG", "INFO", "WARN", "ERROR"), + log_level = "DEBUG", app_version = "1.0.0", loading_indicator = NULL, announcements_file = NULL) { @@ -634,6 +634,7 @@ set_app_parameters <- function(title = NULL, .g_opts$announcements_file <- announcements_file } + #' Parse application passed URL parameters #' #' This function returns any url parameters passed to the application as From 9f3e8505a19e3f2f2ba26179732dcdc9c766064c Mon Sep 17 00:00:00 2001 From: Mennahtullah Mabrouk Date: Sun, 3 Aug 2025 23:22:39 -0700 Subject: [PATCH 028/139] Modify Text --- vignettes/logViewer-module.Rmd | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vignettes/logViewer-module.Rmd b/vignettes/logViewer-module.Rmd index 6d001ab3..bc95c9da 100644 --- a/vignettes/logViewer-module.Rmd +++ b/vignettes/logViewer-module.Rmd @@ -25,9 +25,9 @@ This *Shiny Module* displays recorded session logs in tabular format * time: display action time * The log files are kept in the /log directory and named 'actions.log'. ONE old copy of the log is kept as 'actions.log.last * Many actions are automatically logged by the framework and it is easy for developers to add additional items as they see fit. -* Filtering logs by setting log_level as desired - * "DEBUG" will log logdebug, loginfo, logwarn, and logerror messages - * "INFO" will log loginfo, logwarn, and logerror messages +* Filtering logs by setting log_level argument as desired in set_app_parameters + * "DEBUG" will log logdebug, loginfo, logwarn, and logerror messages + * "INFO" will log loginfo, logwarn, and logerror messages * "WARN" will log logwarn and logerror messages * "ERROR" will log logerror messages * It is important to note that the log rolls over for each session and is reset if using the appReset module. From 3b99d62da8703cd862af835834d0c80937fac001 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Mon, 4 Aug 2025 20:00:25 -0700 Subject: [PATCH 029/139] - Added ggplot deprecation methods fix --- DESCRIPTION | 2 +- R/downloadablePlot.R | 12 ++++++------ inst/fw_templ/p_example/plots.R | 6 +++--- man/downloadablePlot.Rd | 6 +++--- man/downloadablePlotUI.Rd | 6 +++--- man/set_app_parameters.Rd | 2 +- .../sample_app_both_sidebars/program/fxn/plots.R | 6 +++--- .../sample_app_left_sidebar/program/fxn/plots.R | 6 +++--- .../sample_app_no_both_sidebars/program/fxn/plots.R | 6 +++--- .../sample_app_right_sidebar/program/fxn/plots.R | 6 +++--- tests/testthat/test_download_file.R | 6 +++--- tests/testthat/test_downloadable_plot.R | 6 +++--- 12 files changed, 35 insertions(+), 35 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index a066e18c..24589838 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: periscope2 Type: Package Title: Enterprise Streamlined 'shiny' Application Framework Using 'bs4Dash' -Version: 0.3.0.9007 +Version: 0.3.0.9008 Authors@R: c( person("Mohammed", "Ali", role = c("aut", "cre"), email = "mohammed@aggregate-genius.com"), person("Constance", "Brett", role = c("ctb")), diff --git a/R/downloadablePlot.R b/R/downloadablePlot.R index 4c8cc6f1..5782527f 100644 --- a/R/downloadablePlot.R +++ b/R/downloadablePlot.R @@ -74,9 +74,9 @@ #' download_plot <- function() { #' ggplot(data = mtcars, aes(x = wt, y = mpg)) + #' geom_point(aes(color = cyl)) + -#' theme(legend.justification = c(1, 1), -#' legend.position = c(1, 1), -#' legend.title = element_blank()) + +#' theme(legend.justification = c(1, 1), +#' legend.position.inside = c(1, 1), +#' legend.title = element_blank()) + #' ggtitle("GGPlot Example ") + #' xlab("wt") + #' ylab("mpg") @@ -217,9 +217,9 @@ downloadablePlotUI <- function(id, #' download_plot <- function() { #' ggplot(data = mtcars, aes(x = wt, y = mpg)) + #' geom_point(aes(color = cyl)) + -#' theme(legend.justification = c(1, 1), -#' legend.position = c(1, 1), -#' legend.title = element_blank()) + +#' theme(legend.justification = c(1, 1), +#' legend.position.inside = c(1, 1), +#' legend.title = element_blank()) + #' ggtitle("GGPlot Example ") + #' xlab("wt") + #' ylab("mpg") diff --git a/inst/fw_templ/p_example/plots.R b/inst/fw_templ/p_example/plots.R index 14049ce9..dc0ce9b1 100644 --- a/inst/fw_templ/p_example/plots.R +++ b/inst/fw_templ/p_example/plots.R @@ -15,9 +15,9 @@ attr(mtcars, "show_rownames") <- TRUE plot2ggplot <- function() { ggplot(data = mtcars, aes(x = wt, y = mpg)) + geom_point(aes(color = cyl)) + - theme(legend.justification = c(1, 1), - legend.position = c(1, 1), - legend.title = element_blank()) + + theme(legend.justification = c(1, 1), + legend.position.inside = c(1, 1), + legend.title = element_blank()) + ggtitle("GGPlot Example w/Hover") + xlab("wt") + ylab("mpg") diff --git a/man/downloadablePlot.Rd b/man/downloadablePlot.Rd index cbff6f58..0b565c5b 100644 --- a/man/downloadablePlot.Rd +++ b/man/downloadablePlot.Rd @@ -74,9 +74,9 @@ if (interactive()) { download_plot <- function() { ggplot(data = mtcars, aes(x = wt, y = mpg)) + geom_point(aes(color = cyl)) + - theme(legend.justification = c(1, 1), - legend.position = c(1, 1), - legend.title = element_blank()) + + theme(legend.justification = c(1, 1), + legend.position.inside = c(1, 1), + legend.title = element_blank()) + ggtitle("GGPlot Example ") + xlab("wt") + ylab("mpg") diff --git a/man/downloadablePlotUI.Rd b/man/downloadablePlotUI.Rd index e09250e9..38e68591 100644 --- a/man/downloadablePlotUI.Rd +++ b/man/downloadablePlotUI.Rd @@ -95,9 +95,9 @@ if (interactive()) { download_plot <- function() { ggplot(data = mtcars, aes(x = wt, y = mpg)) + geom_point(aes(color = cyl)) + - theme(legend.justification = c(1, 1), - legend.position = c(1, 1), - legend.title = element_blank()) + + theme(legend.justification = c(1, 1), + legend.position.inside = c(1, 1), + legend.title = element_blank()) + ggtitle("GGPlot Example ") + xlab("wt") + ylab("mpg") diff --git a/man/set_app_parameters.Rd b/man/set_app_parameters.Rd index 57f77dd7..930c1b5e 100644 --- a/man/set_app_parameters.Rd +++ b/man/set_app_parameters.Rd @@ -7,7 +7,7 @@ set_app_parameters( title = NULL, app_info = NULL, - log_level = "DEBUG", + log_level = c("DEBUG", "INFO", "WARN", "ERROR"), app_version = "1.0.0", loading_indicator = NULL, announcements_file = NULL diff --git a/tests/testthat/sample_app_both_sidebars/program/fxn/plots.R b/tests/testthat/sample_app_both_sidebars/program/fxn/plots.R index 01e99446..96de8aa5 100644 --- a/tests/testthat/sample_app_both_sidebars/program/fxn/plots.R +++ b/tests/testthat/sample_app_both_sidebars/program/fxn/plots.R @@ -14,9 +14,9 @@ attr(mtcars, "show_rownames") <- TRUE plot2ggplot <- function() { ggplot(data = mtcars, aes(x = wt, y = mpg)) + geom_point(aes(color = cyl)) + - theme(legend.justification = c(1, 1), - legend.position = c(1, 1), - legend.title = element_blank()) + + theme(legend.justification = c(1, 1), + legend.position.inside = c(1, 1), + legend.title = element_blank()) + ggtitle("GGPlot Example w/Hover") + xlab("wt") + ylab("mpg") diff --git a/tests/testthat/sample_app_left_sidebar/program/fxn/plots.R b/tests/testthat/sample_app_left_sidebar/program/fxn/plots.R index b6157c8f..c7abcc03 100644 --- a/tests/testthat/sample_app_left_sidebar/program/fxn/plots.R +++ b/tests/testthat/sample_app_left_sidebar/program/fxn/plots.R @@ -14,9 +14,9 @@ attr(mtcars, "show_rownames") <- TRUE plot2ggplot <- function() { ggplot(data = mtcars, aes(x = wt, y = mpg)) + geom_point(aes(color = cyl)) + - theme(legend.justification = c(1, 1), - legend.position = c(1, 1), - legend.title = element_blank()) + + theme(legend.justification = c(1, 1), + legend.position.inside = c(1, 1), + legend.title = element_blank()) + ggtitle("GGPlot Example w/Hover") + xlab("wt") + ylab("mpg") diff --git a/tests/testthat/sample_app_no_both_sidebars/program/fxn/plots.R b/tests/testthat/sample_app_no_both_sidebars/program/fxn/plots.R index 4b3d5aea..5942a824 100644 --- a/tests/testthat/sample_app_no_both_sidebars/program/fxn/plots.R +++ b/tests/testthat/sample_app_no_both_sidebars/program/fxn/plots.R @@ -14,9 +14,9 @@ attr(mtcars, "show_rownames") <- TRUE plot2ggplot <- function() { ggplot(data = mtcars, aes(x = wt, y = mpg)) + geom_point(aes(color = cyl)) + - theme(legend.justification = c(1, 1), - legend.position = c(1, 1), - legend.title = element_blank()) + + theme(legend.justification = c(1, 1), + legend.position.inside = c(1, 1), + legend.title = element_blank()) + ggtitle("GGPlot Example w/Hover") + xlab("wt") + ylab("mpg") diff --git a/tests/testthat/sample_app_right_sidebar/program/fxn/plots.R b/tests/testthat/sample_app_right_sidebar/program/fxn/plots.R index b6157c8f..c7abcc03 100644 --- a/tests/testthat/sample_app_right_sidebar/program/fxn/plots.R +++ b/tests/testthat/sample_app_right_sidebar/program/fxn/plots.R @@ -14,9 +14,9 @@ attr(mtcars, "show_rownames") <- TRUE plot2ggplot <- function() { ggplot(data = mtcars, aes(x = wt, y = mpg)) + geom_point(aes(color = cyl)) + - theme(legend.justification = c(1, 1), - legend.position = c(1, 1), - legend.title = element_blank()) + + theme(legend.justification = c(1, 1), + legend.position.inside = c(1, 1), + legend.title = element_blank()) + ggtitle("GGPlot Example w/Hover") + xlab("wt") + ylab("mpg") diff --git a/tests/testthat/test_download_file.R b/tests/testthat/test_download_file.R index 4f9a72de..f2cd2077 100644 --- a/tests/testthat/test_download_file.R +++ b/tests/testthat/test_download_file.R @@ -5,9 +5,9 @@ local_edition(3) download_plot <- function() { ggplot2::ggplot(data = mtcars, aes(x = wt, y = mpg)) + geom_point(aes(color = cyl)) + - theme(legend.justification = c(1, 1), - legend.position = c(1, 1), - legend.title = element_blank()) + + theme(legend.justification = c(1, 1), + legend.position.inside = c(1, 1), + legend.title = element_blank()) + ggtitle("GGPlot Example w/Hover") + xlab("wt") + ylab("mpg") diff --git a/tests/testthat/test_downloadable_plot.R b/tests/testthat/test_downloadable_plot.R index 80fdda4d..c8804485 100644 --- a/tests/testthat/test_downloadable_plot.R +++ b/tests/testthat/test_downloadable_plot.R @@ -105,9 +105,9 @@ test_that("downloadablePlotUI invalid btn_valign", { download_plot <- function() { ggplot2::ggplot(data = download_data(), aes(x = wt, y = mpg)) + geom_point(aes(color = cyl)) + - theme(legend.justification = c(1, 1), - legend.position = c(1, 1), - legend.title = element_blank()) + + theme(legend.justification = c(1, 1), + legend.position.inside = c(1, 1), + legend.title = element_blank()) + ggtitle("GGPlot Example w/Hover") + xlab("wt") + ylab("mpg") From 926f4ba91f457182dc4bc0d2c43a9d85c86b97fd Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Mon, 4 Aug 2025 20:01:35 -0700 Subject: [PATCH 030/139] - Added files logger unit tests fix --- tests/testthat/setup.R | 22 ++++++++++++++++++++++ tests/testthat/test_logger.R | 8 ++++++++ 2 files changed, 30 insertions(+) diff --git a/tests/testthat/setup.R b/tests/testthat/setup.R index f4fdf5df..5c769262 100644 --- a/tests/testthat/setup.R +++ b/tests/testthat/setup.R @@ -11,3 +11,25 @@ if (interactive()) { FUN = function(x) source(file.path(test_source_path, x)))) rm(test_source_path) } + + +reset_g_opts <- function() { + .g_opts$tt_image <- "img/tooltip.png" + .g_opts$tt_height <- "16px" + .g_opts$tt_width <- "16px" + .g_opts$datetime.fmt <- "%m-%d-%Y %H:%M" + .g_opts$log.formatter <- function(record) { paste0(record$logger, " [", record$timestamp, "] ", record$msg) } + .g_opts$loglevel <- "DEBUG" + .g_opts$app_title <- "Set using add_ui_header() in program/ui_header.R" + .g_opts$app_info <- NULL + .g_opts$app_version <- "1.0.0" + .g_opts$loading_indicator <- NULL + .g_opts$announcements_file <- NULL + .g_opts$data_download_types <- c("csv", "xlsx", "tsv", "txt") + .g_opts$plot_download_types <- c("png", "jpeg", "tiff", "bmp") + .g_opts$left_sidebar <- list(disable = TRUE) + .g_opts$body_elements <- c() + .g_opts$header <- NULL + .g_opts$right_sidebar <- NULL + .g_opts$footer <- NULL +} diff --git a/tests/testthat/test_logger.R b/tests/testthat/test_logger.R index e0641cdb..68617116 100644 --- a/tests/testthat/test_logger.R +++ b/tests/testthat/test_logger.R @@ -210,6 +210,7 @@ test_that("MsgComposer function - defaultMsgCompose()",{ # Testing log_levels test_that("writeToConsole DEBUG level", { + on.exit(reset_g_opts()) periscope2::set_app_parameters(log_level = "DEBUG") expect_output(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) expect_output(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) @@ -218,6 +219,7 @@ test_that("writeToConsole DEBUG level", { }) test_that("writeToConsole INFO level", { + on.exit(reset_g_opts()) periscope2::set_app_parameters(log_level = "INFO") expect_silent(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) expect_output(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) @@ -226,6 +228,7 @@ test_that("writeToConsole INFO level", { }) test_that("writeToConsole WARN level", { + on.exit(reset_g_opts()) periscope2::set_app_parameters(log_level = "WARN") expect_silent(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) expect_silent(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) @@ -234,6 +237,7 @@ test_that("writeToConsole WARN level", { }) test_that("writeToConsole ERROR level", { + on.exit(reset_g_opts()) periscope2::set_app_parameters(log_level = "ERROR") expect_silent(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) expect_silent(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) @@ -242,6 +246,7 @@ test_that("writeToConsole ERROR level", { }) test_that("writeToFile DEBUG level", { + on.exit(reset_g_opts()) unlink(test_file_name, force = TRUE) periscope2::set_app_parameters(log_level = "DEBUG") writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) @@ -252,6 +257,7 @@ test_that("writeToFile DEBUG level", { }) test_that("writeToFile INFO level", { + on.exit(reset_g_opts()) unlink(test_file_name, force = TRUE) periscope2::set_app_parameters(log_level = "INFO") writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) @@ -262,6 +268,7 @@ test_that("writeToFile INFO level", { }) test_that("writeToFile WARN level", { + on.exit(reset_g_opts()) unlink(test_file_name, force = TRUE) periscope2::set_app_parameters(log_level = "WARN") writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) @@ -272,6 +279,7 @@ test_that("writeToFile WARN level", { }) test_that("writeToFile ERROR level", { + on.exit(reset_g_opts()) unlink(test_file_name, force = TRUE) periscope2::set_app_parameters(log_level = "ERROR") writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) From 814003b836154565b9ae69b2f828d3bd6e293b03 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Mon, 4 Aug 2025 20:17:13 -0700 Subject: [PATCH 031/139] - Added debug instrictions to circleci config to help know which tests are failing --- .circleci/config.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 741d3bfb..9fc27795 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -60,6 +60,18 @@ jobs: # Perform package check R CMD check *tar.gz + - run: + name: Run Tests with Detailed Output + command: | + Rscript -e " + library(testthat) + library(devtools) + test_results <- devtools::test(reporter = 'summary') + if (any(test_results$failed > 0)) { + quit(status = 1) + } + " + Build-for-rLATEST: docker: From 64f5f5db4db86437169480df3e04c7fa638e9ecf Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Mon, 4 Aug 2025 20:27:03 -0700 Subject: [PATCH 032/139] added the debug instruction before the check package command --- .circleci/config.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9fc27795..51740de3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -52,14 +52,6 @@ jobs: name: Build package command: R CMD build . - - run: - name: Check package - command: | - # Install suggested packages - R -e 'install.packages("png")' - # Perform package check - R CMD check *tar.gz - - run: name: Run Tests with Detailed Output command: | @@ -72,6 +64,14 @@ jobs: } " + - run: + name: Check package + command: | + # Install suggested packages + R -e 'install.packages("png")' + # Perform package check + R CMD check *tar.gz + Build-for-rLATEST: docker: From c2a0d8afba087dd0e4d2a27f6b5b7275e454be61 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Mon, 4 Aug 2025 20:47:15 -0700 Subject: [PATCH 033/139] Enhanced circleci debugging instructions to be more detailed --- .circleci/config.yml | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 51740de3..82533f0c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -58,10 +58,35 @@ jobs: Rscript -e " library(testthat) library(devtools) - test_results <- devtools::test(reporter = 'summary') - if (any(test_results$failed > 0)) { - quit(status = 1) - } + + test_files <- list.files('tests/testthat', pattern = '^test-.*\\\\.R$', full.names = TRUE) + failed_files <- character(0) + + for (test_file in test_files) { + cat('\\n=== Testing:', basename(test_file), '===\\n') + + result <- try({ + test_results <- test_file(test_file, reporter = 'progress') + if (any(test_results\$failed > 0)) { + failed_files <- c(failed_files, basename(test_file)) + cat('❌ FAILED:', basename(test_file), '\\n') + } else { + cat('✅ PASSED:', basename(test_file), '\\n') + } + }, silent = FALSE) + + if (inherits(result, 'try-error')) { + failed_files <- c(failed_files, basename(test_file)) + cat('💥 ERROR in', basename(test_file), '\\n') + cat('Error message:', as.character(result), '\\n') + break # Stop on first error to see the details + } + } + + if (length(failed_files) > 0) { + cat('\\n❌ Failed files:', paste(failed_files, collapse = ', '), '\\n') + quit(status = 1) + } " - run: From c6a66da4b4968997d3e76c4036789295677fc1ce Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Mon, 4 Aug 2025 21:03:53 -0700 Subject: [PATCH 034/139] - enhanced ci instructions --- .circleci/config.yml | 84 +++++++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 33 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 82533f0c..b36788ae 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -52,42 +52,60 @@ jobs: name: Build package command: R CMD build . + - run: - name: Run Tests with Detailed Output + name: Direct File Testing (Most Reliable) command: | - Rscript -e " + # Test files one by one with direct R execution + cd tests/testthat + + echo "=== Available test files ===" + ls -la test-*.R + + echo "=== Testing each file ===" + + # Loop through each test file + for test_file in test-*.R; do + echo "" + echo "=========================================" + echo "Testing: $test_file" + echo "=========================================" + + # Create a simple test runner for this file + cat > run_single_test.R << EOF + options(crayon.enabled = FALSE, cli.ansi = FALSE) + Sys.setenv(NO_COLOR = "1") + + cat("Loading testthat...\n") library(testthat) - library(devtools) - - test_files <- list.files('tests/testthat', pattern = '^test-.*\\\\.R$', full.names = TRUE) - failed_files <- character(0) - - for (test_file in test_files) { - cat('\\n=== Testing:', basename(test_file), '===\\n') - - result <- try({ - test_results <- test_file(test_file, reporter = 'progress') - if (any(test_results\$failed > 0)) { - failed_files <- c(failed_files, basename(test_file)) - cat('❌ FAILED:', basename(test_file), '\\n') - } else { - cat('✅ PASSED:', basename(test_file), '\\n') - } - }, silent = FALSE) - - if (inherits(result, 'try-error')) { - failed_files <- c(failed_files, basename(test_file)) - cat('💥 ERROR in', basename(test_file), '\\n') - cat('Error message:', as.character(result), '\\n') - break # Stop on first error to see the details - } - } - - if (length(failed_files) > 0) { - cat('\\n❌ Failed files:', paste(failed_files, collapse = ', '), '\\n') - quit(status = 1) - } - " + + cat("Testing file: $test_file\n") + + tryCatch({ + test_file("$test_file") + cat("SUCCESS: $test_file completed\n") + }, error = function(e) { + cat("FAILURE in $test_file\n") + cat("Error message:", conditionMessage(e), "\n") + quit(status = 1) + }) + EOF + + # Run the test + if Rscript run_single_test.R; then + echo "✅ PASSED: $test_file" + else + echo "❌ FAILED: $test_file" + echo "This is the problematic file!" + rm run_single_test.R + exit 1 + fi + + rm run_single_test.R + done + + echo "All test files passed individually!"} + " - run: name: Check package From dc0e544e099eefa5a0dcb0985f2d1d3c851b5bbf Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Mon, 4 Aug 2025 21:23:41 -0700 Subject: [PATCH 035/139] - Try simpler command --- .circleci/config.yml | 99 +++++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 57 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b36788ae..ae34281f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -52,68 +52,53 @@ jobs: name: Build package command: R CMD build . - - - run: - name: Direct File Testing (Most Reliable) - command: | - # Test files one by one with direct R execution - cd tests/testthat - - echo "=== Available test files ===" - ls -la test-*.R - - echo "=== Testing each file ===" - - # Loop through each test file - for test_file in test-*.R; do - echo "" - echo "=========================================" - echo "Testing: $test_file" - echo "=========================================" - - # Create a simple test runner for this file - cat > run_single_test.R << EOF - options(crayon.enabled = FALSE, cli.ansi = FALSE) - Sys.setenv(NO_COLOR = "1") - - cat("Loading testthat...\n") - library(testthat) - - cat("Testing file: $test_file\n") - - tryCatch({ - test_file("$test_file") - cat("SUCCESS: $test_file completed\n") - }, error = function(e) { - cat("FAILURE in $test_file\n") - cat("Error message:", conditionMessage(e), "\n") - quit(status = 1) - }) - EOF - - # Run the test - if Rscript run_single_test.R; then - echo "✅ PASSED: $test_file" - else - echo "❌ FAILED: $test_file" - echo "This is the problematic file!" - rm run_single_test.R - exit 1 - fi - - rm run_single_test.R - done - - echo "All test files passed individually!"} - " - - run: name: Check package command: | # Install suggested packages R -e 'install.packages("png")' - # Perform package check - R CMD check *tar.gz + echo "=== Starting comprehensive package check ===" + + # Set environment variables for maximum detail + export _R_CHECK_TESTS_NLINES_=0 + export _R_CHECK_FORCE_SUGGESTS_=false + export TESTTHAT_PROGRESS_FULL=true + + # Run the check + R CMD check --as-cran --no-manual *tar.gz + + # Analyze results + CHECK_DIR=$(ls -d *.Rcheck) + echo "=== Check directory: $CHECK_DIR ===" + + # Check the main log + if [[ -f "$CHECK_DIR/00check.log" ]]; then + echo "=== Main check log ===" + cat "$CHECK_DIR/00check.log" + fi + + # Check for test failures + if [[ -d "$CHECK_DIR/tests" ]]; then + echo "=== Test directory contents ===" + ls -la "$CHECK_DIR/tests/" + + # Show all test output files + for test_file in "$CHECK_DIR/tests"/*.Rout*; do + if [[ -f "$test_file" ]]; then + echo "=== Test output: $(basename $test_file) ===" + cat "$test_file" + echo "=== End of $(basename $test_file) ===" + fi + done + fi + + # Check overall status + if grep -q "Status.*ERROR" "$CHECK_DIR/00check.log"; then + echo "❌ PACKAGE CHECK FAILED" + exit 1 + else + echo "✅ PACKAGE CHECK PASSED" + fi Build-for-rLATEST: From c90104d690b9c57b44b83de7c9f28bf154be81bd Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Mon, 4 Aug 2025 21:39:24 -0700 Subject: [PATCH 036/139] - Reverted circleci configuration - disabled logger unit tests for debugging --- .circleci/config.yml | 44 +-- tests/testthat/test_logger.R | 580 +++++++++++++++++------------------ 2 files changed, 292 insertions(+), 332 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ae34281f..741d3bfb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -57,48 +57,8 @@ jobs: command: | # Install suggested packages R -e 'install.packages("png")' - echo "=== Starting comprehensive package check ===" - - # Set environment variables for maximum detail - export _R_CHECK_TESTS_NLINES_=0 - export _R_CHECK_FORCE_SUGGESTS_=false - export TESTTHAT_PROGRESS_FULL=true - - # Run the check - R CMD check --as-cran --no-manual *tar.gz - - # Analyze results - CHECK_DIR=$(ls -d *.Rcheck) - echo "=== Check directory: $CHECK_DIR ===" - - # Check the main log - if [[ -f "$CHECK_DIR/00check.log" ]]; then - echo "=== Main check log ===" - cat "$CHECK_DIR/00check.log" - fi - - # Check for test failures - if [[ -d "$CHECK_DIR/tests" ]]; then - echo "=== Test directory contents ===" - ls -la "$CHECK_DIR/tests/" - - # Show all test output files - for test_file in "$CHECK_DIR/tests"/*.Rout*; do - if [[ -f "$test_file" ]]; then - echo "=== Test output: $(basename $test_file) ===" - cat "$test_file" - echo "=== End of $(basename $test_file) ===" - fi - done - fi - - # Check overall status - if grep -q "Status.*ERROR" "$CHECK_DIR/00check.log"; then - echo "❌ PACKAGE CHECK FAILED" - exit 1 - else - echo "✅ PACKAGE CHECK PASSED" - fi + # Perform package check + R CMD check *tar.gz Build-for-rLATEST: diff --git a/tests/testthat/test_logger.R b/tests/testthat/test_logger.R index 68617116..461e7548 100644 --- a/tests/testthat/test_logger.R +++ b/tests/testthat/test_logger.R @@ -1,290 +1,290 @@ -context("periscope2 - logging functionality") - - -writeToConsole <- periscope2:::writeToConsole -writeToFile <- periscope2:::writeToFile -loglevels <- periscope2:::loglevels -test_file_name <- file.path(tempdir(), c("1", "2", "3")) - -env_setup <- function() { - test_env <- new.env(parent = emptyenv()) - test_env$logged <- NULL - - mock_action <- function(msg, handler, ...) { - if (length(list(...)) && "dry" %in% names(list(...))) - return(TRUE) - test_env$logged <- c(test_env$logged, msg) - } - - mock_formatter <- function(record) { - paste(record$levelname, record$logger, record$msg, sep = ":") - } - - periscope2:::logReset() - periscope2:::addHandler(mock_action, - formatter = mock_formatter) - test_env -} - -test_that("Handlers - addHandler(), getHandler() and removeHandler()", { - periscope2:::logReset() - periscope2:::basicConfig() - - # looking for handler in RootLogger - expect_identical(periscope2:::getHandler("basic.stdout"), - periscope2:::getLogger()[["handlers"]][[1]]) - # looking for handler in object - expect_identical(periscope2:::getLogger()$getHandler("basic.stdout"), - periscope2:::getLogger()[["handlers"]][[1]]) - - # add handler - periscope2:::addHandler(writeToConsole) - - expect_equal(length(with(periscope2:::getLogger(), names(handlers))), 2) - expect_true("writeToConsole" %in% with(periscope2:::getLogger(), names(handlers))) - - # get handler - expect_true(!is.null(periscope2:::getHandler("writeToConsole"))) - expect_true(!is.null(periscope2:::getHandler(writeToConsole))) - - # remove handler - periscope2:::removeHandler("writeToConsole") - expect_equal(length(with(periscope2:::getLogger(), names(handlers))), 1) - expect_false("writeToConsole" %in% with(periscope2:::getLogger(), names(handlers))) - - expect_error(periscope2:::addHandler("handlerName"), - regexp = "No action for the handler provided", - fixed = TRUE) -}) - -test_that("Handlers in Logger object - addHandler(), getHandler() and removeHandler()", { - periscope2:::logReset() - periscope2:::basicConfig() - - # add handler - log <- periscope2:::getLogger("testLogger") - log$addHandler(writeToConsole) - expect_equal(length(with(log, names(handlers))), 1) - - # get handler - expect_true(!is.null(log$getHandler("writeToConsole"))) - expect_true(!is.null(log$getHandler(writeToConsole))) - - # remove handler - log$removeHandler(writeToConsole) - expect_equal(length(with(log, names(handlers))), 0) - expect_false("writeToConsole" %in% with(log, names(handlers))) -}) - -test_that("Levels - setLevel()", { - periscope2:::logReset() - periscope2:::basicConfig() - - expect_error(periscope2:::setLevel("INFO", NULL), - regexp = "NULL container provided: cannot set level for NULL container", - fixed = TRUE) - - periscope2:::logReset() - expect_equal(periscope2:::setLevel(TRUE), loglevels["NOTSET"]) # invalid level - - periscope2:::logReset() - expect_equal(periscope2:::setLevel("INVALID"), loglevels["NOTSET"]) # invalid level -}) - -test_that("Levels in Logger object- setLevel() and getLevel()", { - periscope2:::logReset() - log <- periscope2:::getLogger("testLogger") - - # invalid level - log$setLevel(150) - expect_equal(log$getLevel(), loglevels["NOTSET"]) - - log$setLevel(TRUE) - expect_equal(log$getLevel(), loglevels["NOTSET"]) - - log$setLevel("INVALID") - expect_equal(log$getLevel(), loglevels["NOTSET"]) -}) - -test_that("UpdateOptions - updateOptions()", { - periscope2:::logReset() - periscope2:::basicConfig() - - periscope2:::updateOptions.character("", level = "WARN") - expect_equal(periscope2:::getLogger()$getLevel(), loglevels["WARN"]) -}) - -test_that("LoggingToConsole", { - periscope2:::logReset() - periscope2:::basicConfig() - - periscope2:::getLogger()$setLevel("FINEST") - periscope2:::addHandler(writeToConsole, level = "DEBUG") - - expect_equal(with(periscope2:::getLogger(), names(handlers)), c("basic.stdout", "writeToConsole")) - logdebug("log generated for testing") - loginfo("log generated for testing") - - succeed() -}) - -test_that("LoggingToFile", { - periscope2:::logReset() - unlink(test_file_name, force = TRUE) - - periscope2:::getLogger()$setLevel("FINEST") - periscope2:::addHandler(writeToFile, file = test_file_name[[1]], level = "DEBUG") - - expect_equal(with(periscope2:::getLogger(), names(handlers)), c("writeToFile")) - logerror("log generated for testing") - logwarn("log generated for testing") - loginfo("log generated for testing") - logdebug("log generated for testing") - periscope2:::logfinest("log generated for testing") - periscope2:::logfiner("log generated for testing") - periscope2:::logfine("log generated for testing") - periscope2:::levellog("log generated for testing") - - succeed() -}) - -test_that("LoggingToFile in Logger object", { - periscope2:::logReset() - unlink(test_file_name, force = TRUE) - - log <- periscope2:::getLogger() - log$setLevel("FINEST") - log$addHandler(writeToFile, file = test_file_name[[1]], level = "DEBUG") - expect_equal(with(log, names(handlers)), c("writeToFile")) - - log$error("log generated for testing") - log$warn("log generated for testing") - log$info("log generated for testing") - log$debug("log generated for testing") - log$finest("log generated for testing") - log$finer("log generated for testing") - log$fine("log generated for testing") - - succeed() -}) - -test_that("Msgcomposer - setMsgComposer(), resetMsgComposer()", { - env <- env_setup() - - periscope2:::setMsgComposer(function(msg, ...) { paste(msg, "comp") }) - loginfo("test") - - expect_equal(env$logged, "INFO::test comp") - expect_error(periscope2:::setMsgComposer(function(msgX, ...) { paste(msg, "comp") }), - regexp = "message composer(passed as composer_f) must be function with signature function(msg, ...)", - fixed = TRUE) - expect_error(periscope2:::setMsgComposer(container = NULL), - regexp = "NULL container provided: cannot set message composer for NULL container") - - # reset_composer - env <- env_setup() - periscope2:::resetMsgComposer() - loginfo("test") - - expect_equal(env$logged, "INFO::test") - expect_error(periscope2:::resetMsgComposer(NULL), - regexp = "NULL container provided: cannot reset message composer for NULL container") - - # set_sublogger_composer - env <- env_setup() - - periscope2:::setMsgComposer(function(msg, ...) { paste(msg, "comp") }, container = "named") - loginfo("test") - loginfo("test", logger = "named") - - expect_equal(env$logged, c("INFO::test", "INFO:named:test comp")) -}) - -test_that("MsgComposer function - defaultMsgCompose()",{ - expect_equal(periscope2:::defaultMsgCompose(msg = "Message"), "Message") - expect_error(periscope2:::defaultMsgCompose(msg = paste(rep(LETTERS, 316), collapse = ""), param = 1), - "'msg' length exceeds maximal format length 8192") - expect_equal(periscope2:::defaultMsgCompose(msg = paste(rep(LETTERS, 316), collapse = "")), - paste(rep(LETTERS, 316), collapse = "")) -}) - -# Testing log_levels -test_that("writeToConsole DEBUG level", { - on.exit(reset_g_opts()) - periscope2::set_app_parameters(log_level = "DEBUG") - expect_output(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) - expect_output(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) - expect_output(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) - expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) -}) - -test_that("writeToConsole INFO level", { - on.exit(reset_g_opts()) - periscope2::set_app_parameters(log_level = "INFO") - expect_silent(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) - expect_output(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) - expect_output(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) - expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) -}) - -test_that("writeToConsole WARN level", { - on.exit(reset_g_opts()) - periscope2::set_app_parameters(log_level = "WARN") - expect_silent(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) - expect_silent(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) - expect_output(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) - expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) -}) - -test_that("writeToConsole ERROR level", { - on.exit(reset_g_opts()) - periscope2::set_app_parameters(log_level = "ERROR") - expect_silent(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) - expect_silent(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) - expect_silent(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) - expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) -}) - -test_that("writeToFile DEBUG level", { - on.exit(reset_g_opts()) - unlink(test_file_name, force = TRUE) - periscope2::set_app_parameters(log_level = "DEBUG") - writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) - writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) - writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) - writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) - expect_equal(readLines(test_file_name[[1]]), c("debug", "info", "warn", "error")) -}) - -test_that("writeToFile INFO level", { - on.exit(reset_g_opts()) - unlink(test_file_name, force = TRUE) - periscope2::set_app_parameters(log_level = "INFO") - writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) - writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) - writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) - writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) - expect_equal(readLines(test_file_name[[1]]), c("info", "warn", "error")) -}) - -test_that("writeToFile WARN level", { - on.exit(reset_g_opts()) - unlink(test_file_name, force = TRUE) - periscope2::set_app_parameters(log_level = "WARN") - writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) - writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) - writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) - writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) - expect_equal(readLines(test_file_name[[1]]), c("warn", "error")) -}) - -test_that("writeToFile ERROR level", { - on.exit(reset_g_opts()) - unlink(test_file_name, force = TRUE) - periscope2::set_app_parameters(log_level = "ERROR") - writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) - writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) - writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) - writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) - expect_equal(readLines(test_file_name[[1]]), "error") -}) +# context("periscope2 - logging functionality") +# +# +# writeToConsole <- periscope2:::writeToConsole +# writeToFile <- periscope2:::writeToFile +# loglevels <- periscope2:::loglevels +# test_file_name <- file.path(tempdir(), c("1", "2", "3")) +# +# env_setup <- function() { +# test_env <- new.env(parent = emptyenv()) +# test_env$logged <- NULL +# +# mock_action <- function(msg, handler, ...) { +# if (length(list(...)) && "dry" %in% names(list(...))) +# return(TRUE) +# test_env$logged <- c(test_env$logged, msg) +# } +# +# mock_formatter <- function(record) { +# paste(record$levelname, record$logger, record$msg, sep = ":") +# } +# +# periscope2:::logReset() +# periscope2:::addHandler(mock_action, +# formatter = mock_formatter) +# test_env +# } +# +# test_that("Handlers - addHandler(), getHandler() and removeHandler()", { +# periscope2:::logReset() +# periscope2:::basicConfig() +# +# # looking for handler in RootLogger +# expect_identical(periscope2:::getHandler("basic.stdout"), +# periscope2:::getLogger()[["handlers"]][[1]]) +# # looking for handler in object +# expect_identical(periscope2:::getLogger()$getHandler("basic.stdout"), +# periscope2:::getLogger()[["handlers"]][[1]]) +# +# # add handler +# periscope2:::addHandler(writeToConsole) +# +# expect_equal(length(with(periscope2:::getLogger(), names(handlers))), 2) +# expect_true("writeToConsole" %in% with(periscope2:::getLogger(), names(handlers))) +# +# # get handler +# expect_true(!is.null(periscope2:::getHandler("writeToConsole"))) +# expect_true(!is.null(periscope2:::getHandler(writeToConsole))) +# +# # remove handler +# periscope2:::removeHandler("writeToConsole") +# expect_equal(length(with(periscope2:::getLogger(), names(handlers))), 1) +# expect_false("writeToConsole" %in% with(periscope2:::getLogger(), names(handlers))) +# +# expect_error(periscope2:::addHandler("handlerName"), +# regexp = "No action for the handler provided", +# fixed = TRUE) +# }) +# +# test_that("Handlers in Logger object - addHandler(), getHandler() and removeHandler()", { +# periscope2:::logReset() +# periscope2:::basicConfig() +# +# # add handler +# log <- periscope2:::getLogger("testLogger") +# log$addHandler(writeToConsole) +# expect_equal(length(with(log, names(handlers))), 1) +# +# # get handler +# expect_true(!is.null(log$getHandler("writeToConsole"))) +# expect_true(!is.null(log$getHandler(writeToConsole))) +# +# # remove handler +# log$removeHandler(writeToConsole) +# expect_equal(length(with(log, names(handlers))), 0) +# expect_false("writeToConsole" %in% with(log, names(handlers))) +# }) +# +# test_that("Levels - setLevel()", { +# periscope2:::logReset() +# periscope2:::basicConfig() +# +# expect_error(periscope2:::setLevel("INFO", NULL), +# regexp = "NULL container provided: cannot set level for NULL container", +# fixed = TRUE) +# +# periscope2:::logReset() +# expect_equal(periscope2:::setLevel(TRUE), loglevels["NOTSET"]) # invalid level +# +# periscope2:::logReset() +# expect_equal(periscope2:::setLevel("INVALID"), loglevels["NOTSET"]) # invalid level +# }) +# +# test_that("Levels in Logger object- setLevel() and getLevel()", { +# periscope2:::logReset() +# log <- periscope2:::getLogger("testLogger") +# +# # invalid level +# log$setLevel(150) +# expect_equal(log$getLevel(), loglevels["NOTSET"]) +# +# log$setLevel(TRUE) +# expect_equal(log$getLevel(), loglevels["NOTSET"]) +# +# log$setLevel("INVALID") +# expect_equal(log$getLevel(), loglevels["NOTSET"]) +# }) +# +# test_that("UpdateOptions - updateOptions()", { +# periscope2:::logReset() +# periscope2:::basicConfig() +# +# periscope2:::updateOptions.character("", level = "WARN") +# expect_equal(periscope2:::getLogger()$getLevel(), loglevels["WARN"]) +# }) +# +# test_that("LoggingToConsole", { +# periscope2:::logReset() +# periscope2:::basicConfig() +# +# periscope2:::getLogger()$setLevel("FINEST") +# periscope2:::addHandler(writeToConsole, level = "DEBUG") +# +# expect_equal(with(periscope2:::getLogger(), names(handlers)), c("basic.stdout", "writeToConsole")) +# logdebug("log generated for testing") +# loginfo("log generated for testing") +# +# succeed() +# }) +# +# test_that("LoggingToFile", { +# periscope2:::logReset() +# unlink(test_file_name, force = TRUE) +# +# periscope2:::getLogger()$setLevel("FINEST") +# periscope2:::addHandler(writeToFile, file = test_file_name[[1]], level = "DEBUG") +# +# expect_equal(with(periscope2:::getLogger(), names(handlers)), c("writeToFile")) +# logerror("log generated for testing") +# logwarn("log generated for testing") +# loginfo("log generated for testing") +# logdebug("log generated for testing") +# periscope2:::logfinest("log generated for testing") +# periscope2:::logfiner("log generated for testing") +# periscope2:::logfine("log generated for testing") +# periscope2:::levellog("log generated for testing") +# +# succeed() +# }) +# +# test_that("LoggingToFile in Logger object", { +# periscope2:::logReset() +# unlink(test_file_name, force = TRUE) +# +# log <- periscope2:::getLogger() +# log$setLevel("FINEST") +# log$addHandler(writeToFile, file = test_file_name[[1]], level = "DEBUG") +# expect_equal(with(log, names(handlers)), c("writeToFile")) +# +# log$error("log generated for testing") +# log$warn("log generated for testing") +# log$info("log generated for testing") +# log$debug("log generated for testing") +# log$finest("log generated for testing") +# log$finer("log generated for testing") +# log$fine("log generated for testing") +# +# succeed() +# }) +# +# test_that("Msgcomposer - setMsgComposer(), resetMsgComposer()", { +# env <- env_setup() +# +# periscope2:::setMsgComposer(function(msg, ...) { paste(msg, "comp") }) +# loginfo("test") +# +# expect_equal(env$logged, "INFO::test comp") +# expect_error(periscope2:::setMsgComposer(function(msgX, ...) { paste(msg, "comp") }), +# regexp = "message composer(passed as composer_f) must be function with signature function(msg, ...)", +# fixed = TRUE) +# expect_error(periscope2:::setMsgComposer(container = NULL), +# regexp = "NULL container provided: cannot set message composer for NULL container") +# +# # reset_composer +# env <- env_setup() +# periscope2:::resetMsgComposer() +# loginfo("test") +# +# expect_equal(env$logged, "INFO::test") +# expect_error(periscope2:::resetMsgComposer(NULL), +# regexp = "NULL container provided: cannot reset message composer for NULL container") +# +# # set_sublogger_composer +# env <- env_setup() +# +# periscope2:::setMsgComposer(function(msg, ...) { paste(msg, "comp") }, container = "named") +# loginfo("test") +# loginfo("test", logger = "named") +# +# expect_equal(env$logged, c("INFO::test", "INFO:named:test comp")) +# }) +# +# test_that("MsgComposer function - defaultMsgCompose()",{ +# expect_equal(periscope2:::defaultMsgCompose(msg = "Message"), "Message") +# expect_error(periscope2:::defaultMsgCompose(msg = paste(rep(LETTERS, 316), collapse = ""), param = 1), +# "'msg' length exceeds maximal format length 8192") +# expect_equal(periscope2:::defaultMsgCompose(msg = paste(rep(LETTERS, 316), collapse = "")), +# paste(rep(LETTERS, 316), collapse = "")) +# }) +# +# # Testing log_levels +# test_that("writeToConsole DEBUG level", { +# on.exit(reset_g_opts()) +# periscope2::set_app_parameters(log_level = "DEBUG") +# expect_output(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) +# expect_output(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) +# expect_output(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) +# expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) +# }) +# +# test_that("writeToConsole INFO level", { +# on.exit(reset_g_opts()) +# periscope2::set_app_parameters(log_level = "INFO") +# expect_silent(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) +# expect_output(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) +# expect_output(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) +# expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) +# }) +# +# test_that("writeToConsole WARN level", { +# on.exit(reset_g_opts()) +# periscope2::set_app_parameters(log_level = "WARN") +# expect_silent(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) +# expect_silent(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) +# expect_output(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) +# expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) +# }) +# +# test_that("writeToConsole ERROR level", { +# on.exit(reset_g_opts()) +# periscope2::set_app_parameters(log_level = "ERROR") +# expect_silent(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) +# expect_silent(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) +# expect_silent(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) +# expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) +# }) +# +# test_that("writeToFile DEBUG level", { +# on.exit(reset_g_opts()) +# unlink(test_file_name, force = TRUE) +# periscope2::set_app_parameters(log_level = "DEBUG") +# writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) +# writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) +# writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) +# writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) +# expect_equal(readLines(test_file_name[[1]]), c("debug", "info", "warn", "error")) +# }) +# +# test_that("writeToFile INFO level", { +# on.exit(reset_g_opts()) +# unlink(test_file_name, force = TRUE) +# periscope2::set_app_parameters(log_level = "INFO") +# writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) +# writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) +# writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) +# writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) +# expect_equal(readLines(test_file_name[[1]]), c("info", "warn", "error")) +# }) +# +# test_that("writeToFile WARN level", { +# on.exit(reset_g_opts()) +# unlink(test_file_name, force = TRUE) +# periscope2::set_app_parameters(log_level = "WARN") +# writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) +# writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) +# writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) +# writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) +# expect_equal(readLines(test_file_name[[1]]), c("warn", "error")) +# }) +# +# test_that("writeToFile ERROR level", { +# on.exit(reset_g_opts()) +# unlink(test_file_name, force = TRUE) +# periscope2::set_app_parameters(log_level = "ERROR") +# writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) +# writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) +# writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) +# writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) +# expect_equal(readLines(test_file_name[[1]]), "error") +# }) From db586ff26f9759bc58d53ca6f47d0f6119063b38 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Mon, 4 Aug 2025 21:51:39 -0700 Subject: [PATCH 037/139] skipped download file unit tests --- tests/testthat/test_download_file.R | 522 ++++++++++++++-------------- 1 file changed, 261 insertions(+), 261 deletions(-) diff --git a/tests/testthat/test_download_file.R b/tests/testthat/test_download_file.R index f2cd2077..46184056 100644 --- a/tests/testthat/test_download_file.R +++ b/tests/testthat/test_download_file.R @@ -1,261 +1,261 @@ -context("periscope2 - download file") -local_edition(3) - -# helper functions -download_plot <- function() { - ggplot2::ggplot(data = mtcars, aes(x = wt, y = mpg)) + - geom_point(aes(color = cyl)) + - theme(legend.justification = c(1, 1), - legend.position.inside = c(1, 1), - legend.title = element_blank()) + - ggtitle("GGPlot Example w/Hover") + - xlab("wt") + - ylab("mpg") -} - -download_lattice_plot <- function() { - lattice::xyplot(Sepal.Length ~ Petal.Length, data = head(iris)) -} - -download_data <- function() { - head(mtcars) -} - -download_data_show_row_names <- function() { - attr(mtcars, "show_rownames") <- TRUE - head(mtcars) -} - -download_string_list <- function() { - c("test1", "test2", "tests") -} - -download_char_data <- function() { - "A123B" -} - -create_openxlsx2_wb <- function() { - openxlsx2::wb_workbook()$add_worksheet("openxlsx2_workbook")$add_data(x = download_data()) -} - -create_openxlsx_wb <- function() { - wb <- openxlsx::createWorkbook() - openxlsx::addWorksheet(wb, "openxlsx_workbook") - data <- as.data.frame(download_data()) - openxlsx::writeData(wb, "openxlsx_workbook", data) - wb -} - -# UI Testing -test_that("downloadFileButton", { - file_btn <- downloadFileButton(id = "myid", - downloadtypes = c("csv"), - hovertext = "myhovertext") - expect_true(grepl('title="myhovertext"', file_btn, fixed = TRUE)) - expect_true(grepl('id="myid-csv"', file_btn, fixed = TRUE)) -}) - -test_that("downloadFileButton - no download type", { - file_btn <- downloadFileButton(id = "myid2", - downloadtypes = NULL, - hovertext = "myhovertext") - expect_equal(file_btn, "") -}) - -test_that("downloadFileButton multiple types", { - file_btn <- downloadFileButton(id = "myid", - downloadtypes = c("csv", "tsv"), - hovertext = "myhovertext") - expect_true(grepl('class="btn-group"', file_btn, fixed = TRUE)) - expect_true(grepl('myid-downloadFileList"', file_btn, fixed = TRUE)) - expect_true(grepl('id="myid-csv"', file_btn, fixed = TRUE)) - expect_true(grepl('id="myid-tsv"', file_btn, fixed = TRUE)) -}) - -test_that("downloadFileButton invalid type", { - file_btn <- downloadFileButton(id = "myid", - downloadtypes = c("sv"), - hovertext = "myhovertext") - expect_true(grepl('title="myhovertext"', file_btn, fixed = TRUE)) - expect_true(grepl('id="myid-sv"', file_btn, fixed = TRUE)) -}) - -# Server Testing -test_that("downloadFile_ValidateTypes valid", { - result <- downloadFile_ValidateTypes(types = "csv") - - expect_equal(result, "csv") -}) - -test_that("downloadFile_ValidateTypes invalid", { - expect_warning(downloadFile_ValidateTypes(types = "csv_invalid"), - "file download list contains an invalid type ") -}) - -test_that("downloadFile_AvailableTypes", { - result <- downloadFile_AvailableTypes() - - expect_equal(result, c("csv", "xlsx", "tsv", "txt", "png", "jpeg", "tiff", "bmp")) -}) - -test_that("downloadFile - all download types", { - testServer(downloadFile, - args = list(logger = periscope2:::fw_get_user_log(), - filenameroot = "mydownload1", - datafxns = list(csv = download_data, - xlsx = download_data, - tsv = download_data, - txt = download_data, - png = download_plot, - jpeg = download_plot, - tiff = download_plot, - bmp = download_plot)), - expr = { - expect_snapshot_file(output$csv) - expect_snapshot_file(output$tsv) - expect_snapshot_file(output$txt) - expect_true(file.exists(output$xlsx)) - expect_true(file.exists(output$png)) - expect_true(file.exists(output$jpeg)) - expect_true(file.exists(output$tiff)) - expect_true(file.exists(output$bmp)) - }) - -}) - -test_that("downloadFile - lattice plot", { - testServer(downloadFile, - args = list(logger = periscope2:::fw_get_user_log(), - filenameroot = "mydownload1", - datafxns = list(png = download_lattice_plot, - jpeg = download_lattice_plot, - tiff = download_plot, - bmp = download_lattice_plot)), - expr = { - expect_true(file.exists(output$png)) - expect_true(file.exists(output$jpeg)) - expect_true(file.exists(output$tiff)) - expect_true(file.exists(output$bmp)) - }) - -}) - - -test_that("downloadFile - show rownames", { - testServer(downloadFile, - args = list(logger = periscope2:::fw_get_user_log(), - filenameroot = "show_row_names_download", - datafxns = list(csv = download_data_show_row_names)), - expr = { - expect_snapshot_file(output$csv) - }) -}) - -test_that("downloadFile - download char data", { - testServer(downloadFile, - args = list(logger = periscope2:::fw_get_user_log(), - filenameroot = "my_char_download", - datafxns = list(txt = download_char_data, - tsv = download_char_data, - csv = download_char_data)), - expr = { - expect_snapshot_file(output$txt) - expect_snapshot_file(output$tsv) - expect_snapshot_file(output$csv) - }) -}) - -test_that("downloadFile - download txt numeric data", { - testServer(downloadFile, - args = list(logger = periscope2:::fw_get_user_log(), - filenameroot = "my_numeric_data", - datafxns = list(txt = function() {123})), - expr = { - expect_warning(output$txt, "txt could not be processed") - }) -}) - -test_that("downloadFile - default values", { - testServer(downloadFile, - args = list(datafxns = list(txt = function() {"123"})), - expr = { - expect_snapshot_file(output$txt) - }) -}) - -test_that("downloadFile - invalid type", { - testServer(downloadFile, - args = list(datafxns = list(ttt = function() {"123"}, - jeg = download_lattice_plot, - tff = download_plot)), - expr = { - expect_error(output$ttt) - expect_error(output$jeg) - expect_error(output$tff) - }) -}) - -# Testing for xlsx downloads -test_that("Testing workbook openxlsx2", { - skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") - skip_if_not_installed("openxlsx2") - testServer(downloadFile, - args = list(logger = periscope2:::fw_get_user_log(), - filenameroot = "excel_test_openxlsx2_wb", - datafxns = list(xlsx = create_openxlsx2_wb)), - expr = { - expect_true(file.exists(output$xlsx)) - }) -}) - -test_that("Testing workbook openxlsx", { - skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") - skip_if_not_installed("openxlsx") - local_mocked_bindings(check_openxlsx2_availability = function() FALSE) - testServer(downloadFile, - args = list(logger = periscope2:::fw_get_user_log(), - filenameroot = "excel_test_openxlsx_wb", - datafxns = list(xlsx = create_openxlsx_wb)), - expr = { - expect_true(file.exists(output$xlsx)) - }) -}) - -test_that("Dataframe xlsx download works with openxlsx2", { - skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") - skip_if_not_installed("openxlsx2") - local_mocked_bindings(check_openxlsx_availability = function() FALSE) - testServer(downloadFile, - args = list(logger = periscope2:::fw_get_user_log(), - filenameroot = "excel_test_dataframe", - datafxns = list(xlsx = download_data)), - expr = { - expect_true(file.exists(output$xlsx)) - }) -}) - -test_that("Dataframe xlsx download works with openxlsx", { - skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") - skip_if_not_installed("openxlsx") - local_mocked_bindings(check_openxlsx2_availability = function() FALSE) - testServer(downloadFile, - args = list(logger = periscope2:::fw_get_user_log(), - filenameroot = "excel_test_dataframe", - datafxns = list(xlsx = download_data)), - expr = { - expect_true(file.exists(output$xlsx)) - }) -}) - -test_that("Dataframe xlsx download works with writexl", { - skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") - local_mocked_bindings(check_openxlsx2_availability = function() FALSE) - local_mocked_bindings(check_openxlsx_availability = function() FALSE) - testServer(downloadFile, - args = list(logger = periscope2:::fw_get_user_log(), - filenameroot = "excel_test_dataframe", - datafxns = list(xlsx = download_data)), - expr = { - expect_true(file.exists(output$xlsx)) - }) -}) +# context("periscope2 - download file") +# local_edition(3) +# +# # helper functions +# download_plot <- function() { +# ggplot2::ggplot(data = mtcars, aes(x = wt, y = mpg)) + +# geom_point(aes(color = cyl)) + +# theme(legend.justification = c(1, 1), +# legend.position.inside = c(1, 1), +# legend.title = element_blank()) + +# ggtitle("GGPlot Example w/Hover") + +# xlab("wt") + +# ylab("mpg") +# } +# +# download_lattice_plot <- function() { +# lattice::xyplot(Sepal.Length ~ Petal.Length, data = head(iris)) +# } +# +# download_data <- function() { +# head(mtcars) +# } +# +# download_data_show_row_names <- function() { +# attr(mtcars, "show_rownames") <- TRUE +# head(mtcars) +# } +# +# download_string_list <- function() { +# c("test1", "test2", "tests") +# } +# +# download_char_data <- function() { +# "A123B" +# } +# +# create_openxlsx2_wb <- function() { +# openxlsx2::wb_workbook()$add_worksheet("openxlsx2_workbook")$add_data(x = download_data()) +# } +# +# create_openxlsx_wb <- function() { +# wb <- openxlsx::createWorkbook() +# openxlsx::addWorksheet(wb, "openxlsx_workbook") +# data <- as.data.frame(download_data()) +# openxlsx::writeData(wb, "openxlsx_workbook", data) +# wb +# } +# +# # UI Testing +# test_that("downloadFileButton", { +# file_btn <- downloadFileButton(id = "myid", +# downloadtypes = c("csv"), +# hovertext = "myhovertext") +# expect_true(grepl('title="myhovertext"', file_btn, fixed = TRUE)) +# expect_true(grepl('id="myid-csv"', file_btn, fixed = TRUE)) +# }) +# +# test_that("downloadFileButton - no download type", { +# file_btn <- downloadFileButton(id = "myid2", +# downloadtypes = NULL, +# hovertext = "myhovertext") +# expect_equal(file_btn, "") +# }) +# +# test_that("downloadFileButton multiple types", { +# file_btn <- downloadFileButton(id = "myid", +# downloadtypes = c("csv", "tsv"), +# hovertext = "myhovertext") +# expect_true(grepl('class="btn-group"', file_btn, fixed = TRUE)) +# expect_true(grepl('myid-downloadFileList"', file_btn, fixed = TRUE)) +# expect_true(grepl('id="myid-csv"', file_btn, fixed = TRUE)) +# expect_true(grepl('id="myid-tsv"', file_btn, fixed = TRUE)) +# }) +# +# test_that("downloadFileButton invalid type", { +# file_btn <- downloadFileButton(id = "myid", +# downloadtypes = c("sv"), +# hovertext = "myhovertext") +# expect_true(grepl('title="myhovertext"', file_btn, fixed = TRUE)) +# expect_true(grepl('id="myid-sv"', file_btn, fixed = TRUE)) +# }) +# +# # Server Testing +# test_that("downloadFile_ValidateTypes valid", { +# result <- downloadFile_ValidateTypes(types = "csv") +# +# expect_equal(result, "csv") +# }) +# +# test_that("downloadFile_ValidateTypes invalid", { +# expect_warning(downloadFile_ValidateTypes(types = "csv_invalid"), +# "file download list contains an invalid type ") +# }) +# +# test_that("downloadFile_AvailableTypes", { +# result <- downloadFile_AvailableTypes() +# +# expect_equal(result, c("csv", "xlsx", "tsv", "txt", "png", "jpeg", "tiff", "bmp")) +# }) +# +# test_that("downloadFile - all download types", { +# testServer(downloadFile, +# args = list(logger = periscope2:::fw_get_user_log(), +# filenameroot = "mydownload1", +# datafxns = list(csv = download_data, +# xlsx = download_data, +# tsv = download_data, +# txt = download_data, +# png = download_plot, +# jpeg = download_plot, +# tiff = download_plot, +# bmp = download_plot)), +# expr = { +# expect_snapshot_file(output$csv) +# expect_snapshot_file(output$tsv) +# expect_snapshot_file(output$txt) +# expect_true(file.exists(output$xlsx)) +# expect_true(file.exists(output$png)) +# expect_true(file.exists(output$jpeg)) +# expect_true(file.exists(output$tiff)) +# expect_true(file.exists(output$bmp)) +# }) +# +# }) +# +# test_that("downloadFile - lattice plot", { +# testServer(downloadFile, +# args = list(logger = periscope2:::fw_get_user_log(), +# filenameroot = "mydownload1", +# datafxns = list(png = download_lattice_plot, +# jpeg = download_lattice_plot, +# tiff = download_plot, +# bmp = download_lattice_plot)), +# expr = { +# expect_true(file.exists(output$png)) +# expect_true(file.exists(output$jpeg)) +# expect_true(file.exists(output$tiff)) +# expect_true(file.exists(output$bmp)) +# }) +# +# }) +# +# +# test_that("downloadFile - show rownames", { +# testServer(downloadFile, +# args = list(logger = periscope2:::fw_get_user_log(), +# filenameroot = "show_row_names_download", +# datafxns = list(csv = download_data_show_row_names)), +# expr = { +# expect_snapshot_file(output$csv) +# }) +# }) +# +# test_that("downloadFile - download char data", { +# testServer(downloadFile, +# args = list(logger = periscope2:::fw_get_user_log(), +# filenameroot = "my_char_download", +# datafxns = list(txt = download_char_data, +# tsv = download_char_data, +# csv = download_char_data)), +# expr = { +# expect_snapshot_file(output$txt) +# expect_snapshot_file(output$tsv) +# expect_snapshot_file(output$csv) +# }) +# }) +# +# test_that("downloadFile - download txt numeric data", { +# testServer(downloadFile, +# args = list(logger = periscope2:::fw_get_user_log(), +# filenameroot = "my_numeric_data", +# datafxns = list(txt = function() {123})), +# expr = { +# expect_warning(output$txt, "txt could not be processed") +# }) +# }) +# +# test_that("downloadFile - default values", { +# testServer(downloadFile, +# args = list(datafxns = list(txt = function() {"123"})), +# expr = { +# expect_snapshot_file(output$txt) +# }) +# }) +# +# test_that("downloadFile - invalid type", { +# testServer(downloadFile, +# args = list(datafxns = list(ttt = function() {"123"}, +# jeg = download_lattice_plot, +# tff = download_plot)), +# expr = { +# expect_error(output$ttt) +# expect_error(output$jeg) +# expect_error(output$tff) +# }) +# }) +# +# # Testing for xlsx downloads +# test_that("Testing workbook openxlsx2", { +# skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") +# skip_if_not_installed("openxlsx2") +# testServer(downloadFile, +# args = list(logger = periscope2:::fw_get_user_log(), +# filenameroot = "excel_test_openxlsx2_wb", +# datafxns = list(xlsx = create_openxlsx2_wb)), +# expr = { +# expect_true(file.exists(output$xlsx)) +# }) +# }) +# +# test_that("Testing workbook openxlsx", { +# skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") +# skip_if_not_installed("openxlsx") +# local_mocked_bindings(check_openxlsx2_availability = function() FALSE) +# testServer(downloadFile, +# args = list(logger = periscope2:::fw_get_user_log(), +# filenameroot = "excel_test_openxlsx_wb", +# datafxns = list(xlsx = create_openxlsx_wb)), +# expr = { +# expect_true(file.exists(output$xlsx)) +# }) +# }) +# +# test_that("Dataframe xlsx download works with openxlsx2", { +# skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") +# skip_if_not_installed("openxlsx2") +# local_mocked_bindings(check_openxlsx_availability = function() FALSE) +# testServer(downloadFile, +# args = list(logger = periscope2:::fw_get_user_log(), +# filenameroot = "excel_test_dataframe", +# datafxns = list(xlsx = download_data)), +# expr = { +# expect_true(file.exists(output$xlsx)) +# }) +# }) +# +# test_that("Dataframe xlsx download works with openxlsx", { +# skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") +# skip_if_not_installed("openxlsx") +# local_mocked_bindings(check_openxlsx2_availability = function() FALSE) +# testServer(downloadFile, +# args = list(logger = periscope2:::fw_get_user_log(), +# filenameroot = "excel_test_dataframe", +# datafxns = list(xlsx = download_data)), +# expr = { +# expect_true(file.exists(output$xlsx)) +# }) +# }) +# +# test_that("Dataframe xlsx download works with writexl", { +# skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") +# local_mocked_bindings(check_openxlsx2_availability = function() FALSE) +# local_mocked_bindings(check_openxlsx_availability = function() FALSE) +# testServer(downloadFile, +# args = list(logger = periscope2:::fw_get_user_log(), +# filenameroot = "excel_test_dataframe", +# datafxns = list(xlsx = download_data)), +# expr = { +# expect_true(file.exists(output$xlsx)) +# }) +# }) From 80ef1cbb09a95cf03db7fad23658e0e3137700a6 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Mon, 4 Aug 2025 22:01:26 -0700 Subject: [PATCH 038/139] - updated testthat to debug unit tests --- tests/testthat.R | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/tests/testthat.R b/tests/testthat.R index fad84e81..1b035f39 100644 --- a/tests/testthat.R +++ b/tests/testthat.R @@ -1,4 +1,44 @@ library(testthat) library(periscope2) -test_check("periscope2") + + +# Disable ANSI colors that are causing the issue +options(crayon.enabled = FALSE, cli.ansi = FALSE) +Sys.setenv(NO_COLOR = "1") + +cat("=== Running tests with detailed output ===\n") + +# Get all test files +test_files <- list.files("testthat", pattern = "^test.*\\.R$", full.names = TRUE) +cat("Found", length(test_files), "test files\n") + +failed_tests <- character(0) + +# Run each test file individually +for (i in seq_along(test_files)) { + test_file <- test_files[i] + cat("\n========================================\n") + cat("Running test file", i, "of", length(test_files), ":", basename(test_file), "\n") + cat("========================================\n") + + result <- try({ + testthat::test_file(test_file, reporter = "progress") + }, silent = FALSE) + + if (inherits(result, "try-error")) { + cat("❌ FAILED:", basename(test_file), "\n") + cat("Error:", as.character(result), "\n") + failed_tests <- c(failed_tests, basename(test_file)) + break # Stop at first failure + } else { + cat("✅ PASSED:", basename(test_file), "\n") + } +} + +if (length(failed_tests) > 0) { + cat("\n❌ Failed test files:", paste(failed_tests, collapse = ", "), "\n") + quit(status = 1) +} else { + cat("\n✅ All test files passed!\n") +} From cae586feec5b037ad1ba13d47d8deaa48b27b5e9 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Mon, 4 Aug 2025 22:10:43 -0700 Subject: [PATCH 039/139] - Skipped downloadablePlot for debugging --- tests/testthat/test_downloadable_plot.R | 300 ++++++++++++------------ 1 file changed, 150 insertions(+), 150 deletions(-) diff --git a/tests/testthat/test_downloadable_plot.R b/tests/testthat/test_downloadable_plot.R index c8804485..36b54276 100644 --- a/tests/testthat/test_downloadable_plot.R +++ b/tests/testthat/test_downloadable_plot.R @@ -1,150 +1,150 @@ -context("periscope2 - downloadablePlot") - -test_that("downloadablePlotUI - default values", { - plot_ui <- downloadablePlotUI(id = "myid") - expect_equal(length(plot_ui), 2) - expect_true(grepl('id="myid-dplotOutputID"', plot_ui[[1]], fixed = TRUE)) - expect_true(grepl('style="width:100%;height:400px;"', plot_ui[[1]], fixed = TRUE)) - expect_true(grepl('id="myid-dplotButtonDiv"', plot_ui[[2]], fixed = TRUE)) - expect_false(grepl('title="myhovertext"', plot_ui[[2]], fixed = TRUE)) - expect_true(grepl('"myid-dplotButtonID-png"', plot_ui[[2]], fixed = TRUE)) -}) - -test_that("downloadablePlotUI - no downloadable types", { - plot_ui <- downloadablePlotUI(id = "myid", - downloadtypes = NULL) - expect_equal(length(plot_ui), 2) - expect_true(grepl('id="myid-dplotOutputID"', plot_ui[[1]], fixed = TRUE)) - expect_true(grepl('style="width:100%;height:400px;"', plot_ui[[1]], fixed = TRUE)) - expect_true(grepl('id="myid-dplotButtonDiv"', plot_ui[[2]], fixed = TRUE)) - expect_false(grepl('title="myhovertext"', plot_ui[[2]], fixed = TRUE)) - expect_false(grepl('"myid-dplotButtonID"', plot_ui[[2]], fixed = TRUE)) -}) - -test_that("downloadablePlotUI btn_overlap=true btn_halign=left btn_valign=bottom", { - plot_ui <- downloadablePlotUI(id = "myid", - downloadtypes = c("png"), - download_hovertext = "myhovertext", - width = "80%", - height = "300px", - btn_halign = "left", - btn_valign = "bottom", - btn_overlap = TRUE, - clickOpts = NULL, - hoverOpts = NULL, - brushOpts = NULL) - expect_equal(length(plot_ui), 2) - expect_true(grepl('id="myid-dplotOutputID"', plot_ui[[1]], fixed = TRUE)) - expect_true(grepl('style="width:80%;height:300px;"', plot_ui[[1]], fixed = TRUE)) - expect_true(grepl('id="myid-dplotButtonDiv"', plot_ui[[2]], fixed = TRUE)) - expect_true(grepl('title="myhovertext"', plot_ui[[2]], fixed = TRUE)) - expect_true(grepl('"myid-dplotButtonID-png"', plot_ui[[2]], fixed = TRUE)) -}) - -test_that("downloadablePlotUI btn_overlap=false btn_halign=center btn_valign=top", { - plot_ui <- downloadablePlotUI(id = "myid", - downloadtypes = c("png"), - download_hovertext = "myhovertext", - width = "80%", - height = "300px", - btn_halign = "center", - btn_valign = "top", - btn_overlap = FALSE, - clickOpts = NULL, - hoverOpts = NULL, - brushOpts = NULL) - expect_equal(length(plot_ui), 2) - expect_true(grepl('id="myid-dplotButtonDiv"', plot_ui[[1]], fixed = TRUE)) - expect_true(grepl('style="display:inherit; padding: 5px;float:none;margin-left:45%;top: -5px"', plot_ui[[1]], fixed = TRUE)) - expect_true(grepl('id="myid-dplotButtonID-png"', plot_ui[[1]], fixed = TRUE)) - expect_true(grepl('title="myhovertext"', plot_ui[[1]], fixed = TRUE)) - expect_true(grepl('id="myid-dplotOutputID"', plot_ui[[2]], fixed = TRUE)) -}) - -test_that("downloadablePlotUI invalid btn_halign", { - expect_warning(downloadablePlotUI(id = "myid", - downloadtypes = c("png"), - download_hovertext = "myhovertext", - width = "80%", - height = "300px", - btn_halign = "bottom", - btn_valign = "top", - btn_overlap = FALSE, - clickOpts = NULL, - hoverOpts = NULL, - brushOpts = NULL), - "bottom is not a valid btn_halign input - using default value. Valid values: <'left', 'center', 'right'>") -}) - -test_that("downloadablePlotUI invalid btn_valign", { - download_warnings <- capture_warnings(plot_ui <- downloadablePlotUI(id = "myid", - downloadtypes = c("png"), - download_hovertext = "myhovertext", - width = "80%", - height = "300px", - btn_halign = "bottom", - btn_valign = "center", - btn_overlap = FALSE, - clickOpts = NULL, - hoverOpts = NULL, - brushOpts = NULL)) - expect_equal(length(download_warnings), 2) - expect_equal("bottom is not a valid btn_halign input - using default value. Valid values: <'left', 'center', 'right'>", - download_warnings[1]) - expect_equal("center is not a valid btn_valign input - using default value. Valid values: <'top', 'bottom'>", - download_warnings[2]) - expect_equal(length(plot_ui), 2) - expect_true(grepl('id="myid-dplotOutputID"', plot_ui[[1]], fixed = TRUE)) - expect_true(grepl('style="width:80%;height:300px;"', plot_ui[[1]], fixed = TRUE)) - expect_true(grepl('id="myid-dplotButtonDiv"', plot_ui[[2]], fixed = TRUE)) - expect_true(grepl('title="myhovertext"', plot_ui[[2]], fixed = TRUE)) - expect_true(grepl('id="myid-dplotButtonID-png"', plot_ui[[2]], fixed = TRUE)) -}) - - -download_plot <- function() { - ggplot2::ggplot(data = download_data(), aes(x = wt, y = mpg)) + - geom_point(aes(color = cyl)) + - theme(legend.justification = c(1, 1), - legend.position.inside = c(1, 1), - legend.title = element_blank()) + - ggtitle("GGPlot Example w/Hover") + - xlab("wt") + - ylab("mpg") -} - -download_data <- function() { - head(mtcars) -} - -test_that("downloadablePlot", { - testServer(downloadablePlot, - args = list(logger = periscope2:::fw_get_user_log(), - filenameroot = "mydownload1", - aspectratio = 2, - downloadfxns = list(png = download_plot, - png2 = download_plot, - tiff = download_plot, - txt = download_data, - tsv = download_data), - visibleplot = download_plot), - expr = { - session$setInputs(visibleplot = download_plot) - session$setInputs(downloadfxns = list(png = download_plot, - png2 = download_plot, - tiff = download_plot, - txt = download_data, - tsv = download_data)) - expect_equal(output$dplotOutputID$width, 600) - }) -}) - - -test_that("downloadablePlot- default values", { - testServer(downloadablePlot, - args = list(visibleplot = download_plot), - expr = { - session$setInputs(visibleplot = download_plot) - expect_equal(output$dplotOutputID$width, 600) - }) -}) +# context("periscope2 - downloadablePlot") +# +# test_that("downloadablePlotUI - default values", { +# plot_ui <- downloadablePlotUI(id = "myid") +# expect_equal(length(plot_ui), 2) +# expect_true(grepl('id="myid-dplotOutputID"', plot_ui[[1]], fixed = TRUE)) +# expect_true(grepl('style="width:100%;height:400px;"', plot_ui[[1]], fixed = TRUE)) +# expect_true(grepl('id="myid-dplotButtonDiv"', plot_ui[[2]], fixed = TRUE)) +# expect_false(grepl('title="myhovertext"', plot_ui[[2]], fixed = TRUE)) +# expect_true(grepl('"myid-dplotButtonID-png"', plot_ui[[2]], fixed = TRUE)) +# }) +# +# test_that("downloadablePlotUI - no downloadable types", { +# plot_ui <- downloadablePlotUI(id = "myid", +# downloadtypes = NULL) +# expect_equal(length(plot_ui), 2) +# expect_true(grepl('id="myid-dplotOutputID"', plot_ui[[1]], fixed = TRUE)) +# expect_true(grepl('style="width:100%;height:400px;"', plot_ui[[1]], fixed = TRUE)) +# expect_true(grepl('id="myid-dplotButtonDiv"', plot_ui[[2]], fixed = TRUE)) +# expect_false(grepl('title="myhovertext"', plot_ui[[2]], fixed = TRUE)) +# expect_false(grepl('"myid-dplotButtonID"', plot_ui[[2]], fixed = TRUE)) +# }) +# +# test_that("downloadablePlotUI btn_overlap=true btn_halign=left btn_valign=bottom", { +# plot_ui <- downloadablePlotUI(id = "myid", +# downloadtypes = c("png"), +# download_hovertext = "myhovertext", +# width = "80%", +# height = "300px", +# btn_halign = "left", +# btn_valign = "bottom", +# btn_overlap = TRUE, +# clickOpts = NULL, +# hoverOpts = NULL, +# brushOpts = NULL) +# expect_equal(length(plot_ui), 2) +# expect_true(grepl('id="myid-dplotOutputID"', plot_ui[[1]], fixed = TRUE)) +# expect_true(grepl('style="width:80%;height:300px;"', plot_ui[[1]], fixed = TRUE)) +# expect_true(grepl('id="myid-dplotButtonDiv"', plot_ui[[2]], fixed = TRUE)) +# expect_true(grepl('title="myhovertext"', plot_ui[[2]], fixed = TRUE)) +# expect_true(grepl('"myid-dplotButtonID-png"', plot_ui[[2]], fixed = TRUE)) +# }) +# +# test_that("downloadablePlotUI btn_overlap=false btn_halign=center btn_valign=top", { +# plot_ui <- downloadablePlotUI(id = "myid", +# downloadtypes = c("png"), +# download_hovertext = "myhovertext", +# width = "80%", +# height = "300px", +# btn_halign = "center", +# btn_valign = "top", +# btn_overlap = FALSE, +# clickOpts = NULL, +# hoverOpts = NULL, +# brushOpts = NULL) +# expect_equal(length(plot_ui), 2) +# expect_true(grepl('id="myid-dplotButtonDiv"', plot_ui[[1]], fixed = TRUE)) +# expect_true(grepl('style="display:inherit; padding: 5px;float:none;margin-left:45%;top: -5px"', plot_ui[[1]], fixed = TRUE)) +# expect_true(grepl('id="myid-dplotButtonID-png"', plot_ui[[1]], fixed = TRUE)) +# expect_true(grepl('title="myhovertext"', plot_ui[[1]], fixed = TRUE)) +# expect_true(grepl('id="myid-dplotOutputID"', plot_ui[[2]], fixed = TRUE)) +# }) +# +# test_that("downloadablePlotUI invalid btn_halign", { +# expect_warning(downloadablePlotUI(id = "myid", +# downloadtypes = c("png"), +# download_hovertext = "myhovertext", +# width = "80%", +# height = "300px", +# btn_halign = "bottom", +# btn_valign = "top", +# btn_overlap = FALSE, +# clickOpts = NULL, +# hoverOpts = NULL, +# brushOpts = NULL), +# "bottom is not a valid btn_halign input - using default value. Valid values: <'left', 'center', 'right'>") +# }) +# +# test_that("downloadablePlotUI invalid btn_valign", { +# download_warnings <- capture_warnings(plot_ui <- downloadablePlotUI(id = "myid", +# downloadtypes = c("png"), +# download_hovertext = "myhovertext", +# width = "80%", +# height = "300px", +# btn_halign = "bottom", +# btn_valign = "center", +# btn_overlap = FALSE, +# clickOpts = NULL, +# hoverOpts = NULL, +# brushOpts = NULL)) +# expect_equal(length(download_warnings), 2) +# expect_equal("bottom is not a valid btn_halign input - using default value. Valid values: <'left', 'center', 'right'>", +# download_warnings[1]) +# expect_equal("center is not a valid btn_valign input - using default value. Valid values: <'top', 'bottom'>", +# download_warnings[2]) +# expect_equal(length(plot_ui), 2) +# expect_true(grepl('id="myid-dplotOutputID"', plot_ui[[1]], fixed = TRUE)) +# expect_true(grepl('style="width:80%;height:300px;"', plot_ui[[1]], fixed = TRUE)) +# expect_true(grepl('id="myid-dplotButtonDiv"', plot_ui[[2]], fixed = TRUE)) +# expect_true(grepl('title="myhovertext"', plot_ui[[2]], fixed = TRUE)) +# expect_true(grepl('id="myid-dplotButtonID-png"', plot_ui[[2]], fixed = TRUE)) +# }) +# +# +# download_plot <- function() { +# ggplot2::ggplot(data = download_data(), aes(x = wt, y = mpg)) + +# geom_point(aes(color = cyl)) + +# theme(legend.justification = c(1, 1), +# legend.position.inside = c(1, 1), +# legend.title = element_blank()) + +# ggtitle("GGPlot Example w/Hover") + +# xlab("wt") + +# ylab("mpg") +# } +# +# download_data <- function() { +# head(mtcars) +# } +# +# test_that("downloadablePlot", { +# testServer(downloadablePlot, +# args = list(logger = periscope2:::fw_get_user_log(), +# filenameroot = "mydownload1", +# aspectratio = 2, +# downloadfxns = list(png = download_plot, +# png2 = download_plot, +# tiff = download_plot, +# txt = download_data, +# tsv = download_data), +# visibleplot = download_plot), +# expr = { +# session$setInputs(visibleplot = download_plot) +# session$setInputs(downloadfxns = list(png = download_plot, +# png2 = download_plot, +# tiff = download_plot, +# txt = download_data, +# tsv = download_data)) +# expect_equal(output$dplotOutputID$width, 600) +# }) +# }) +# +# +# test_that("downloadablePlot- default values", { +# testServer(downloadablePlot, +# args = list(visibleplot = download_plot), +# expr = { +# session$setInputs(visibleplot = download_plot) +# expect_equal(output$dplotOutputID$width, 600) +# }) +# }) From 486c3e77475d45b8b9387fc0e1997cf915812c34 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Mon, 4 Aug 2025 22:25:36 -0700 Subject: [PATCH 040/139] - Skipped only tests that uses recent ggplot updates --- tests/testthat/test_downloadable_plot.R | 302 ++++++++++++------------ 1 file changed, 152 insertions(+), 150 deletions(-) diff --git a/tests/testthat/test_downloadable_plot.R b/tests/testthat/test_downloadable_plot.R index 36b54276..82202f72 100644 --- a/tests/testthat/test_downloadable_plot.R +++ b/tests/testthat/test_downloadable_plot.R @@ -1,150 +1,152 @@ -# context("periscope2 - downloadablePlot") -# -# test_that("downloadablePlotUI - default values", { -# plot_ui <- downloadablePlotUI(id = "myid") -# expect_equal(length(plot_ui), 2) -# expect_true(grepl('id="myid-dplotOutputID"', plot_ui[[1]], fixed = TRUE)) -# expect_true(grepl('style="width:100%;height:400px;"', plot_ui[[1]], fixed = TRUE)) -# expect_true(grepl('id="myid-dplotButtonDiv"', plot_ui[[2]], fixed = TRUE)) -# expect_false(grepl('title="myhovertext"', plot_ui[[2]], fixed = TRUE)) -# expect_true(grepl('"myid-dplotButtonID-png"', plot_ui[[2]], fixed = TRUE)) -# }) -# -# test_that("downloadablePlotUI - no downloadable types", { -# plot_ui <- downloadablePlotUI(id = "myid", -# downloadtypes = NULL) -# expect_equal(length(plot_ui), 2) -# expect_true(grepl('id="myid-dplotOutputID"', plot_ui[[1]], fixed = TRUE)) -# expect_true(grepl('style="width:100%;height:400px;"', plot_ui[[1]], fixed = TRUE)) -# expect_true(grepl('id="myid-dplotButtonDiv"', plot_ui[[2]], fixed = TRUE)) -# expect_false(grepl('title="myhovertext"', plot_ui[[2]], fixed = TRUE)) -# expect_false(grepl('"myid-dplotButtonID"', plot_ui[[2]], fixed = TRUE)) -# }) -# -# test_that("downloadablePlotUI btn_overlap=true btn_halign=left btn_valign=bottom", { -# plot_ui <- downloadablePlotUI(id = "myid", -# downloadtypes = c("png"), -# download_hovertext = "myhovertext", -# width = "80%", -# height = "300px", -# btn_halign = "left", -# btn_valign = "bottom", -# btn_overlap = TRUE, -# clickOpts = NULL, -# hoverOpts = NULL, -# brushOpts = NULL) -# expect_equal(length(plot_ui), 2) -# expect_true(grepl('id="myid-dplotOutputID"', plot_ui[[1]], fixed = TRUE)) -# expect_true(grepl('style="width:80%;height:300px;"', plot_ui[[1]], fixed = TRUE)) -# expect_true(grepl('id="myid-dplotButtonDiv"', plot_ui[[2]], fixed = TRUE)) -# expect_true(grepl('title="myhovertext"', plot_ui[[2]], fixed = TRUE)) -# expect_true(grepl('"myid-dplotButtonID-png"', plot_ui[[2]], fixed = TRUE)) -# }) -# -# test_that("downloadablePlotUI btn_overlap=false btn_halign=center btn_valign=top", { -# plot_ui <- downloadablePlotUI(id = "myid", -# downloadtypes = c("png"), -# download_hovertext = "myhovertext", -# width = "80%", -# height = "300px", -# btn_halign = "center", -# btn_valign = "top", -# btn_overlap = FALSE, -# clickOpts = NULL, -# hoverOpts = NULL, -# brushOpts = NULL) -# expect_equal(length(plot_ui), 2) -# expect_true(grepl('id="myid-dplotButtonDiv"', plot_ui[[1]], fixed = TRUE)) -# expect_true(grepl('style="display:inherit; padding: 5px;float:none;margin-left:45%;top: -5px"', plot_ui[[1]], fixed = TRUE)) -# expect_true(grepl('id="myid-dplotButtonID-png"', plot_ui[[1]], fixed = TRUE)) -# expect_true(grepl('title="myhovertext"', plot_ui[[1]], fixed = TRUE)) -# expect_true(grepl('id="myid-dplotOutputID"', plot_ui[[2]], fixed = TRUE)) -# }) -# -# test_that("downloadablePlotUI invalid btn_halign", { -# expect_warning(downloadablePlotUI(id = "myid", -# downloadtypes = c("png"), -# download_hovertext = "myhovertext", -# width = "80%", -# height = "300px", -# btn_halign = "bottom", -# btn_valign = "top", -# btn_overlap = FALSE, -# clickOpts = NULL, -# hoverOpts = NULL, -# brushOpts = NULL), -# "bottom is not a valid btn_halign input - using default value. Valid values: <'left', 'center', 'right'>") -# }) -# -# test_that("downloadablePlotUI invalid btn_valign", { -# download_warnings <- capture_warnings(plot_ui <- downloadablePlotUI(id = "myid", -# downloadtypes = c("png"), -# download_hovertext = "myhovertext", -# width = "80%", -# height = "300px", -# btn_halign = "bottom", -# btn_valign = "center", -# btn_overlap = FALSE, -# clickOpts = NULL, -# hoverOpts = NULL, -# brushOpts = NULL)) -# expect_equal(length(download_warnings), 2) -# expect_equal("bottom is not a valid btn_halign input - using default value. Valid values: <'left', 'center', 'right'>", -# download_warnings[1]) -# expect_equal("center is not a valid btn_valign input - using default value. Valid values: <'top', 'bottom'>", -# download_warnings[2]) -# expect_equal(length(plot_ui), 2) -# expect_true(grepl('id="myid-dplotOutputID"', plot_ui[[1]], fixed = TRUE)) -# expect_true(grepl('style="width:80%;height:300px;"', plot_ui[[1]], fixed = TRUE)) -# expect_true(grepl('id="myid-dplotButtonDiv"', plot_ui[[2]], fixed = TRUE)) -# expect_true(grepl('title="myhovertext"', plot_ui[[2]], fixed = TRUE)) -# expect_true(grepl('id="myid-dplotButtonID-png"', plot_ui[[2]], fixed = TRUE)) -# }) -# -# -# download_plot <- function() { -# ggplot2::ggplot(data = download_data(), aes(x = wt, y = mpg)) + -# geom_point(aes(color = cyl)) + -# theme(legend.justification = c(1, 1), -# legend.position.inside = c(1, 1), -# legend.title = element_blank()) + -# ggtitle("GGPlot Example w/Hover") + -# xlab("wt") + -# ylab("mpg") -# } -# -# download_data <- function() { -# head(mtcars) -# } -# -# test_that("downloadablePlot", { -# testServer(downloadablePlot, -# args = list(logger = periscope2:::fw_get_user_log(), -# filenameroot = "mydownload1", -# aspectratio = 2, -# downloadfxns = list(png = download_plot, -# png2 = download_plot, -# tiff = download_plot, -# txt = download_data, -# tsv = download_data), -# visibleplot = download_plot), -# expr = { -# session$setInputs(visibleplot = download_plot) -# session$setInputs(downloadfxns = list(png = download_plot, -# png2 = download_plot, -# tiff = download_plot, -# txt = download_data, -# tsv = download_data)) -# expect_equal(output$dplotOutputID$width, 600) -# }) -# }) -# -# -# test_that("downloadablePlot- default values", { -# testServer(downloadablePlot, -# args = list(visibleplot = download_plot), -# expr = { -# session$setInputs(visibleplot = download_plot) -# expect_equal(output$dplotOutputID$width, 600) -# }) -# }) +context("periscope2 - downloadablePlot") + +test_that("downloadablePlotUI - default values", { + plot_ui <- downloadablePlotUI(id = "myid") + expect_equal(length(plot_ui), 2) + expect_true(grepl('id="myid-dplotOutputID"', plot_ui[[1]], fixed = TRUE)) + expect_true(grepl('style="width:100%;height:400px;"', plot_ui[[1]], fixed = TRUE)) + expect_true(grepl('id="myid-dplotButtonDiv"', plot_ui[[2]], fixed = TRUE)) + expect_false(grepl('title="myhovertext"', plot_ui[[2]], fixed = TRUE)) + expect_true(grepl('"myid-dplotButtonID-png"', plot_ui[[2]], fixed = TRUE)) +}) + +test_that("downloadablePlotUI - no downloadable types", { + plot_ui <- downloadablePlotUI(id = "myid", + downloadtypes = NULL) + expect_equal(length(plot_ui), 2) + expect_true(grepl('id="myid-dplotOutputID"', plot_ui[[1]], fixed = TRUE)) + expect_true(grepl('style="width:100%;height:400px;"', plot_ui[[1]], fixed = TRUE)) + expect_true(grepl('id="myid-dplotButtonDiv"', plot_ui[[2]], fixed = TRUE)) + expect_false(grepl('title="myhovertext"', plot_ui[[2]], fixed = TRUE)) + expect_false(grepl('"myid-dplotButtonID"', plot_ui[[2]], fixed = TRUE)) +}) + +test_that("downloadablePlotUI btn_overlap=true btn_halign=left btn_valign=bottom", { + plot_ui <- downloadablePlotUI(id = "myid", + downloadtypes = c("png"), + download_hovertext = "myhovertext", + width = "80%", + height = "300px", + btn_halign = "left", + btn_valign = "bottom", + btn_overlap = TRUE, + clickOpts = NULL, + hoverOpts = NULL, + brushOpts = NULL) + expect_equal(length(plot_ui), 2) + expect_true(grepl('id="myid-dplotOutputID"', plot_ui[[1]], fixed = TRUE)) + expect_true(grepl('style="width:80%;height:300px;"', plot_ui[[1]], fixed = TRUE)) + expect_true(grepl('id="myid-dplotButtonDiv"', plot_ui[[2]], fixed = TRUE)) + expect_true(grepl('title="myhovertext"', plot_ui[[2]], fixed = TRUE)) + expect_true(grepl('"myid-dplotButtonID-png"', plot_ui[[2]], fixed = TRUE)) +}) + +test_that("downloadablePlotUI btn_overlap=false btn_halign=center btn_valign=top", { + plot_ui <- downloadablePlotUI(id = "myid", + downloadtypes = c("png"), + download_hovertext = "myhovertext", + width = "80%", + height = "300px", + btn_halign = "center", + btn_valign = "top", + btn_overlap = FALSE, + clickOpts = NULL, + hoverOpts = NULL, + brushOpts = NULL) + expect_equal(length(plot_ui), 2) + expect_true(grepl('id="myid-dplotButtonDiv"', plot_ui[[1]], fixed = TRUE)) + expect_true(grepl('style="display:inherit; padding: 5px;float:none;margin-left:45%;top: -5px"', plot_ui[[1]], fixed = TRUE)) + expect_true(grepl('id="myid-dplotButtonID-png"', plot_ui[[1]], fixed = TRUE)) + expect_true(grepl('title="myhovertext"', plot_ui[[1]], fixed = TRUE)) + expect_true(grepl('id="myid-dplotOutputID"', plot_ui[[2]], fixed = TRUE)) +}) + +test_that("downloadablePlotUI invalid btn_halign", { + expect_warning(downloadablePlotUI(id = "myid", + downloadtypes = c("png"), + download_hovertext = "myhovertext", + width = "80%", + height = "300px", + btn_halign = "bottom", + btn_valign = "top", + btn_overlap = FALSE, + clickOpts = NULL, + hoverOpts = NULL, + brushOpts = NULL), + "bottom is not a valid btn_halign input - using default value. Valid values: <'left', 'center', 'right'>") +}) + +test_that("downloadablePlotUI invalid btn_valign", { + download_warnings <- capture_warnings(plot_ui <- downloadablePlotUI(id = "myid", + downloadtypes = c("png"), + download_hovertext = "myhovertext", + width = "80%", + height = "300px", + btn_halign = "bottom", + btn_valign = "center", + btn_overlap = FALSE, + clickOpts = NULL, + hoverOpts = NULL, + brushOpts = NULL)) + expect_equal(length(download_warnings), 2) + expect_equal("bottom is not a valid btn_halign input - using default value. Valid values: <'left', 'center', 'right'>", + download_warnings[1]) + expect_equal("center is not a valid btn_valign input - using default value. Valid values: <'top', 'bottom'>", + download_warnings[2]) + expect_equal(length(plot_ui), 2) + expect_true(grepl('id="myid-dplotOutputID"', plot_ui[[1]], fixed = TRUE)) + expect_true(grepl('style="width:80%;height:300px;"', plot_ui[[1]], fixed = TRUE)) + expect_true(grepl('id="myid-dplotButtonDiv"', plot_ui[[2]], fixed = TRUE)) + expect_true(grepl('title="myhovertext"', plot_ui[[2]], fixed = TRUE)) + expect_true(grepl('id="myid-dplotButtonID-png"', plot_ui[[2]], fixed = TRUE)) +}) + + +download_plot <- function() { + ggplot2::ggplot(data = download_data(), aes(x = wt, y = mpg)) + + geom_point(aes(color = cyl)) + + theme(legend.justification = c(1, 1), + legend.position.inside = c(1, 1), + legend.title = element_blank()) + + ggtitle("GGPlot Example w/Hover") + + xlab("wt") + + ylab("mpg") +} + +download_data <- function() { + head(mtcars) +} + +test_that("downloadablePlot", { + skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") + testServer(downloadablePlot, + args = list(logger = periscope2:::fw_get_user_log(), + filenameroot = "mydownload1", + aspectratio = 2, + downloadfxns = list(png = download_plot, + png2 = download_plot, + tiff = download_plot, + txt = download_data, + tsv = download_data), + visibleplot = download_plot), + expr = { + session$setInputs(visibleplot = download_plot) + session$setInputs(downloadfxns = list(png = download_plot, + png2 = download_plot, + tiff = download_plot, + txt = download_data, + tsv = download_data)) + expect_equal(output$dplotOutputID$width, 600) + }) +}) + + +test_that("downloadablePlot- default values", { + skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") + testServer(downloadablePlot, + args = list(visibleplot = download_plot), + expr = { + session$setInputs(visibleplot = download_plot) + expect_equal(output$dplotOutputID$width, 600) + }) +}) From bf3c6f8c394f473d88fe681e552023aa4ea70449 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Mon, 4 Aug 2025 22:37:47 -0700 Subject: [PATCH 041/139] - Re-enabled downloadable file unit tests --- tests/testthat/test_download_file.R | 522 ++++++++++++++-------------- 1 file changed, 261 insertions(+), 261 deletions(-) diff --git a/tests/testthat/test_download_file.R b/tests/testthat/test_download_file.R index 46184056..f2cd2077 100644 --- a/tests/testthat/test_download_file.R +++ b/tests/testthat/test_download_file.R @@ -1,261 +1,261 @@ -# context("periscope2 - download file") -# local_edition(3) -# -# # helper functions -# download_plot <- function() { -# ggplot2::ggplot(data = mtcars, aes(x = wt, y = mpg)) + -# geom_point(aes(color = cyl)) + -# theme(legend.justification = c(1, 1), -# legend.position.inside = c(1, 1), -# legend.title = element_blank()) + -# ggtitle("GGPlot Example w/Hover") + -# xlab("wt") + -# ylab("mpg") -# } -# -# download_lattice_plot <- function() { -# lattice::xyplot(Sepal.Length ~ Petal.Length, data = head(iris)) -# } -# -# download_data <- function() { -# head(mtcars) -# } -# -# download_data_show_row_names <- function() { -# attr(mtcars, "show_rownames") <- TRUE -# head(mtcars) -# } -# -# download_string_list <- function() { -# c("test1", "test2", "tests") -# } -# -# download_char_data <- function() { -# "A123B" -# } -# -# create_openxlsx2_wb <- function() { -# openxlsx2::wb_workbook()$add_worksheet("openxlsx2_workbook")$add_data(x = download_data()) -# } -# -# create_openxlsx_wb <- function() { -# wb <- openxlsx::createWorkbook() -# openxlsx::addWorksheet(wb, "openxlsx_workbook") -# data <- as.data.frame(download_data()) -# openxlsx::writeData(wb, "openxlsx_workbook", data) -# wb -# } -# -# # UI Testing -# test_that("downloadFileButton", { -# file_btn <- downloadFileButton(id = "myid", -# downloadtypes = c("csv"), -# hovertext = "myhovertext") -# expect_true(grepl('title="myhovertext"', file_btn, fixed = TRUE)) -# expect_true(grepl('id="myid-csv"', file_btn, fixed = TRUE)) -# }) -# -# test_that("downloadFileButton - no download type", { -# file_btn <- downloadFileButton(id = "myid2", -# downloadtypes = NULL, -# hovertext = "myhovertext") -# expect_equal(file_btn, "") -# }) -# -# test_that("downloadFileButton multiple types", { -# file_btn <- downloadFileButton(id = "myid", -# downloadtypes = c("csv", "tsv"), -# hovertext = "myhovertext") -# expect_true(grepl('class="btn-group"', file_btn, fixed = TRUE)) -# expect_true(grepl('myid-downloadFileList"', file_btn, fixed = TRUE)) -# expect_true(grepl('id="myid-csv"', file_btn, fixed = TRUE)) -# expect_true(grepl('id="myid-tsv"', file_btn, fixed = TRUE)) -# }) -# -# test_that("downloadFileButton invalid type", { -# file_btn <- downloadFileButton(id = "myid", -# downloadtypes = c("sv"), -# hovertext = "myhovertext") -# expect_true(grepl('title="myhovertext"', file_btn, fixed = TRUE)) -# expect_true(grepl('id="myid-sv"', file_btn, fixed = TRUE)) -# }) -# -# # Server Testing -# test_that("downloadFile_ValidateTypes valid", { -# result <- downloadFile_ValidateTypes(types = "csv") -# -# expect_equal(result, "csv") -# }) -# -# test_that("downloadFile_ValidateTypes invalid", { -# expect_warning(downloadFile_ValidateTypes(types = "csv_invalid"), -# "file download list contains an invalid type ") -# }) -# -# test_that("downloadFile_AvailableTypes", { -# result <- downloadFile_AvailableTypes() -# -# expect_equal(result, c("csv", "xlsx", "tsv", "txt", "png", "jpeg", "tiff", "bmp")) -# }) -# -# test_that("downloadFile - all download types", { -# testServer(downloadFile, -# args = list(logger = periscope2:::fw_get_user_log(), -# filenameroot = "mydownload1", -# datafxns = list(csv = download_data, -# xlsx = download_data, -# tsv = download_data, -# txt = download_data, -# png = download_plot, -# jpeg = download_plot, -# tiff = download_plot, -# bmp = download_plot)), -# expr = { -# expect_snapshot_file(output$csv) -# expect_snapshot_file(output$tsv) -# expect_snapshot_file(output$txt) -# expect_true(file.exists(output$xlsx)) -# expect_true(file.exists(output$png)) -# expect_true(file.exists(output$jpeg)) -# expect_true(file.exists(output$tiff)) -# expect_true(file.exists(output$bmp)) -# }) -# -# }) -# -# test_that("downloadFile - lattice plot", { -# testServer(downloadFile, -# args = list(logger = periscope2:::fw_get_user_log(), -# filenameroot = "mydownload1", -# datafxns = list(png = download_lattice_plot, -# jpeg = download_lattice_plot, -# tiff = download_plot, -# bmp = download_lattice_plot)), -# expr = { -# expect_true(file.exists(output$png)) -# expect_true(file.exists(output$jpeg)) -# expect_true(file.exists(output$tiff)) -# expect_true(file.exists(output$bmp)) -# }) -# -# }) -# -# -# test_that("downloadFile - show rownames", { -# testServer(downloadFile, -# args = list(logger = periscope2:::fw_get_user_log(), -# filenameroot = "show_row_names_download", -# datafxns = list(csv = download_data_show_row_names)), -# expr = { -# expect_snapshot_file(output$csv) -# }) -# }) -# -# test_that("downloadFile - download char data", { -# testServer(downloadFile, -# args = list(logger = periscope2:::fw_get_user_log(), -# filenameroot = "my_char_download", -# datafxns = list(txt = download_char_data, -# tsv = download_char_data, -# csv = download_char_data)), -# expr = { -# expect_snapshot_file(output$txt) -# expect_snapshot_file(output$tsv) -# expect_snapshot_file(output$csv) -# }) -# }) -# -# test_that("downloadFile - download txt numeric data", { -# testServer(downloadFile, -# args = list(logger = periscope2:::fw_get_user_log(), -# filenameroot = "my_numeric_data", -# datafxns = list(txt = function() {123})), -# expr = { -# expect_warning(output$txt, "txt could not be processed") -# }) -# }) -# -# test_that("downloadFile - default values", { -# testServer(downloadFile, -# args = list(datafxns = list(txt = function() {"123"})), -# expr = { -# expect_snapshot_file(output$txt) -# }) -# }) -# -# test_that("downloadFile - invalid type", { -# testServer(downloadFile, -# args = list(datafxns = list(ttt = function() {"123"}, -# jeg = download_lattice_plot, -# tff = download_plot)), -# expr = { -# expect_error(output$ttt) -# expect_error(output$jeg) -# expect_error(output$tff) -# }) -# }) -# -# # Testing for xlsx downloads -# test_that("Testing workbook openxlsx2", { -# skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") -# skip_if_not_installed("openxlsx2") -# testServer(downloadFile, -# args = list(logger = periscope2:::fw_get_user_log(), -# filenameroot = "excel_test_openxlsx2_wb", -# datafxns = list(xlsx = create_openxlsx2_wb)), -# expr = { -# expect_true(file.exists(output$xlsx)) -# }) -# }) -# -# test_that("Testing workbook openxlsx", { -# skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") -# skip_if_not_installed("openxlsx") -# local_mocked_bindings(check_openxlsx2_availability = function() FALSE) -# testServer(downloadFile, -# args = list(logger = periscope2:::fw_get_user_log(), -# filenameroot = "excel_test_openxlsx_wb", -# datafxns = list(xlsx = create_openxlsx_wb)), -# expr = { -# expect_true(file.exists(output$xlsx)) -# }) -# }) -# -# test_that("Dataframe xlsx download works with openxlsx2", { -# skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") -# skip_if_not_installed("openxlsx2") -# local_mocked_bindings(check_openxlsx_availability = function() FALSE) -# testServer(downloadFile, -# args = list(logger = periscope2:::fw_get_user_log(), -# filenameroot = "excel_test_dataframe", -# datafxns = list(xlsx = download_data)), -# expr = { -# expect_true(file.exists(output$xlsx)) -# }) -# }) -# -# test_that("Dataframe xlsx download works with openxlsx", { -# skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") -# skip_if_not_installed("openxlsx") -# local_mocked_bindings(check_openxlsx2_availability = function() FALSE) -# testServer(downloadFile, -# args = list(logger = periscope2:::fw_get_user_log(), -# filenameroot = "excel_test_dataframe", -# datafxns = list(xlsx = download_data)), -# expr = { -# expect_true(file.exists(output$xlsx)) -# }) -# }) -# -# test_that("Dataframe xlsx download works with writexl", { -# skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") -# local_mocked_bindings(check_openxlsx2_availability = function() FALSE) -# local_mocked_bindings(check_openxlsx_availability = function() FALSE) -# testServer(downloadFile, -# args = list(logger = periscope2:::fw_get_user_log(), -# filenameroot = "excel_test_dataframe", -# datafxns = list(xlsx = download_data)), -# expr = { -# expect_true(file.exists(output$xlsx)) -# }) -# }) +context("periscope2 - download file") +local_edition(3) + +# helper functions +download_plot <- function() { + ggplot2::ggplot(data = mtcars, aes(x = wt, y = mpg)) + + geom_point(aes(color = cyl)) + + theme(legend.justification = c(1, 1), + legend.position.inside = c(1, 1), + legend.title = element_blank()) + + ggtitle("GGPlot Example w/Hover") + + xlab("wt") + + ylab("mpg") +} + +download_lattice_plot <- function() { + lattice::xyplot(Sepal.Length ~ Petal.Length, data = head(iris)) +} + +download_data <- function() { + head(mtcars) +} + +download_data_show_row_names <- function() { + attr(mtcars, "show_rownames") <- TRUE + head(mtcars) +} + +download_string_list <- function() { + c("test1", "test2", "tests") +} + +download_char_data <- function() { + "A123B" +} + +create_openxlsx2_wb <- function() { + openxlsx2::wb_workbook()$add_worksheet("openxlsx2_workbook")$add_data(x = download_data()) +} + +create_openxlsx_wb <- function() { + wb <- openxlsx::createWorkbook() + openxlsx::addWorksheet(wb, "openxlsx_workbook") + data <- as.data.frame(download_data()) + openxlsx::writeData(wb, "openxlsx_workbook", data) + wb +} + +# UI Testing +test_that("downloadFileButton", { + file_btn <- downloadFileButton(id = "myid", + downloadtypes = c("csv"), + hovertext = "myhovertext") + expect_true(grepl('title="myhovertext"', file_btn, fixed = TRUE)) + expect_true(grepl('id="myid-csv"', file_btn, fixed = TRUE)) +}) + +test_that("downloadFileButton - no download type", { + file_btn <- downloadFileButton(id = "myid2", + downloadtypes = NULL, + hovertext = "myhovertext") + expect_equal(file_btn, "") +}) + +test_that("downloadFileButton multiple types", { + file_btn <- downloadFileButton(id = "myid", + downloadtypes = c("csv", "tsv"), + hovertext = "myhovertext") + expect_true(grepl('class="btn-group"', file_btn, fixed = TRUE)) + expect_true(grepl('myid-downloadFileList"', file_btn, fixed = TRUE)) + expect_true(grepl('id="myid-csv"', file_btn, fixed = TRUE)) + expect_true(grepl('id="myid-tsv"', file_btn, fixed = TRUE)) +}) + +test_that("downloadFileButton invalid type", { + file_btn <- downloadFileButton(id = "myid", + downloadtypes = c("sv"), + hovertext = "myhovertext") + expect_true(grepl('title="myhovertext"', file_btn, fixed = TRUE)) + expect_true(grepl('id="myid-sv"', file_btn, fixed = TRUE)) +}) + +# Server Testing +test_that("downloadFile_ValidateTypes valid", { + result <- downloadFile_ValidateTypes(types = "csv") + + expect_equal(result, "csv") +}) + +test_that("downloadFile_ValidateTypes invalid", { + expect_warning(downloadFile_ValidateTypes(types = "csv_invalid"), + "file download list contains an invalid type ") +}) + +test_that("downloadFile_AvailableTypes", { + result <- downloadFile_AvailableTypes() + + expect_equal(result, c("csv", "xlsx", "tsv", "txt", "png", "jpeg", "tiff", "bmp")) +}) + +test_that("downloadFile - all download types", { + testServer(downloadFile, + args = list(logger = periscope2:::fw_get_user_log(), + filenameroot = "mydownload1", + datafxns = list(csv = download_data, + xlsx = download_data, + tsv = download_data, + txt = download_data, + png = download_plot, + jpeg = download_plot, + tiff = download_plot, + bmp = download_plot)), + expr = { + expect_snapshot_file(output$csv) + expect_snapshot_file(output$tsv) + expect_snapshot_file(output$txt) + expect_true(file.exists(output$xlsx)) + expect_true(file.exists(output$png)) + expect_true(file.exists(output$jpeg)) + expect_true(file.exists(output$tiff)) + expect_true(file.exists(output$bmp)) + }) + +}) + +test_that("downloadFile - lattice plot", { + testServer(downloadFile, + args = list(logger = periscope2:::fw_get_user_log(), + filenameroot = "mydownload1", + datafxns = list(png = download_lattice_plot, + jpeg = download_lattice_plot, + tiff = download_plot, + bmp = download_lattice_plot)), + expr = { + expect_true(file.exists(output$png)) + expect_true(file.exists(output$jpeg)) + expect_true(file.exists(output$tiff)) + expect_true(file.exists(output$bmp)) + }) + +}) + + +test_that("downloadFile - show rownames", { + testServer(downloadFile, + args = list(logger = periscope2:::fw_get_user_log(), + filenameroot = "show_row_names_download", + datafxns = list(csv = download_data_show_row_names)), + expr = { + expect_snapshot_file(output$csv) + }) +}) + +test_that("downloadFile - download char data", { + testServer(downloadFile, + args = list(logger = periscope2:::fw_get_user_log(), + filenameroot = "my_char_download", + datafxns = list(txt = download_char_data, + tsv = download_char_data, + csv = download_char_data)), + expr = { + expect_snapshot_file(output$txt) + expect_snapshot_file(output$tsv) + expect_snapshot_file(output$csv) + }) +}) + +test_that("downloadFile - download txt numeric data", { + testServer(downloadFile, + args = list(logger = periscope2:::fw_get_user_log(), + filenameroot = "my_numeric_data", + datafxns = list(txt = function() {123})), + expr = { + expect_warning(output$txt, "txt could not be processed") + }) +}) + +test_that("downloadFile - default values", { + testServer(downloadFile, + args = list(datafxns = list(txt = function() {"123"})), + expr = { + expect_snapshot_file(output$txt) + }) +}) + +test_that("downloadFile - invalid type", { + testServer(downloadFile, + args = list(datafxns = list(ttt = function() {"123"}, + jeg = download_lattice_plot, + tff = download_plot)), + expr = { + expect_error(output$ttt) + expect_error(output$jeg) + expect_error(output$tff) + }) +}) + +# Testing for xlsx downloads +test_that("Testing workbook openxlsx2", { + skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") + skip_if_not_installed("openxlsx2") + testServer(downloadFile, + args = list(logger = periscope2:::fw_get_user_log(), + filenameroot = "excel_test_openxlsx2_wb", + datafxns = list(xlsx = create_openxlsx2_wb)), + expr = { + expect_true(file.exists(output$xlsx)) + }) +}) + +test_that("Testing workbook openxlsx", { + skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") + skip_if_not_installed("openxlsx") + local_mocked_bindings(check_openxlsx2_availability = function() FALSE) + testServer(downloadFile, + args = list(logger = periscope2:::fw_get_user_log(), + filenameroot = "excel_test_openxlsx_wb", + datafxns = list(xlsx = create_openxlsx_wb)), + expr = { + expect_true(file.exists(output$xlsx)) + }) +}) + +test_that("Dataframe xlsx download works with openxlsx2", { + skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") + skip_if_not_installed("openxlsx2") + local_mocked_bindings(check_openxlsx_availability = function() FALSE) + testServer(downloadFile, + args = list(logger = periscope2:::fw_get_user_log(), + filenameroot = "excel_test_dataframe", + datafxns = list(xlsx = download_data)), + expr = { + expect_true(file.exists(output$xlsx)) + }) +}) + +test_that("Dataframe xlsx download works with openxlsx", { + skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") + skip_if_not_installed("openxlsx") + local_mocked_bindings(check_openxlsx2_availability = function() FALSE) + testServer(downloadFile, + args = list(logger = periscope2:::fw_get_user_log(), + filenameroot = "excel_test_dataframe", + datafxns = list(xlsx = download_data)), + expr = { + expect_true(file.exists(output$xlsx)) + }) +}) + +test_that("Dataframe xlsx download works with writexl", { + skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") + local_mocked_bindings(check_openxlsx2_availability = function() FALSE) + local_mocked_bindings(check_openxlsx_availability = function() FALSE) + testServer(downloadFile, + args = list(logger = periscope2:::fw_get_user_log(), + filenameroot = "excel_test_dataframe", + datafxns = list(xlsx = download_data)), + expr = { + expect_true(file.exists(output$xlsx)) + }) +}) From f0ca88474fb21628399785aa42b008af20ac0a81 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Mon, 4 Aug 2025 22:49:33 -0700 Subject: [PATCH 042/139] - Skipped download file unit tests related to ggplot updates --- tests/testthat/test_download_file.R | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/testthat/test_download_file.R b/tests/testthat/test_download_file.R index f2cd2077..fed5d26b 100644 --- a/tests/testthat/test_download_file.R +++ b/tests/testthat/test_download_file.R @@ -99,6 +99,7 @@ test_that("downloadFile_AvailableTypes", { }) test_that("downloadFile - all download types", { + skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") testServer(downloadFile, args = list(logger = periscope2:::fw_get_user_log(), filenameroot = "mydownload1", @@ -124,6 +125,7 @@ test_that("downloadFile - all download types", { }) test_that("downloadFile - lattice plot", { + skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") testServer(downloadFile, args = list(logger = periscope2:::fw_get_user_log(), filenameroot = "mydownload1", @@ -184,6 +186,7 @@ test_that("downloadFile - default values", { }) test_that("downloadFile - invalid type", { + skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") testServer(downloadFile, args = list(datafxns = list(ttt = function() {"123"}, jeg = download_lattice_plot, From 6ee4f1626cb1b74614dc07d8e8c0518621511300 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Mon, 4 Aug 2025 23:00:10 -0700 Subject: [PATCH 043/139] - Renabled logger unit tests --- tests/testthat/test_logger.R | 580 +++++++++++++++++------------------ 1 file changed, 290 insertions(+), 290 deletions(-) diff --git a/tests/testthat/test_logger.R b/tests/testthat/test_logger.R index 461e7548..68617116 100644 --- a/tests/testthat/test_logger.R +++ b/tests/testthat/test_logger.R @@ -1,290 +1,290 @@ -# context("periscope2 - logging functionality") -# -# -# writeToConsole <- periscope2:::writeToConsole -# writeToFile <- periscope2:::writeToFile -# loglevels <- periscope2:::loglevels -# test_file_name <- file.path(tempdir(), c("1", "2", "3")) -# -# env_setup <- function() { -# test_env <- new.env(parent = emptyenv()) -# test_env$logged <- NULL -# -# mock_action <- function(msg, handler, ...) { -# if (length(list(...)) && "dry" %in% names(list(...))) -# return(TRUE) -# test_env$logged <- c(test_env$logged, msg) -# } -# -# mock_formatter <- function(record) { -# paste(record$levelname, record$logger, record$msg, sep = ":") -# } -# -# periscope2:::logReset() -# periscope2:::addHandler(mock_action, -# formatter = mock_formatter) -# test_env -# } -# -# test_that("Handlers - addHandler(), getHandler() and removeHandler()", { -# periscope2:::logReset() -# periscope2:::basicConfig() -# -# # looking for handler in RootLogger -# expect_identical(periscope2:::getHandler("basic.stdout"), -# periscope2:::getLogger()[["handlers"]][[1]]) -# # looking for handler in object -# expect_identical(periscope2:::getLogger()$getHandler("basic.stdout"), -# periscope2:::getLogger()[["handlers"]][[1]]) -# -# # add handler -# periscope2:::addHandler(writeToConsole) -# -# expect_equal(length(with(periscope2:::getLogger(), names(handlers))), 2) -# expect_true("writeToConsole" %in% with(periscope2:::getLogger(), names(handlers))) -# -# # get handler -# expect_true(!is.null(periscope2:::getHandler("writeToConsole"))) -# expect_true(!is.null(periscope2:::getHandler(writeToConsole))) -# -# # remove handler -# periscope2:::removeHandler("writeToConsole") -# expect_equal(length(with(periscope2:::getLogger(), names(handlers))), 1) -# expect_false("writeToConsole" %in% with(periscope2:::getLogger(), names(handlers))) -# -# expect_error(periscope2:::addHandler("handlerName"), -# regexp = "No action for the handler provided", -# fixed = TRUE) -# }) -# -# test_that("Handlers in Logger object - addHandler(), getHandler() and removeHandler()", { -# periscope2:::logReset() -# periscope2:::basicConfig() -# -# # add handler -# log <- periscope2:::getLogger("testLogger") -# log$addHandler(writeToConsole) -# expect_equal(length(with(log, names(handlers))), 1) -# -# # get handler -# expect_true(!is.null(log$getHandler("writeToConsole"))) -# expect_true(!is.null(log$getHandler(writeToConsole))) -# -# # remove handler -# log$removeHandler(writeToConsole) -# expect_equal(length(with(log, names(handlers))), 0) -# expect_false("writeToConsole" %in% with(log, names(handlers))) -# }) -# -# test_that("Levels - setLevel()", { -# periscope2:::logReset() -# periscope2:::basicConfig() -# -# expect_error(periscope2:::setLevel("INFO", NULL), -# regexp = "NULL container provided: cannot set level for NULL container", -# fixed = TRUE) -# -# periscope2:::logReset() -# expect_equal(periscope2:::setLevel(TRUE), loglevels["NOTSET"]) # invalid level -# -# periscope2:::logReset() -# expect_equal(periscope2:::setLevel("INVALID"), loglevels["NOTSET"]) # invalid level -# }) -# -# test_that("Levels in Logger object- setLevel() and getLevel()", { -# periscope2:::logReset() -# log <- periscope2:::getLogger("testLogger") -# -# # invalid level -# log$setLevel(150) -# expect_equal(log$getLevel(), loglevels["NOTSET"]) -# -# log$setLevel(TRUE) -# expect_equal(log$getLevel(), loglevels["NOTSET"]) -# -# log$setLevel("INVALID") -# expect_equal(log$getLevel(), loglevels["NOTSET"]) -# }) -# -# test_that("UpdateOptions - updateOptions()", { -# periscope2:::logReset() -# periscope2:::basicConfig() -# -# periscope2:::updateOptions.character("", level = "WARN") -# expect_equal(periscope2:::getLogger()$getLevel(), loglevels["WARN"]) -# }) -# -# test_that("LoggingToConsole", { -# periscope2:::logReset() -# periscope2:::basicConfig() -# -# periscope2:::getLogger()$setLevel("FINEST") -# periscope2:::addHandler(writeToConsole, level = "DEBUG") -# -# expect_equal(with(periscope2:::getLogger(), names(handlers)), c("basic.stdout", "writeToConsole")) -# logdebug("log generated for testing") -# loginfo("log generated for testing") -# -# succeed() -# }) -# -# test_that("LoggingToFile", { -# periscope2:::logReset() -# unlink(test_file_name, force = TRUE) -# -# periscope2:::getLogger()$setLevel("FINEST") -# periscope2:::addHandler(writeToFile, file = test_file_name[[1]], level = "DEBUG") -# -# expect_equal(with(periscope2:::getLogger(), names(handlers)), c("writeToFile")) -# logerror("log generated for testing") -# logwarn("log generated for testing") -# loginfo("log generated for testing") -# logdebug("log generated for testing") -# periscope2:::logfinest("log generated for testing") -# periscope2:::logfiner("log generated for testing") -# periscope2:::logfine("log generated for testing") -# periscope2:::levellog("log generated for testing") -# -# succeed() -# }) -# -# test_that("LoggingToFile in Logger object", { -# periscope2:::logReset() -# unlink(test_file_name, force = TRUE) -# -# log <- periscope2:::getLogger() -# log$setLevel("FINEST") -# log$addHandler(writeToFile, file = test_file_name[[1]], level = "DEBUG") -# expect_equal(with(log, names(handlers)), c("writeToFile")) -# -# log$error("log generated for testing") -# log$warn("log generated for testing") -# log$info("log generated for testing") -# log$debug("log generated for testing") -# log$finest("log generated for testing") -# log$finer("log generated for testing") -# log$fine("log generated for testing") -# -# succeed() -# }) -# -# test_that("Msgcomposer - setMsgComposer(), resetMsgComposer()", { -# env <- env_setup() -# -# periscope2:::setMsgComposer(function(msg, ...) { paste(msg, "comp") }) -# loginfo("test") -# -# expect_equal(env$logged, "INFO::test comp") -# expect_error(periscope2:::setMsgComposer(function(msgX, ...) { paste(msg, "comp") }), -# regexp = "message composer(passed as composer_f) must be function with signature function(msg, ...)", -# fixed = TRUE) -# expect_error(periscope2:::setMsgComposer(container = NULL), -# regexp = "NULL container provided: cannot set message composer for NULL container") -# -# # reset_composer -# env <- env_setup() -# periscope2:::resetMsgComposer() -# loginfo("test") -# -# expect_equal(env$logged, "INFO::test") -# expect_error(periscope2:::resetMsgComposer(NULL), -# regexp = "NULL container provided: cannot reset message composer for NULL container") -# -# # set_sublogger_composer -# env <- env_setup() -# -# periscope2:::setMsgComposer(function(msg, ...) { paste(msg, "comp") }, container = "named") -# loginfo("test") -# loginfo("test", logger = "named") -# -# expect_equal(env$logged, c("INFO::test", "INFO:named:test comp")) -# }) -# -# test_that("MsgComposer function - defaultMsgCompose()",{ -# expect_equal(periscope2:::defaultMsgCompose(msg = "Message"), "Message") -# expect_error(periscope2:::defaultMsgCompose(msg = paste(rep(LETTERS, 316), collapse = ""), param = 1), -# "'msg' length exceeds maximal format length 8192") -# expect_equal(periscope2:::defaultMsgCompose(msg = paste(rep(LETTERS, 316), collapse = "")), -# paste(rep(LETTERS, 316), collapse = "")) -# }) -# -# # Testing log_levels -# test_that("writeToConsole DEBUG level", { -# on.exit(reset_g_opts()) -# periscope2::set_app_parameters(log_level = "DEBUG") -# expect_output(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) -# expect_output(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) -# expect_output(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) -# expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) -# }) -# -# test_that("writeToConsole INFO level", { -# on.exit(reset_g_opts()) -# periscope2::set_app_parameters(log_level = "INFO") -# expect_silent(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) -# expect_output(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) -# expect_output(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) -# expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) -# }) -# -# test_that("writeToConsole WARN level", { -# on.exit(reset_g_opts()) -# periscope2::set_app_parameters(log_level = "WARN") -# expect_silent(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) -# expect_silent(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) -# expect_output(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) -# expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) -# }) -# -# test_that("writeToConsole ERROR level", { -# on.exit(reset_g_opts()) -# periscope2::set_app_parameters(log_level = "ERROR") -# expect_silent(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) -# expect_silent(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) -# expect_silent(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) -# expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) -# }) -# -# test_that("writeToFile DEBUG level", { -# on.exit(reset_g_opts()) -# unlink(test_file_name, force = TRUE) -# periscope2::set_app_parameters(log_level = "DEBUG") -# writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) -# writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) -# writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) -# writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) -# expect_equal(readLines(test_file_name[[1]]), c("debug", "info", "warn", "error")) -# }) -# -# test_that("writeToFile INFO level", { -# on.exit(reset_g_opts()) -# unlink(test_file_name, force = TRUE) -# periscope2::set_app_parameters(log_level = "INFO") -# writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) -# writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) -# writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) -# writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) -# expect_equal(readLines(test_file_name[[1]]), c("info", "warn", "error")) -# }) -# -# test_that("writeToFile WARN level", { -# on.exit(reset_g_opts()) -# unlink(test_file_name, force = TRUE) -# periscope2::set_app_parameters(log_level = "WARN") -# writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) -# writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) -# writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) -# writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) -# expect_equal(readLines(test_file_name[[1]]), c("warn", "error")) -# }) -# -# test_that("writeToFile ERROR level", { -# on.exit(reset_g_opts()) -# unlink(test_file_name, force = TRUE) -# periscope2::set_app_parameters(log_level = "ERROR") -# writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) -# writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) -# writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) -# writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) -# expect_equal(readLines(test_file_name[[1]]), "error") -# }) +context("periscope2 - logging functionality") + + +writeToConsole <- periscope2:::writeToConsole +writeToFile <- periscope2:::writeToFile +loglevels <- periscope2:::loglevels +test_file_name <- file.path(tempdir(), c("1", "2", "3")) + +env_setup <- function() { + test_env <- new.env(parent = emptyenv()) + test_env$logged <- NULL + + mock_action <- function(msg, handler, ...) { + if (length(list(...)) && "dry" %in% names(list(...))) + return(TRUE) + test_env$logged <- c(test_env$logged, msg) + } + + mock_formatter <- function(record) { + paste(record$levelname, record$logger, record$msg, sep = ":") + } + + periscope2:::logReset() + periscope2:::addHandler(mock_action, + formatter = mock_formatter) + test_env +} + +test_that("Handlers - addHandler(), getHandler() and removeHandler()", { + periscope2:::logReset() + periscope2:::basicConfig() + + # looking for handler in RootLogger + expect_identical(periscope2:::getHandler("basic.stdout"), + periscope2:::getLogger()[["handlers"]][[1]]) + # looking for handler in object + expect_identical(periscope2:::getLogger()$getHandler("basic.stdout"), + periscope2:::getLogger()[["handlers"]][[1]]) + + # add handler + periscope2:::addHandler(writeToConsole) + + expect_equal(length(with(periscope2:::getLogger(), names(handlers))), 2) + expect_true("writeToConsole" %in% with(periscope2:::getLogger(), names(handlers))) + + # get handler + expect_true(!is.null(periscope2:::getHandler("writeToConsole"))) + expect_true(!is.null(periscope2:::getHandler(writeToConsole))) + + # remove handler + periscope2:::removeHandler("writeToConsole") + expect_equal(length(with(periscope2:::getLogger(), names(handlers))), 1) + expect_false("writeToConsole" %in% with(periscope2:::getLogger(), names(handlers))) + + expect_error(periscope2:::addHandler("handlerName"), + regexp = "No action for the handler provided", + fixed = TRUE) +}) + +test_that("Handlers in Logger object - addHandler(), getHandler() and removeHandler()", { + periscope2:::logReset() + periscope2:::basicConfig() + + # add handler + log <- periscope2:::getLogger("testLogger") + log$addHandler(writeToConsole) + expect_equal(length(with(log, names(handlers))), 1) + + # get handler + expect_true(!is.null(log$getHandler("writeToConsole"))) + expect_true(!is.null(log$getHandler(writeToConsole))) + + # remove handler + log$removeHandler(writeToConsole) + expect_equal(length(with(log, names(handlers))), 0) + expect_false("writeToConsole" %in% with(log, names(handlers))) +}) + +test_that("Levels - setLevel()", { + periscope2:::logReset() + periscope2:::basicConfig() + + expect_error(periscope2:::setLevel("INFO", NULL), + regexp = "NULL container provided: cannot set level for NULL container", + fixed = TRUE) + + periscope2:::logReset() + expect_equal(periscope2:::setLevel(TRUE), loglevels["NOTSET"]) # invalid level + + periscope2:::logReset() + expect_equal(periscope2:::setLevel("INVALID"), loglevels["NOTSET"]) # invalid level +}) + +test_that("Levels in Logger object- setLevel() and getLevel()", { + periscope2:::logReset() + log <- periscope2:::getLogger("testLogger") + + # invalid level + log$setLevel(150) + expect_equal(log$getLevel(), loglevels["NOTSET"]) + + log$setLevel(TRUE) + expect_equal(log$getLevel(), loglevels["NOTSET"]) + + log$setLevel("INVALID") + expect_equal(log$getLevel(), loglevels["NOTSET"]) +}) + +test_that("UpdateOptions - updateOptions()", { + periscope2:::logReset() + periscope2:::basicConfig() + + periscope2:::updateOptions.character("", level = "WARN") + expect_equal(periscope2:::getLogger()$getLevel(), loglevels["WARN"]) +}) + +test_that("LoggingToConsole", { + periscope2:::logReset() + periscope2:::basicConfig() + + periscope2:::getLogger()$setLevel("FINEST") + periscope2:::addHandler(writeToConsole, level = "DEBUG") + + expect_equal(with(periscope2:::getLogger(), names(handlers)), c("basic.stdout", "writeToConsole")) + logdebug("log generated for testing") + loginfo("log generated for testing") + + succeed() +}) + +test_that("LoggingToFile", { + periscope2:::logReset() + unlink(test_file_name, force = TRUE) + + periscope2:::getLogger()$setLevel("FINEST") + periscope2:::addHandler(writeToFile, file = test_file_name[[1]], level = "DEBUG") + + expect_equal(with(periscope2:::getLogger(), names(handlers)), c("writeToFile")) + logerror("log generated for testing") + logwarn("log generated for testing") + loginfo("log generated for testing") + logdebug("log generated for testing") + periscope2:::logfinest("log generated for testing") + periscope2:::logfiner("log generated for testing") + periscope2:::logfine("log generated for testing") + periscope2:::levellog("log generated for testing") + + succeed() +}) + +test_that("LoggingToFile in Logger object", { + periscope2:::logReset() + unlink(test_file_name, force = TRUE) + + log <- periscope2:::getLogger() + log$setLevel("FINEST") + log$addHandler(writeToFile, file = test_file_name[[1]], level = "DEBUG") + expect_equal(with(log, names(handlers)), c("writeToFile")) + + log$error("log generated for testing") + log$warn("log generated for testing") + log$info("log generated for testing") + log$debug("log generated for testing") + log$finest("log generated for testing") + log$finer("log generated for testing") + log$fine("log generated for testing") + + succeed() +}) + +test_that("Msgcomposer - setMsgComposer(), resetMsgComposer()", { + env <- env_setup() + + periscope2:::setMsgComposer(function(msg, ...) { paste(msg, "comp") }) + loginfo("test") + + expect_equal(env$logged, "INFO::test comp") + expect_error(periscope2:::setMsgComposer(function(msgX, ...) { paste(msg, "comp") }), + regexp = "message composer(passed as composer_f) must be function with signature function(msg, ...)", + fixed = TRUE) + expect_error(periscope2:::setMsgComposer(container = NULL), + regexp = "NULL container provided: cannot set message composer for NULL container") + + # reset_composer + env <- env_setup() + periscope2:::resetMsgComposer() + loginfo("test") + + expect_equal(env$logged, "INFO::test") + expect_error(periscope2:::resetMsgComposer(NULL), + regexp = "NULL container provided: cannot reset message composer for NULL container") + + # set_sublogger_composer + env <- env_setup() + + periscope2:::setMsgComposer(function(msg, ...) { paste(msg, "comp") }, container = "named") + loginfo("test") + loginfo("test", logger = "named") + + expect_equal(env$logged, c("INFO::test", "INFO:named:test comp")) +}) + +test_that("MsgComposer function - defaultMsgCompose()",{ + expect_equal(periscope2:::defaultMsgCompose(msg = "Message"), "Message") + expect_error(periscope2:::defaultMsgCompose(msg = paste(rep(LETTERS, 316), collapse = ""), param = 1), + "'msg' length exceeds maximal format length 8192") + expect_equal(periscope2:::defaultMsgCompose(msg = paste(rep(LETTERS, 316), collapse = "")), + paste(rep(LETTERS, 316), collapse = "")) +}) + +# Testing log_levels +test_that("writeToConsole DEBUG level", { + on.exit(reset_g_opts()) + periscope2::set_app_parameters(log_level = "DEBUG") + expect_output(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) + expect_output(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) + expect_output(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) + expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) +}) + +test_that("writeToConsole INFO level", { + on.exit(reset_g_opts()) + periscope2::set_app_parameters(log_level = "INFO") + expect_silent(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) + expect_output(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) + expect_output(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) + expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) +}) + +test_that("writeToConsole WARN level", { + on.exit(reset_g_opts()) + periscope2::set_app_parameters(log_level = "WARN") + expect_silent(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) + expect_silent(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) + expect_output(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) + expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) +}) + +test_that("writeToConsole ERROR level", { + on.exit(reset_g_opts()) + periscope2::set_app_parameters(log_level = "ERROR") + expect_silent(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) + expect_silent(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) + expect_silent(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) + expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) +}) + +test_that("writeToFile DEBUG level", { + on.exit(reset_g_opts()) + unlink(test_file_name, force = TRUE) + periscope2::set_app_parameters(log_level = "DEBUG") + writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) + writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) + writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) + writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) + expect_equal(readLines(test_file_name[[1]]), c("debug", "info", "warn", "error")) +}) + +test_that("writeToFile INFO level", { + on.exit(reset_g_opts()) + unlink(test_file_name, force = TRUE) + periscope2::set_app_parameters(log_level = "INFO") + writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) + writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) + writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) + writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) + expect_equal(readLines(test_file_name[[1]]), c("info", "warn", "error")) +}) + +test_that("writeToFile WARN level", { + on.exit(reset_g_opts()) + unlink(test_file_name, force = TRUE) + periscope2::set_app_parameters(log_level = "WARN") + writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) + writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) + writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) + writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) + expect_equal(readLines(test_file_name[[1]]), c("warn", "error")) +}) + +test_that("writeToFile ERROR level", { + on.exit(reset_g_opts()) + unlink(test_file_name, force = TRUE) + periscope2::set_app_parameters(log_level = "ERROR") + writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) + writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) + writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) + writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) + expect_equal(readLines(test_file_name[[1]]), "error") +}) From c71db857733454ff0725afc86b359b2276541ac5 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Mon, 4 Aug 2025 23:19:42 -0700 Subject: [PATCH 044/139] - Updated spelling lists and readme --- README.md | 2 +- inst/WORDLIST | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c2c4734f..197f2267 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ User can adapt layout for both packages generated apps easily via related functi - Both packages share the following modules: - downloadable file - - Use openxlsx2 to download .xlsx files, openxlsx for legacy apps and backwards compatibility, and writexl as a fallback incase openxlsx2 or openxlsx not installed. + - Use openxlsx2 to download .xlsx files, openxlsx for legacy apps and backwards compatibility, and writexl as a fallback in case openxlsx2 or openxlsx not installed. - downloadable plot - downloadable table - However, periscope2 has more modules (more to come with each new version) as Announcements module diff --git a/inst/WORDLIST b/inst/WORDLIST index f48a16e3..7b99f585 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -72,6 +72,11 @@ javascript js logViewer logViewerOutput +logdebug +logerror +loginfo +loglevel +logwarn mailto mis modularize @@ -79,6 +84,8 @@ msg mytestapp navbar navbarMenu +openxlsx +pageSize param params periscopeapps @@ -106,6 +113,7 @@ unitless userAction valueBox waiterShowOnLoad +writexl www xlsx yaml From 9f7492810c7c46ba4c4d8abfc2d3f3d3b70e3ae1 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Mon, 4 Aug 2025 23:39:35 -0700 Subject: [PATCH 045/139] - Added download file more unit tests --- tests/testthat/test_download_file.R | 70 +++++++++++++++++++---------- 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/tests/testthat/test_download_file.R b/tests/testthat/test_download_file.R index fed5d26b..8cbf62a7 100644 --- a/tests/testthat/test_download_file.R +++ b/tests/testthat/test_download_file.R @@ -112,14 +112,22 @@ test_that("downloadFile - all download types", { tiff = download_plot, bmp = download_plot)), expr = { - expect_snapshot_file(output$csv) - expect_snapshot_file(output$tsv) - expect_snapshot_file(output$txt) - expect_true(file.exists(output$xlsx)) - expect_true(file.exists(output$png)) - expect_true(file.exists(output$jpeg)) - expect_true(file.exists(output$tiff)) - expect_true(file.exists(output$bmp)) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < mydownload1.csv >", + x = capture_output(expect_snapshot_file(output$csv)))) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < mydownload1.tsv >", + x = capture_output(expect_snapshot_file(output$tsv)))) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < mydownload1.txt >", + x = capture_output(expect_snapshot_file(output$txt)))) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < mydownload1.xlsx >", + x = capture_output(file.exists(output$xlsx)))) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < mydownload1.png >", + x = capture_output(file.exists(output$png)))) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < mydownload1.jpeg >", + x = capture_output(file.exists(output$jpeg)))) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < mydownload1.tiff >", + x = capture_output(file.exists(output$tiff)))) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < mydownload1.bmp >", + x = capture_output(file.exists(output$bmp)))) }) }) @@ -134,10 +142,14 @@ test_that("downloadFile - lattice plot", { tiff = download_plot, bmp = download_lattice_plot)), expr = { - expect_true(file.exists(output$png)) - expect_true(file.exists(output$jpeg)) - expect_true(file.exists(output$tiff)) - expect_true(file.exists(output$bmp)) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < mydownload1.png >", + x = capture_output(expect_true(file.exists(output$png))))) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < mydownload1.jpeg >", + x = capture_output(expect_true(file.exists(output$jpeg))))) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < mydownload1.tiff >", + x = capture_output(expect_true(file.exists(output$tiff))))) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < mydownload1.bmp >", + x = capture_output(expect_true(file.exists(output$bmp))))) }) }) @@ -149,7 +161,8 @@ test_that("downloadFile - show rownames", { filenameroot = "show_row_names_download", datafxns = list(csv = download_data_show_row_names)), expr = { - expect_snapshot_file(output$csv) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < show_row_names_download.csv >", + x = capture_output(expect_snapshot_file(output$csv)))) }) }) @@ -161,9 +174,12 @@ test_that("downloadFile - download char data", { tsv = download_char_data, csv = download_char_data)), expr = { - expect_snapshot_file(output$txt) - expect_snapshot_file(output$tsv) - expect_snapshot_file(output$csv) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < my_char_download.csv >", + x = capture_output(expect_snapshot_file(output$csv)))) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < my_char_download.tsv >", + x = capture_output(expect_snapshot_file(output$tsv)))) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < my_char_download.txt >", + x = capture_output(expect_snapshot_file(output$txt)))) }) }) @@ -173,7 +189,9 @@ test_that("downloadFile - download txt numeric data", { filenameroot = "my_numeric_data", datafxns = list(txt = function() {123})), expr = { - expect_warning(output$txt, "txt could not be processed") + expect_warning(expect_true(grepl( + pattern = "INFO:actions:File downloaded in browser: < my_numeric_data.txt >", + x = capture_output(output$txt))), "txt could not be processed") }) }) @@ -181,7 +199,8 @@ test_that("downloadFile - default values", { testServer(downloadFile, args = list(datafxns = list(txt = function() {"123"})), expr = { - expect_snapshot_file(output$txt) + expect_true(grepl(pattern = "INFO::File downloaded in browser: < download.txt >", + x = capture_output(expect_snapshot_file(output$txt)))) }) }) @@ -207,7 +226,8 @@ test_that("Testing workbook openxlsx2", { filenameroot = "excel_test_openxlsx2_wb", datafxns = list(xlsx = create_openxlsx2_wb)), expr = { - expect_true(file.exists(output$xlsx)) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < excel_test_openxlsx2_wb.xlsx >", + x = capture_output(file.exists(output$xlsx)))) }) }) @@ -220,7 +240,8 @@ test_that("Testing workbook openxlsx", { filenameroot = "excel_test_openxlsx_wb", datafxns = list(xlsx = create_openxlsx_wb)), expr = { - expect_true(file.exists(output$xlsx)) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < excel_test_openxlsx_wb.xlsx >", + x = capture_output(file.exists(output$xlsx)))) }) }) @@ -233,7 +254,8 @@ test_that("Dataframe xlsx download works with openxlsx2", { filenameroot = "excel_test_dataframe", datafxns = list(xlsx = download_data)), expr = { - expect_true(file.exists(output$xlsx)) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < excel_test_dataframe.xlsx >", + x = capture_output(file.exists(output$xlsx)))) }) }) @@ -246,7 +268,8 @@ test_that("Dataframe xlsx download works with openxlsx", { filenameroot = "excel_test_dataframe", datafxns = list(xlsx = download_data)), expr = { - expect_true(file.exists(output$xlsx)) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < excel_test_dataframe.xlsx >", + x = capture_output(file.exists(output$xlsx)))) }) }) @@ -259,6 +282,7 @@ test_that("Dataframe xlsx download works with writexl", { filenameroot = "excel_test_dataframe", datafxns = list(xlsx = download_data)), expr = { - expect_true(file.exists(output$xlsx)) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < excel_test_dataframe.xlsx >", + x = capture_output(file.exists(output$xlsx)))) }) }) From 828884e6dfbb394b3f9852750462ce853e4321af Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Tue, 5 Aug 2025 03:31:19 -0700 Subject: [PATCH 046/139] - Updated download table filename root unit tests --- tests/testthat/test_downloadable_table.R | 80 ++++++++++++++---------- 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/tests/testthat/test_downloadable_table.R b/tests/testthat/test_downloadable_table.R index 9ea3cafe..d4174086 100644 --- a/tests/testthat/test_downloadable_table.R +++ b/tests/testthat/test_downloadable_table.R @@ -125,41 +125,57 @@ test_that("downloadableTable - invalid_selection", { test_that("downloadableTable - filenameroot", { - testServer(downloadableTable, - args = list(filenameroot = "test", - downloaddatafxns = list(csv = data), - tabledata = data), - expr = { - selected <- '"selection":{"mode":"multiple","selected":null,"target":"row","selectable":null}' - expect_true(grepl(selected, output$dtableOutputID, fixed = TRUE)) - }) + expect_message( + testServer(downloadableTable, + args = list(filenameroot = "test", + downloaddatafxns = list(csv = data), + tabledata = data), + expr = { + selected <- '"selection":{"mode":"multiple","selected":null,"target":"row","selectable":null}' + expect_true(grepl(selected, output$dtableOutputID, fixed = TRUE)) + }), + regexp = "Could not apply DT options due to.*selection.*argument is incorrect", + class = "message" + ) - testServer(downloadableTable, - args = list(filenameroot = reactiveVal("test"), - downloaddatafxns = list(csv = data), - tabledata = data), - expr = { - selected <- '"selection":{"mode":"multiple","selected":null,"target":"row","selectable":null}' - expect_true(grepl(selected, output$dtableOutputID, fixed = TRUE)) - }) + expect_message( + testServer(downloadableTable, + args = list(filenameroot = reactiveVal("test"), + downloaddatafxns = list(csv = data), + tabledata = data), + expr = { + selected <- '"selection":{"mode":"multiple","selected":null,"target":"row","selectable":null}' + expect_true(grepl(selected, output$dtableOutputID, fixed = TRUE)) + }), + regexp = "Could not apply DT options due to.*selection.*argument is incorrect", + class = "message" + ) - testServer(downloadableTable, - args = list(filenameroot = function(){"test"}, - downloaddatafxns = list(csv = data), - tabledata = data), - expr = { - selected <- '"selection":{"mode":"multiple","selected":null,"target":"row","selectable":null}' - expect_true(grepl(selected, output$dtableOutputID, fixed = TRUE)) - }) + expect_message( + testServer(downloadableTable, + args = list(filenameroot = function(){"test"}, + downloaddatafxns = list(csv = data), + tabledata = data), + expr = { + selected <- '"selection":{"mode":"multiple","selected":null,"target":"row","selectable":null}' + expect_true(grepl(selected, output$dtableOutputID, fixed = TRUE)) + }), + regexp = "Could not apply DT options due to.*selection.*argument is incorrect", + class = "message" + ) - testServer(downloadableTable, - args = list(filenameroot = NULL, - downloaddatafxns = list(csv = data), - tabledata = data), - expr = { - selected <- '"selection":{"mode":"multiple","selected":null,"target":"row","selectable":null}' - expect_true(grepl(selected, output$dtableOutputID, fixed = TRUE)) - }) + expect_message( + testServer(downloadableTable, + args = list(filenameroot = NULL, + downloaddatafxns = list(csv = data), + tabledata = data), + expr = { + selected <- '"selection":{"mode":"multiple","selected":null,"target":"row","selectable":null}' + expect_true(grepl(selected, output$dtableOutputID, fixed = TRUE)) + }), + regexp = "Could not apply DT options due to.*selection.*argument is incorrect", + class = "message" + ) }) test_that("downloadableTable - no downloads", { From 15e63c477ee9ceaf3d0575118557ef1a41754f84 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Tue, 5 Aug 2025 04:05:03 -0700 Subject: [PATCH 047/139] - Removed commented unit tests in ui functions --- tests/testthat/test_ui_functions.R | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/tests/testthat/test_ui_functions.R b/tests/testthat/test_ui_functions.R index c06bbe67..0dfc6e33 100644 --- a/tests/testthat/test_ui_functions.R +++ b/tests/testthat/test_ui_functions.R @@ -504,20 +504,6 @@ test_that("theme - invalid color", { }) -# test_that("theme - invalid width", { -# theme_settings <- yaml::read_yaml(system.file("fw_templ", "p_example", "periscope_style.yaml", package = "periscope2")) -# dir.create("www") -# theme_settings[["sidebar_width"]] <- "300" -# theme_settings[["control_sidebar_width"]] <- "-300" -# -# yaml::write_yaml(theme_settings, "www/periscope_style.yaml") -# expect_warning(nchar(create_theme()), -# regexp = "invalid theme settings -300 must be positive value. Setting default value") -# unlink("www/periscope_style.yaml") -# unlink("www", recursive = TRUE) -# }) - - test_that("dashboard - create default dashboard", { expect_snapshot(periscope2:::create_application_dashboard()) }) From d0cc6192abfdfe9bea087845252cd2ef3fd6eacb Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Tue, 5 Aug 2025 04:06:00 -0700 Subject: [PATCH 048/139] - Added more unit tests to logger ot caught logger side effect messages --- tests/testthat/test_logger.R | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test_logger.R b/tests/testthat/test_logger.R index 68617116..d95b62df 100644 --- a/tests/testthat/test_logger.R +++ b/tests/testthat/test_logger.R @@ -122,8 +122,10 @@ test_that("LoggingToConsole", { periscope2:::addHandler(writeToConsole, level = "DEBUG") expect_equal(with(periscope2:::getLogger(), names(handlers)), c("basic.stdout", "writeToConsole")) - logdebug("log generated for testing") - loginfo("log generated for testing") + expect_true(grepl(pattern = "DEBUG::log generated for testing", + x = capture_output(logdebug("log generated for testing"), print = TRUE))) + expect_true(grepl(pattern = "INFO::log generated for testing", + x = capture_output(loginfo("log generated for testing"), print = TRUE))) succeed() }) From a559c6feb90a938b70914e571607d4ad4b732187 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Tue, 5 Aug 2025 04:19:11 -0700 Subject: [PATCH 049/139] - Reverted debugging instructions in testthat --- tests/testthat.R | 42 +----------------------------------------- 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/tests/testthat.R b/tests/testthat.R index 1b035f39..fad84e81 100644 --- a/tests/testthat.R +++ b/tests/testthat.R @@ -1,44 +1,4 @@ library(testthat) library(periscope2) - - -# Disable ANSI colors that are causing the issue -options(crayon.enabled = FALSE, cli.ansi = FALSE) -Sys.setenv(NO_COLOR = "1") - -cat("=== Running tests with detailed output ===\n") - -# Get all test files -test_files <- list.files("testthat", pattern = "^test.*\\.R$", full.names = TRUE) -cat("Found", length(test_files), "test files\n") - -failed_tests <- character(0) - -# Run each test file individually -for (i in seq_along(test_files)) { - test_file <- test_files[i] - cat("\n========================================\n") - cat("Running test file", i, "of", length(test_files), ":", basename(test_file), "\n") - cat("========================================\n") - - result <- try({ - testthat::test_file(test_file, reporter = "progress") - }, silent = FALSE) - - if (inherits(result, "try-error")) { - cat("❌ FAILED:", basename(test_file), "\n") - cat("Error:", as.character(result), "\n") - failed_tests <- c(failed_tests, basename(test_file)) - break # Stop at first failure - } else { - cat("✅ PASSED:", basename(test_file), "\n") - } -} - -if (length(failed_tests) > 0) { - cat("\n❌ Failed test files:", paste(failed_tests, collapse = ", "), "\n") - quit(status = 1) -} else { - cat("\n✅ All test files passed!\n") -} +test_check("periscope2") From 074044577258e47842562d201dd359882b8b596c Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Wed, 6 Aug 2025 07:06:31 -0700 Subject: [PATCH 050/139] - Added react table demo - Updated module documentation - Updated package version - Fixed tree plot demo --- DESCRIPTION | 2 +- R/downloadableReactTable.R | 13 +++--- inst/fw_templ/p_example/global.R | 1 + inst/fw_templ/p_example/program_helpers.R | 1 + inst/fw_templ/p_example/server_local.R | 37 ++++++++++++++++ inst/fw_templ/p_example/ui_body.R | 42 ++++++++++++++++++ .../p_example/ui_body_no_left_sidebar.R | 43 +++++++++++++++++++ man/downloadableReactTable.Rd | 13 +++--- 8 files changed, 139 insertions(+), 13 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 24589838..289a19fa 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: periscope2 Type: Package Title: Enterprise Streamlined 'shiny' Application Framework Using 'bs4Dash' -Version: 0.3.0.9008 +Version: 0.3.0.9009 Authors@R: c( person("Mohammed", "Ali", role = c("aut", "cre"), email = "mohammed@aggregate-genius.com"), person("Constance", "Brett", role = c("ctb")), diff --git a/R/downloadableReactTable.R b/R/downloadableReactTable.R index 7b00204c..56878b7c 100644 --- a/R/downloadableReactTable.R +++ b/R/downloadableReactTable.R @@ -119,16 +119,17 @@ downloadableReactTableUI <- function(id, #' @param table_data reactive expression (or parameter-less function) that acts as table data source #' @param selection_mode to enable row selection, set \code{selection_mode} value to either "single" for single row #' selection or "multiple" for multiple rows selection, case insensitive. Any other value will -#' disable row selection, (default = NULL). An additional column will be added to the table if +#' disable row selection. An additional column will be added to the table if #' selection mode is enabled with radio buttons for single row selection and checkboxes for -#' "multiple" rows selection mode. +#' "multiple" rows selection mode (default = NULL) #' @param pre_selected_rows reactive expression (or parameter-less function) provides the rows indices of the rows to #' be selected when the table is rendered. If selection_mode is disabled, this parameter will -#' have no effect. If selection_mode is "single" only first row index will be used. -#' @param file_name_root the base text used for user-downloaded file. It can be either a character string, +#' have no effect. If selection_mode is "single" only first row index will be used (default = NULL) +#' @param file_name_root the base text used for user-downloaded file. It can be either a character string #' a reactive expression or a function returning a character string (default = 'data_file') -#' @param download_data_fxns a \strong{named} list of functions providing the data as return values. -#' The names for the list should be the same names that were used when the table UI was created +#' @param download_data_fxns a \strong{named} list of functions providing the data as return values +#' The names for the list should be the same names that were used when the table UI +#' was created (default = NULL) #' @param pagination to enable table pagination (default = FALSE) #' @param table_height max table height in pixels. Vertical scroll will be shown after that height value #' @param show_rownames enable displaying rownames as a separate column (default = FALSE) diff --git a/inst/fw_templ/p_example/global.R b/inst/fw_templ/p_example/global.R index 20fc290b..d7d8c593 100644 --- a/inst/fw_templ/p_example/global.R +++ b/inst/fw_templ/p_example/global.R @@ -9,6 +9,7 @@ # to server, UI and session scopes # ---------------------------------------- library(DT) +library(reactable) library(shiny) library(periscope2) library(shinyWidgets) diff --git a/inst/fw_templ/p_example/program_helpers.R b/inst/fw_templ/p_example/program_helpers.R index 9dbdf6f0..fcdb03d3 100644 --- a/inst/fw_templ/p_example/program_helpers.R +++ b/inst/fw_templ/p_example/program_helpers.R @@ -9,6 +9,7 @@ files_idx <- read.csv("program/data/struc_indx.csv") rownames(app_files) <- app_files$X app_files$X <- NULL +app_files <- app_files %>% mutate(across(where(is.character), ~na_if(., ""))) rownames(files_idx) <- files_idx$X files_idx$X <- NULL diff --git a/inst/fw_templ/p_example/server_local.R b/inst/fw_templ/p_example/server_local.R index d6167639..fa1b0129 100644 --- a/inst/fw_templ/p_example/server_local.R +++ b/inst/fw_templ/p_example/server_local.R @@ -78,6 +78,40 @@ downloadableTable("exampleDT1", formatStyle = list(columns = c("Natural.Increase"), backgroundColor = DT::styleInterval(c(7614, 15914, 34152), c("lightgray", "gray", "cadetblue", "#808000"))))) +downloadableReactTable(id = "exampleReactTable", + logger = ss_userAction.Log, + table_data = load_data3, + file_name_root = "exampleReacttable", + download_data_fxns = list(csv = load_data3, tsv = load_data3), + table_options = list( + defaultSorted = "Total.Population.Change", + columnGroups = list(colGroup(name = "Statistics", columns = c("Total.Population.Change", "Natural.Increase"))), + columns = list( + Total.Population.Change = colDef( + name = "Change", + filterable = TRUE, + cell = function(value) { + if (value <= 0) { + tags$span(style = "color:red", value) + } else { + tags$span(style = "color:green", value) + } + }), + Natural.Increase = colDef( + name = "Increase", + filterable = TRUE, + cell = function(value) { + if (value <= 7614) { + tags$span(class = "badge bg-primary", value) + } else if (value <= 15914) { + tags$span(class = "badge bg-secondary", value) + } else if (value <= 34152) { + tags$span(class = "badge bg-info", value) + } else { + tags$span(class = "badge bg-success", value) + } + }), + Geographic.Area = colDef(name = "Location", filterable = TRUE)))) downloadablePlot("examplePlot2", ss_userAction.Log, filenameroot = "plot2_ggplot", @@ -204,6 +238,9 @@ output$file_structure_plot <- renderCanvasXpress({ hierarchy = list("App_Root", "L1"), title = "Empty Application Files", graphOrientation = "horizontal", + xAxis = list("order"), + colorBy = list("App_Root"), + showLegend = FALSE, events = node_event ) }) diff --git a/inst/fw_templ/p_example/ui_body.R b/inst/fw_templ/p_example/ui_body.R index 07289ae5..e04a61f9 100644 --- a/inst/fw_templ/p_example/ui_body.R +++ b/inst/fw_templ/p_example/ui_body.R @@ -141,6 +141,47 @@ application_setup <- tabItem(tabName = "application_setup", plot2_hover <- hoverOpts(id = "examplePlot2_hover") +react_table_box <- box( + id = "react_downloader", + title = "React Table Downloader", + status = "info", + solidHeader = TRUE, + collapsible = TRUE, + width = 12, + fluidRow(column(width = 6, + tags$dl(tags$dt("Features"), + tags$ul(tags$li("React table downloader module display tabular data in rich formatted tables using", tags$b("`reactable`"), "package"), + tags$li("downloadabkeReactTable returns two names reactive list that can be used in application different areas", + tags$ul(tags$li(tags$b("selected_rows"), ": data.frame of current selected rows"), + tags$li(tags$b("table_state"), ": a list of current rendered table state values."))), + tags$li("Table data can be downloaded in different formats such as: ", tags$b("'csv'"), + ", ", tags$b("'tsv'"), ", ", tags$b("'txt'"), "and/or ", tags$b("'xlsx'")), + tags$li("User can customize downloadableReactTable modules using reactable package options."), + tags$li("For more information about table options please visit the", + tags$a("Reactable documentation", target = "_blank", href = "https://glin.github.io/reactable/"), + "site") + ))), + column(width = 6, + tags$dl(tags$dt("Setup"), + tags$li("Module should be configured in both UI and Server code"), + tags$li("In your 'body_ui.R', place module UI part as follow: ", + blockQuote("downloadableReactTableUI('exampleReactTable', + 'Download react table data'))", color = "info")), + tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + blockQuote("downloadableReactTable('exampleReactTable', + ss_userAction.Log, + 'exampletable', + list(csv = load_data3, tsv = load_data3))", color = "info")), + tags$li("Review ", tags$b("?downloadableReactTableUI"), " and ", tags$b("?downloadableReactTable"), " for more information"), + tags$li("Review below table for detailed example code")))), + fluidRow(column(width = 12, + downloadableReactTableUI(id = "exampleReactTable", + downloadtypes = list("csv", "tsv"), + hovertext = "Download react table data"))) + +) + + table_downloader_box <- box( id = "table_downloader", title = "Table Downloader", @@ -299,6 +340,7 @@ reset_application_box <- box( periscope_modules <- tabItem(tabName = "periscope_modules", file_downloader_box, reset_application_box, + react_table_box, table_downloader_box, plot_downloader_box) diff --git a/inst/fw_templ/p_example/ui_body_no_left_sidebar.R b/inst/fw_templ/p_example/ui_body_no_left_sidebar.R index 7ede0919..b58e16d3 100644 --- a/inst/fw_templ/p_example/ui_body_no_left_sidebar.R +++ b/inst/fw_templ/p_example/ui_body_no_left_sidebar.R @@ -135,6 +135,48 @@ files_organization_box <- box( plot2_hover <- hoverOpts(id = "examplePlot2_hover") +react_table_box <- box( + id = "react_downloader", + title = "React Table Downloader", + status = "info", + solidHeader = TRUE, + collapsible = TRUE, + width = 12, + fluidRow(column(width = 6, + tags$dl(tags$dt("Features"), + tags$ul(tags$li("React table downloader module display tabular data in rich formatted tables using", tags$b("`reactable`"), "package"), + tags$li("downloadabkeReactTable returns two names reactive list that can be used in application different areas", + tags$ul(tags$li(tags$b("selected_rows"), ": data.frame of current selected rows"), + tags$li(tags$b("table_state"), ": a list of current rendered table state values."))), + tags$li("Table data can be downloaded in different formats such as: ", tags$b("'csv'"), + ", ", tags$b("'tsv'"), ", ", tags$b("'txt'"), "and/or ", tags$b("'xlsx'")), + tags$li("User can customize downloadableReactTable modules using reactable package options."), + tags$li("For more information about table options please visit the", + tags$a("Reactable documentation", target = "_blank", href = "https://glin.github.io/reactable/"), + "site") + ))), + column(width = 6, + tags$dl(tags$dt("Setup"), + tags$li("Module should be configured in both UI and Server code"), + tags$li("In your 'body_ui.R', place module UI part as follow: ", + blockQuote("downloadableReactTableUI('exampleReactTable', + 'Download react table data'))", color = "info")), + tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + blockQuote("downloadableReactTable('exampleReactTable', + ss_userAction.Log, + 'exampletable', + list(csv = load_data3, tsv = load_data3))", color = "info")), + tags$li("Review ", tags$b("?downloadableReactTableUI"), " and ", tags$b("?downloadableReactTable"), " for more information"), + tags$li("Review below table for detailed example code")))), + fluidRow(column(width = 12, + downloadableReactTableUI(id = "exampleReactTable", + downloadtypes = list("csv", "tsv"), + hovertext = "Download react table data"))) + +) + + + table_downloader_box <- box( id = "table_downloader", title = "Table Downloader", @@ -536,6 +578,7 @@ add_ui_body(list(uiOutput("app_theme"), announcements_box, fluidRow(file_downloader_box, reset_application_box), + react_table_box, table_downloader_box, plot_downloader_box, logger_box, diff --git a/man/downloadableReactTable.Rd b/man/downloadableReactTable.Rd index 35997c63..0e30b136 100644 --- a/man/downloadableReactTable.Rd +++ b/man/downloadableReactTable.Rd @@ -29,19 +29,20 @@ downloadableReactTable( \item{selection_mode}{to enable row selection, set \code{selection_mode} value to either "single" for single row selection or "multiple" for multiple rows selection, case insensitive. Any other value will -disable row selection, (default = NULL). An additional column will be added to the table if +disable row selection. An additional column will be added to the table if selection mode is enabled with radio buttons for single row selection and checkboxes for -"multiple" rows selection mode.} +"multiple" rows selection mode (default = NULL)} \item{pre_selected_rows}{reactive expression (or parameter-less function) provides the rows indices of the rows to be selected when the table is rendered. If selection_mode is disabled, this parameter will -have no effect. If selection_mode is "single" only first row index will be used.} +have no effect. If selection_mode is "single" only first row index will be used (default = NULL)} -\item{file_name_root}{the base text used for user-downloaded file. It can be either a character string, +\item{file_name_root}{the base text used for user-downloaded file. It can be either a character string a reactive expression or a function returning a character string (default = 'data_file')} -\item{download_data_fxns}{a \strong{named} list of functions providing the data as return values. -The names for the list should be the same names that were used when the table UI was created} +\item{download_data_fxns}{a \strong{named} list of functions providing the data as return values +The names for the list should be the same names that were used when the table UI +was created (default = NULL)} \item{pagination}{to enable table pagination (default = FALSE)} From f1f561cb726578667305d38ca38dd0c55521ca3f Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Wed, 6 Aug 2025 07:52:20 -0700 Subject: [PATCH 051/139] - Updated example apps in test folder --- .../program/fxn/program_helpers.R | 5 ++- .../sample_app_both_sidebars/program/global.R | 1 + .../program/server_local.R | 37 ++++++++++++++++ .../program/ui_body.R | 42 ++++++++++++++++++ .../program/fxn/program_helpers.R | 5 ++- .../sample_app_left_sidebar/program/global.R | 1 + .../program/server_local.R | 37 ++++++++++++++++ .../sample_app_left_sidebar/program/ui_body.R | 42 ++++++++++++++++++ .../program/fxn/program_helpers.R | 5 ++- .../program/global.R | 1 + .../program/server_local.R | 37 ++++++++++++++++ .../program/ui_body.R | 43 +++++++++++++++++++ .../program/fxn/program_helpers.R | 5 ++- .../sample_app_right_sidebar/program/global.R | 1 + .../program/server_local.R | 37 ++++++++++++++++ .../program/ui_body.R | 43 +++++++++++++++++++ 16 files changed, 334 insertions(+), 8 deletions(-) diff --git a/tests/testthat/sample_app_both_sidebars/program/fxn/program_helpers.R b/tests/testthat/sample_app_both_sidebars/program/fxn/program_helpers.R index 5e0b6d64..d6d7d968 100644 --- a/tests/testthat/sample_app_both_sidebars/program/fxn/program_helpers.R +++ b/tests/testthat/sample_app_both_sidebars/program/fxn/program_helpers.R @@ -9,6 +9,7 @@ files_idx <- read.csv("program/data/struc_indx.csv") rownames(app_files) <- app_files$X app_files$X <- NULL +app_files <- app_files %>% mutate(across(where(is.character), ~na_if(., ""))) rownames(files_idx) <- files_idx$X files_idx$X <- NULL @@ -32,10 +33,10 @@ load_data2 <- function() { load_data3 <- function() { ldf <- df %>% - select(1:3) %>% + select(1:3) %>% mutate(Total.Population.Change = as.numeric(gsub(",", "", Total.Population.Change)), Natural.Increase = as.numeric(gsub(",", "", Natural.Increase))) - + as.data.frame(ldf) } diff --git a/tests/testthat/sample_app_both_sidebars/program/global.R b/tests/testthat/sample_app_both_sidebars/program/global.R index f0ec7ffb..5952b8dc 100644 --- a/tests/testthat/sample_app_both_sidebars/program/global.R +++ b/tests/testthat/sample_app_both_sidebars/program/global.R @@ -9,6 +9,7 @@ # to server, UI and session scopes # ---------------------------------------- library(DT) +library(reactable) library(shiny) library(periscope2) library(waiter) diff --git a/tests/testthat/sample_app_both_sidebars/program/server_local.R b/tests/testthat/sample_app_both_sidebars/program/server_local.R index 2b7647bb..da48d989 100644 --- a/tests/testthat/sample_app_both_sidebars/program/server_local.R +++ b/tests/testthat/sample_app_both_sidebars/program/server_local.R @@ -70,6 +70,43 @@ downloadableTable("exampleDT1", formatStyle = list(columns = c("Natural.Increase"), backgroundColor = DT::styleInterval(c(7614, 15914, 34152), c("lightgray", "gray", "cadetblue", "#808000"))))) + +downloadableReactTable(id = "exampleReactTable", + logger = ss_userAction.Log, + table_data = load_data3, + file_name_root = "exampleReacttable", + download_data_fxns = list(csv = load_data3, tsv = load_data3), + table_options = list( + defaultSorted = "Total.Population.Change", + columnGroups = list(colGroup(name = "Statistics", columns = c("Total.Population.Change", "Natural.Increase"))), + columns = list( + Total.Population.Change = colDef( + name = "Change", + filterable = TRUE, + cell = function(value) { + if (value <= 0) { + tags$span(style = "color:red", value) + } else { + tags$span(style = "color:green", value) + } + }), + Natural.Increase = colDef( + name = "Increase", + filterable = TRUE, + cell = function(value) { + if (value <= 7614) { + tags$span(class = "badge bg-primary", value) + } else if (value <= 15914) { + tags$span(class = "badge bg-secondary", value) + } else if (value <= 34152) { + tags$span(class = "badge bg-info", value) + } else { + tags$span(class = "badge bg-success", value) + } + }), + Geographic.Area = colDef(name = "Location", filterable = TRUE)))) + + downloadablePlot("examplePlot2", ss_userAction.Log, filenameroot = "plot2_ggplot", diff --git a/tests/testthat/sample_app_both_sidebars/program/ui_body.R b/tests/testthat/sample_app_both_sidebars/program/ui_body.R index 84fb2d53..29f0ce91 100644 --- a/tests/testthat/sample_app_both_sidebars/program/ui_body.R +++ b/tests/testthat/sample_app_both_sidebars/program/ui_body.R @@ -141,6 +141,47 @@ application_setup <- tabItem(tabName = "application_setup", plot2_hover <- hoverOpts(id = "examplePlot2_hover") +react_table_box <- box( + id = "react_downloader", + title = "React Table Downloader", + status = "info", + solidHeader = TRUE, + collapsible = TRUE, + width = 12, + fluidRow(column(width = 6, + tags$dl(tags$dt("Features"), + tags$ul(tags$li("React table downloader module display tabular data in rich formatted tables using", tags$b("`reactable`"), "package"), + tags$li("downloadabkeReactTable returns two names reactive list that can be used in application different areas", + tags$ul(tags$li(tags$b("selected_rows"), ": data.frame of current selected rows"), + tags$li(tags$b("table_state"), ": a list of current rendered table state values."))), + tags$li("Table data can be downloaded in different formats such as: ", tags$b("'csv'"), + ", ", tags$b("'tsv'"), ", ", tags$b("'txt'"), "and/or ", tags$b("'xlsx'")), + tags$li("User can customize downloadableReactTable modules using reactable package options."), + tags$li("For more information about table options please visit the", + tags$a("Reactable documentation", target = "_blank", href = "https://glin.github.io/reactable/"), + "site") + ))), + column(width = 6, + tags$dl(tags$dt("Setup"), + tags$li("Module should be configured in both UI and Server code"), + tags$li("In your 'body_ui.R', place module UI part as follow: ", + blockQuote("downloadableReactTableUI('exampleReactTable', + 'Download react table data'))", color = "info")), + tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + blockQuote("downloadableReactTable('exampleReactTable', + ss_userAction.Log, + 'exampletable', + list(csv = load_data3, tsv = load_data3))", color = "info")), + tags$li("Review ", tags$b("?downloadableReactTableUI"), " and ", tags$b("?downloadableReactTable"), " for more information"), + tags$li("Review below table for detailed example code")))), + fluidRow(column(width = 12, + downloadableReactTableUI(id = "exampleReactTable", + downloadtypes = list("csv", "tsv"), + hovertext = "Download react table data"))) + +) + + table_downloader_box <- box( id = "table_downloader", title = "Table Downloader", @@ -297,6 +338,7 @@ reset_application_box <- box( periscope_modules <- tabItem(tabName = "periscope_modules", + react_table_box, table_downloader_box, plot_downloader_box, file_downloader_box, diff --git a/tests/testthat/sample_app_left_sidebar/program/fxn/program_helpers.R b/tests/testthat/sample_app_left_sidebar/program/fxn/program_helpers.R index 5e0b6d64..d6d7d968 100644 --- a/tests/testthat/sample_app_left_sidebar/program/fxn/program_helpers.R +++ b/tests/testthat/sample_app_left_sidebar/program/fxn/program_helpers.R @@ -9,6 +9,7 @@ files_idx <- read.csv("program/data/struc_indx.csv") rownames(app_files) <- app_files$X app_files$X <- NULL +app_files <- app_files %>% mutate(across(where(is.character), ~na_if(., ""))) rownames(files_idx) <- files_idx$X files_idx$X <- NULL @@ -32,10 +33,10 @@ load_data2 <- function() { load_data3 <- function() { ldf <- df %>% - select(1:3) %>% + select(1:3) %>% mutate(Total.Population.Change = as.numeric(gsub(",", "", Total.Population.Change)), Natural.Increase = as.numeric(gsub(",", "", Natural.Increase))) - + as.data.frame(ldf) } diff --git a/tests/testthat/sample_app_left_sidebar/program/global.R b/tests/testthat/sample_app_left_sidebar/program/global.R index f0ec7ffb..5952b8dc 100644 --- a/tests/testthat/sample_app_left_sidebar/program/global.R +++ b/tests/testthat/sample_app_left_sidebar/program/global.R @@ -9,6 +9,7 @@ # to server, UI and session scopes # ---------------------------------------- library(DT) +library(reactable) library(shiny) library(periscope2) library(waiter) diff --git a/tests/testthat/sample_app_left_sidebar/program/server_local.R b/tests/testthat/sample_app_left_sidebar/program/server_local.R index beb90bda..cbfc91bf 100644 --- a/tests/testthat/sample_app_left_sidebar/program/server_local.R +++ b/tests/testthat/sample_app_left_sidebar/program/server_local.R @@ -70,6 +70,43 @@ downloadableTable("exampleDT1", formatStyle = list(columns = c("Natural.Increase"), backgroundColor = DT::styleInterval(c(7614, 15914, 34152), c("lightgray", "gray", "cadetblue", "#808000"))))) + +downloadableReactTable(id = "exampleReactTable", + logger = ss_userAction.Log, + table_data = load_data3, + file_name_root = "exampleReacttable", + download_data_fxns = list(csv = load_data3, tsv = load_data3), + table_options = list( + defaultSorted = "Total.Population.Change", + columnGroups = list(colGroup(name = "Statistics", columns = c("Total.Population.Change", "Natural.Increase"))), + columns = list( + Total.Population.Change = colDef( + name = "Change", + filterable = TRUE, + cell = function(value) { + if (value <= 0) { + tags$span(style = "color:red", value) + } else { + tags$span(style = "color:green", value) + } + }), + Natural.Increase = colDef( + name = "Increase", + filterable = TRUE, + cell = function(value) { + if (value <= 7614) { + tags$span(class = "badge bg-primary", value) + } else if (value <= 15914) { + tags$span(class = "badge bg-secondary", value) + } else if (value <= 34152) { + tags$span(class = "badge bg-info", value) + } else { + tags$span(class = "badge bg-success", value) + } + }), + Geographic.Area = colDef(name = "Location", filterable = TRUE)))) + + downloadablePlot("examplePlot2", ss_userAction.Log, filenameroot = "plot2_ggplot", diff --git a/tests/testthat/sample_app_left_sidebar/program/ui_body.R b/tests/testthat/sample_app_left_sidebar/program/ui_body.R index d1c33791..74e3159d 100644 --- a/tests/testthat/sample_app_left_sidebar/program/ui_body.R +++ b/tests/testthat/sample_app_left_sidebar/program/ui_body.R @@ -186,6 +186,47 @@ table_downloader_box <- box( ) + +react_table_box <- box( + id = "react_downloader", + title = "React Table Downloader", + status = "info", + solidHeader = TRUE, + collapsible = TRUE, + width = 12, + fluidRow(column(width = 6, + tags$dl(tags$dt("Features"), + tags$ul(tags$li("React table downloader module display tabular data in rich formatted tables using", tags$b("`reactable`"), "package"), + tags$li("downloadabkeReactTable returns two names reactive list that can be used in application different areas", + tags$ul(tags$li(tags$b("selected_rows"), ": data.frame of current selected rows"), + tags$li(tags$b("table_state"), ": a list of current rendered table state values."))), + tags$li("Table data can be downloaded in different formats such as: ", tags$b("'csv'"), + ", ", tags$b("'tsv'"), ", ", tags$b("'txt'"), "and/or ", tags$b("'xlsx'")), + tags$li("User can customize downloadableReactTable modules using reactable package options."), + tags$li("For more information about table options please visit the", + tags$a("Reactable documentation", target = "_blank", href = "https://glin.github.io/reactable/"), + "site") + ))), + column(width = 6, + tags$dl(tags$dt("Setup"), + tags$li("Module should be configured in both UI and Server code"), + tags$li("In your 'body_ui.R', place module UI part as follow: ", + blockQuote("downloadableReactTableUI('exampleReactTable', + 'Download react table data'))", color = "info")), + tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + blockQuote("downloadableReactTable('exampleReactTable', + ss_userAction.Log, + 'exampletable', + list(csv = load_data3, tsv = load_data3))", color = "info")), + tags$li("Review ", tags$b("?downloadableReactTableUI"), " and ", tags$b("?downloadableReactTable"), " for more information"), + tags$li("Review below table for detailed example code")))), + fluidRow(column(width = 12, + downloadableReactTableUI(id = "exampleReactTable", + downloadtypes = list("csv", "tsv"), + hovertext = "Download react table data"))) + +) + file_downloader_box <- box( id = "file_downloader", title = "File Downloader", @@ -297,6 +338,7 @@ reset_application_box <- box( periscope_modules <- tabItem(tabName = "periscope_modules", + react_table_box, table_downloader_box, plot_downloader_box, file_downloader_box, diff --git a/tests/testthat/sample_app_no_both_sidebars/program/fxn/program_helpers.R b/tests/testthat/sample_app_no_both_sidebars/program/fxn/program_helpers.R index 5e0b6d64..d6d7d968 100644 --- a/tests/testthat/sample_app_no_both_sidebars/program/fxn/program_helpers.R +++ b/tests/testthat/sample_app_no_both_sidebars/program/fxn/program_helpers.R @@ -9,6 +9,7 @@ files_idx <- read.csv("program/data/struc_indx.csv") rownames(app_files) <- app_files$X app_files$X <- NULL +app_files <- app_files %>% mutate(across(where(is.character), ~na_if(., ""))) rownames(files_idx) <- files_idx$X files_idx$X <- NULL @@ -32,10 +33,10 @@ load_data2 <- function() { load_data3 <- function() { ldf <- df %>% - select(1:3) %>% + select(1:3) %>% mutate(Total.Population.Change = as.numeric(gsub(",", "", Total.Population.Change)), Natural.Increase = as.numeric(gsub(",", "", Natural.Increase))) - + as.data.frame(ldf) } diff --git a/tests/testthat/sample_app_no_both_sidebars/program/global.R b/tests/testthat/sample_app_no_both_sidebars/program/global.R index f0ec7ffb..5952b8dc 100644 --- a/tests/testthat/sample_app_no_both_sidebars/program/global.R +++ b/tests/testthat/sample_app_no_both_sidebars/program/global.R @@ -9,6 +9,7 @@ # to server, UI and session scopes # ---------------------------------------- library(DT) +library(reactable) library(shiny) library(periscope2) library(waiter) diff --git a/tests/testthat/sample_app_no_both_sidebars/program/server_local.R b/tests/testthat/sample_app_no_both_sidebars/program/server_local.R index 7f5c46ab..5066c62a 100644 --- a/tests/testthat/sample_app_no_both_sidebars/program/server_local.R +++ b/tests/testthat/sample_app_no_both_sidebars/program/server_local.R @@ -70,6 +70,43 @@ downloadableTable("exampleDT1", formatStyle = list(columns = c("Natural.Increase"), backgroundColor = DT::styleInterval(c(7614, 15914, 34152), c("lightgray", "gray", "cadetblue", "#808000"))))) + +downloadableReactTable(id = "exampleReactTable", + logger = ss_userAction.Log, + table_data = load_data3, + file_name_root = "exampleReacttable", + download_data_fxns = list(csv = load_data3, tsv = load_data3), + table_options = list( + defaultSorted = "Total.Population.Change", + columnGroups = list(colGroup(name = "Statistics", columns = c("Total.Population.Change", "Natural.Increase"))), + columns = list( + Total.Population.Change = colDef( + name = "Change", + filterable = TRUE, + cell = function(value) { + if (value <= 0) { + tags$span(style = "color:red", value) + } else { + tags$span(style = "color:green", value) + } + }), + Natural.Increase = colDef( + name = "Increase", + filterable = TRUE, + cell = function(value) { + if (value <= 7614) { + tags$span(class = "badge bg-primary", value) + } else if (value <= 15914) { + tags$span(class = "badge bg-secondary", value) + } else if (value <= 34152) { + tags$span(class = "badge bg-info", value) + } else { + tags$span(class = "badge bg-success", value) + } + }), + Geographic.Area = colDef(name = "Location", filterable = TRUE)))) + + downloadablePlot("examplePlot2", ss_userAction.Log, filenameroot = "plot2_ggplot", diff --git a/tests/testthat/sample_app_no_both_sidebars/program/ui_body.R b/tests/testthat/sample_app_no_both_sidebars/program/ui_body.R index 488885a4..7ed3021d 100644 --- a/tests/testthat/sample_app_no_both_sidebars/program/ui_body.R +++ b/tests/testthat/sample_app_no_both_sidebars/program/ui_body.R @@ -180,6 +180,48 @@ table_downloader_box <- box( ) + +react_table_box <- box( + id = "react_downloader", + title = "React Table Downloader", + status = "info", + solidHeader = TRUE, + collapsible = TRUE, + width = 12, + fluidRow(column(width = 6, + tags$dl(tags$dt("Features"), + tags$ul(tags$li("React table downloader module display tabular data in rich formatted tables using", tags$b("`reactable`"), "package"), + tags$li("downloadabkeReactTable returns two names reactive list that can be used in application different areas", + tags$ul(tags$li(tags$b("selected_rows"), ": data.frame of current selected rows"), + tags$li(tags$b("table_state"), ": a list of current rendered table state values."))), + tags$li("Table data can be downloaded in different formats such as: ", tags$b("'csv'"), + ", ", tags$b("'tsv'"), ", ", tags$b("'txt'"), "and/or ", tags$b("'xlsx'")), + tags$li("User can customize downloadableReactTable modules using reactable package options."), + tags$li("For more information about table options please visit the", + tags$a("Reactable documentation", target = "_blank", href = "https://glin.github.io/reactable/"), + "site") + ))), + column(width = 6, + tags$dl(tags$dt("Setup"), + tags$li("Module should be configured in both UI and Server code"), + tags$li("In your 'body_ui.R', place module UI part as follow: ", + blockQuote("downloadableReactTableUI('exampleReactTable', + 'Download react table data'))", color = "info")), + tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + blockQuote("downloadableReactTable('exampleReactTable', + ss_userAction.Log, + 'exampletable', + list(csv = load_data3, tsv = load_data3))", color = "info")), + tags$li("Review ", tags$b("?downloadableReactTableUI"), " and ", tags$b("?downloadableReactTable"), " for more information"), + tags$li("Review below table for detailed example code")))), + fluidRow(column(width = 12, + downloadableReactTableUI(id = "exampleReactTable", + downloadtypes = list("csv", "tsv"), + hovertext = "Download react table data"))) + +) + + file_downloader_box <- box( id = "file_downloader", title = "File Downloader", @@ -291,6 +333,7 @@ reset_application_box <- box( periscope_modules <- tabItem(tabName = "periscope_modules", + react_table_box, table_downloader_box, plot_downloader_box, file_downloader_box, diff --git a/tests/testthat/sample_app_right_sidebar/program/fxn/program_helpers.R b/tests/testthat/sample_app_right_sidebar/program/fxn/program_helpers.R index 5e0b6d64..d6d7d968 100644 --- a/tests/testthat/sample_app_right_sidebar/program/fxn/program_helpers.R +++ b/tests/testthat/sample_app_right_sidebar/program/fxn/program_helpers.R @@ -9,6 +9,7 @@ files_idx <- read.csv("program/data/struc_indx.csv") rownames(app_files) <- app_files$X app_files$X <- NULL +app_files <- app_files %>% mutate(across(where(is.character), ~na_if(., ""))) rownames(files_idx) <- files_idx$X files_idx$X <- NULL @@ -32,10 +33,10 @@ load_data2 <- function() { load_data3 <- function() { ldf <- df %>% - select(1:3) %>% + select(1:3) %>% mutate(Total.Population.Change = as.numeric(gsub(",", "", Total.Population.Change)), Natural.Increase = as.numeric(gsub(",", "", Natural.Increase))) - + as.data.frame(ldf) } diff --git a/tests/testthat/sample_app_right_sidebar/program/global.R b/tests/testthat/sample_app_right_sidebar/program/global.R index f0ec7ffb..5952b8dc 100644 --- a/tests/testthat/sample_app_right_sidebar/program/global.R +++ b/tests/testthat/sample_app_right_sidebar/program/global.R @@ -9,6 +9,7 @@ # to server, UI and session scopes # ---------------------------------------- library(DT) +library(reactable) library(shiny) library(periscope2) library(waiter) diff --git a/tests/testthat/sample_app_right_sidebar/program/server_local.R b/tests/testthat/sample_app_right_sidebar/program/server_local.R index df843931..3f6a703e 100644 --- a/tests/testthat/sample_app_right_sidebar/program/server_local.R +++ b/tests/testthat/sample_app_right_sidebar/program/server_local.R @@ -70,6 +70,43 @@ downloadableTable("exampleDT1", formatStyle = list(columns = c("Natural.Increase"), backgroundColor = DT::styleInterval(c(7614, 15914, 34152), c("lightgray", "gray", "cadetblue", "#808000"))))) + +downloadableReactTable(id = "exampleReactTable", + logger = ss_userAction.Log, + table_data = load_data3, + file_name_root = "exampleReacttable", + download_data_fxns = list(csv = load_data3, tsv = load_data3), + table_options = list( + defaultSorted = "Total.Population.Change", + columnGroups = list(colGroup(name = "Statistics", columns = c("Total.Population.Change", "Natural.Increase"))), + columns = list( + Total.Population.Change = colDef( + name = "Change", + filterable = TRUE, + cell = function(value) { + if (value <= 0) { + tags$span(style = "color:red", value) + } else { + tags$span(style = "color:green", value) + } + }), + Natural.Increase = colDef( + name = "Increase", + filterable = TRUE, + cell = function(value) { + if (value <= 7614) { + tags$span(class = "badge bg-primary", value) + } else if (value <= 15914) { + tags$span(class = "badge bg-secondary", value) + } else if (value <= 34152) { + tags$span(class = "badge bg-info", value) + } else { + tags$span(class = "badge bg-success", value) + } + }), + Geographic.Area = colDef(name = "Location", filterable = TRUE)))) + + downloadablePlot("examplePlot2", ss_userAction.Log, filenameroot = "plot2_ggplot", diff --git a/tests/testthat/sample_app_right_sidebar/program/ui_body.R b/tests/testthat/sample_app_right_sidebar/program/ui_body.R index e8a2df84..82df836c 100644 --- a/tests/testthat/sample_app_right_sidebar/program/ui_body.R +++ b/tests/testthat/sample_app_right_sidebar/program/ui_body.R @@ -186,6 +186,48 @@ table_downloader_box <- box( ) + +react_table_box <- box( + id = "react_downloader", + title = "React Table Downloader", + status = "info", + solidHeader = TRUE, + collapsible = TRUE, + width = 12, + fluidRow(column(width = 6, + tags$dl(tags$dt("Features"), + tags$ul(tags$li("React table downloader module display tabular data in rich formatted tables using", tags$b("`reactable`"), "package"), + tags$li("downloadabkeReactTable returns two names reactive list that can be used in application different areas", + tags$ul(tags$li(tags$b("selected_rows"), ": data.frame of current selected rows"), + tags$li(tags$b("table_state"), ": a list of current rendered table state values."))), + tags$li("Table data can be downloaded in different formats such as: ", tags$b("'csv'"), + ", ", tags$b("'tsv'"), ", ", tags$b("'txt'"), "and/or ", tags$b("'xlsx'")), + tags$li("User can customize downloadableReactTable modules using reactable package options."), + tags$li("For more information about table options please visit the", + tags$a("Reactable documentation", target = "_blank", href = "https://glin.github.io/reactable/"), + "site") + ))), + column(width = 6, + tags$dl(tags$dt("Setup"), + tags$li("Module should be configured in both UI and Server code"), + tags$li("In your 'body_ui.R', place module UI part as follow: ", + blockQuote("downloadableReactTableUI('exampleReactTable', + 'Download react table data'))", color = "info")), + tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + blockQuote("downloadableReactTable('exampleReactTable', + ss_userAction.Log, + 'exampletable', + list(csv = load_data3, tsv = load_data3))", color = "info")), + tags$li("Review ", tags$b("?downloadableReactTableUI"), " and ", tags$b("?downloadableReactTable"), " for more information"), + tags$li("Review below table for detailed example code")))), + fluidRow(column(width = 12, + downloadableReactTableUI(id = "exampleReactTable", + downloadtypes = list("csv", "tsv"), + hovertext = "Download react table data"))) + +) + + file_downloader_box <- box( id = "file_downloader", title = "File Downloader", @@ -297,6 +339,7 @@ reset_application_box <- box( periscope_modules <- tabItem(tabName = "periscope_modules", + react_table_box table_downloader_box, plot_downloader_box, file_downloader_box, From 53bfe227912ee603b7427f117d065c1cf29d981a Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Wed, 6 Aug 2025 09:19:08 -0700 Subject: [PATCH 052/139] - Fixed downloadable table example app css issue --- inst/fw_templ/p_example/custom.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/inst/fw_templ/p_example/custom.css b/inst/fw_templ/p_example/custom.css index 758515ea..c781656d 100644 --- a/inst/fw_templ/p_example/custom.css +++ b/inst/fw_templ/p_example/custom.css @@ -53,6 +53,11 @@ ul.dropdown-menu { /* End of Application Header CSS rules */ +.hidden { + display: none !important; +} + + /* Other */ .help-block { From 2052e5883ccb78be8ca25d2128cc344e43fd576c Mon Sep 17 00:00:00 2001 From: Jennifer Walker Date: Wed, 6 Aug 2025 18:29:53 -0700 Subject: [PATCH 053/139] bugfix for xlsx workbook download --- DESCRIPTION | 2 +- R/downloadFile.R | 26 +++++++++++++------------- tests/testthat/test_download_file.R | 7 ++++--- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 24589838..f1bba881 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: periscope2 Type: Package Title: Enterprise Streamlined 'shiny' Application Framework Using 'bs4Dash' -Version: 0.3.0.9008 +Version: 0.3.0.9010 Authors@R: c( person("Mohammed", "Ali", role = c("aut", "cre"), email = "mohammed@aggregate-genius.com"), person("Constance", "Brett", role = c("ctb")), diff --git a/R/downloadFile.R b/R/downloadFile.R index 68dd5af5..fb965925 100644 --- a/R/downloadFile.R +++ b/R/downloadFile.R @@ -265,28 +265,28 @@ downloadFile <- function(id, # excel file else if (type == "xlsx") { - if (check_openxlsx2_availability()) { - if (inherits(data, "wbWorkbook")) { - openxlsx2::wb_save(data, file) - } else { - show_rownames <- attr(data, "show_rownames") + if (inherits(data, "wbWorkbook") && check_openxlsx2_availability()) { + openxlsx2::wb_save(data, file) + } else if ((inherits(data, "Workbook")) && + ("openxlsx" %in% attributes(class(data))) && + check_openxlsx_availability()) { + openxlsx::saveWorkbook(data, file) + } else { + show_rownames <- attr(data, "show_rownames") + + if (check_openxlsx2_availability()) { openxlsx2::write_xlsx(data, file, as_table = TRUE, row_names = !is.null(show_rownames) && show_rownames) - } - } else if (check_openxlsx_availability()) { - if ((inherits(data, "Workbook")) && ("openxlsx" %in% attributes(class(data)))) { - openxlsx::saveWorkbook(data, file) - } else { - show_rownames <- attr(data, "show_rownames") + } else if (check_openxlsx_availability()) { openxlsx::write.xlsx(data, file, asTable = TRUE, rowNames = !is.null(show_rownames) && show_rownames) + } else { + writexl::write_xlsx(data, file) } - } else { - writexl::write_xlsx(data, file) } } # text file processing diff --git a/tests/testthat/test_download_file.R b/tests/testthat/test_download_file.R index 8cbf62a7..a78889f7 100644 --- a/tests/testthat/test_download_file.R +++ b/tests/testthat/test_download_file.R @@ -159,10 +159,13 @@ test_that("downloadFile - show rownames", { testServer(downloadFile, args = list(logger = periscope2:::fw_get_user_log(), filenameroot = "show_row_names_download", - datafxns = list(csv = download_data_show_row_names)), + datafxns = list(csv = download_data_show_row_names, + xlsx = download_data_show_row_names)), expr = { expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < show_row_names_download.csv >", x = capture_output(expect_snapshot_file(output$csv)))) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < show_row_names_download.xlsx >", + x = capture_output(file.exists(output$xlsx)))) }) }) @@ -234,7 +237,6 @@ test_that("Testing workbook openxlsx2", { test_that("Testing workbook openxlsx", { skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") skip_if_not_installed("openxlsx") - local_mocked_bindings(check_openxlsx2_availability = function() FALSE) testServer(downloadFile, args = list(logger = periscope2:::fw_get_user_log(), filenameroot = "excel_test_openxlsx_wb", @@ -248,7 +250,6 @@ test_that("Testing workbook openxlsx", { test_that("Dataframe xlsx download works with openxlsx2", { skip_if(getRversion() < "4.1.0", "Skipping due to lifecycle warnings in R < 4.1.0") skip_if_not_installed("openxlsx2") - local_mocked_bindings(check_openxlsx_availability = function() FALSE) testServer(downloadFile, args = list(logger = periscope2:::fw_get_user_log(), filenameroot = "excel_test_dataframe", From b4f2a4f04d3625aaeac3ec109462bc568cf1b9c8 Mon Sep 17 00:00:00 2001 From: Jennifer Walker Date: Wed, 6 Aug 2025 19:06:46 -0700 Subject: [PATCH 054/139] cleanup --- R/downloadFile.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/downloadFile.R b/R/downloadFile.R index fb965925..21de7df3 100644 --- a/R/downloadFile.R +++ b/R/downloadFile.R @@ -267,7 +267,7 @@ downloadFile <- function(id, if (inherits(data, "wbWorkbook") && check_openxlsx2_availability()) { openxlsx2::wb_save(data, file) - } else if ((inherits(data, "Workbook")) && + } else if (inherits(data, "Workbook") && ("openxlsx" %in% attributes(class(data))) && check_openxlsx_availability()) { openxlsx::saveWorkbook(data, file) From aa7f4bfe7f8acb13de174b8f7317831ca7ff2cb7 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 7 Aug 2025 01:23:02 -0700 Subject: [PATCH 055/139] updated documentation --- R/downloadableReactTable.R | 6 +++--- man/downloadableReactTable.Rd | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/R/downloadableReactTable.R b/R/downloadableReactTable.R index 56878b7c..954f4045 100644 --- a/R/downloadableReactTable.R +++ b/R/downloadableReactTable.R @@ -119,12 +119,12 @@ downloadableReactTableUI <- function(id, #' @param table_data reactive expression (or parameter-less function) that acts as table data source #' @param selection_mode to enable row selection, set \code{selection_mode} value to either "single" for single row #' selection or "multiple" for multiple rows selection, case insensitive. Any other value will -#' disable row selection. An additional column will be added to the table if -#' selection mode is enabled with radio buttons for single row selection and checkboxes for +#' disable row selection. An additional column will be added to the table in +#' selection mode with radio buttons for single row selection and checkboxes for #' "multiple" rows selection mode (default = NULL) #' @param pre_selected_rows reactive expression (or parameter-less function) provides the rows indices of the rows to #' be selected when the table is rendered. If selection_mode is disabled, this parameter will -#' have no effect. If selection_mode is "single" only first row index will be used (default = NULL) +#' have no effect. If selection_mode is "single" only the first row index will be used (default = NULL) #' @param file_name_root the base text used for user-downloaded file. It can be either a character string #' a reactive expression or a function returning a character string (default = 'data_file') #' @param download_data_fxns a \strong{named} list of functions providing the data as return values diff --git a/man/downloadableReactTable.Rd b/man/downloadableReactTable.Rd index 0e30b136..9fa29c09 100644 --- a/man/downloadableReactTable.Rd +++ b/man/downloadableReactTable.Rd @@ -29,13 +29,13 @@ downloadableReactTable( \item{selection_mode}{to enable row selection, set \code{selection_mode} value to either "single" for single row selection or "multiple" for multiple rows selection, case insensitive. Any other value will -disable row selection. An additional column will be added to the table if -selection mode is enabled with radio buttons for single row selection and checkboxes for +disable row selection. An additional column will be added to the table in +selection mode with radio buttons for single row selection and checkboxes for "multiple" rows selection mode (default = NULL)} \item{pre_selected_rows}{reactive expression (or parameter-less function) provides the rows indices of the rows to be selected when the table is rendered. If selection_mode is disabled, this parameter will -have no effect. If selection_mode is "single" only first row index will be used (default = NULL)} +have no effect. If selection_mode is "single" only the first row index will be used (default = NULL)} \item{file_name_root}{the base text used for user-downloaded file. It can be either a character string a reactive expression or a function returning a character string (default = 'data_file')} From 5e0a852c0e83fb1c0f90519af7db7eef1fa16c00 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 7 Aug 2025 06:31:24 -0700 Subject: [PATCH 056/139] - init downloadableTable document --- vignettes/downloadableReactTable-module.Rmd | 61 +++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 vignettes/downloadableReactTable-module.Rmd diff --git a/vignettes/downloadableReactTable-module.Rmd b/vignettes/downloadableReactTable-module.Rmd new file mode 100644 index 00000000..f717d37d --- /dev/null +++ b/vignettes/downloadableReactTable-module.Rmd @@ -0,0 +1,61 @@ +--- +title: "Using downloadableReactTable Shiny Module" +author: "Mohammed Ali" +date: "`r Sys.Date()`" +output: + rmarkdown::html_vignette: + toc: TRUE + toc_depth: 3 +vignette: > + %\VignetteIndexEntry{Using downloadableReactTable Shiny Module} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +# Overview + +## Purpose +The document explains how to use **downloadableReactTable** shiny module in periscope2 applications. + +## Features +* Ability to display and download datasets. +* Table rows selection can be multiple, single or none (the default) + * When selection mode is enabled an additional column will be added to the table + * The selection controls will be radio buttons for single row selection and checkboxes for "multiple" rows selection mode +* Returns a reactive expression containing named list with two elements: + * **selected_rows**: data.frame of current selected rows + * **table_state**: a list of current rendered table state values. The list keys are ("page", "pageSize", "pages", "sorted" and "selected") +* Supports full-table searching including regular expressions +* Columns are sort-able in both directions +* Configurable table "window" (viewing area) height with infinite vertical +scrolling (no paging by default) +* Supports rownames +* Requires minimal code (see the Usage section for details) +* Uses **downloadFile** Shiny Module functionality to ensure consistent +download functionality for table data. +* **downloadFile** button will be hidden if `downloadableReactTable` parameter `download_data_fxns` or +`downloadableReactTableUI` parameter `downloadtypes` is empty + +# Usage + +## Shiny Module Overview + +## downloadableReactTableUI + +## downloadableReactTable + +## Sample Application + +# Additional Resources + + +**Vignettes** + +* [New Application](new-application.html) +* [downloadFile Module](downloadFile-module.html) +* [downloadablePlot Module](downloadablePlot-module.html) +* [logViewer Module](logViewer-module.html) +* [applicationReset Module](applicationReset-module.html) +* [announcement Module](announcement-module.html) +* [Announcement Configuration Builder](announcement_addin.html) +* [Theme Configuration Builder](themeBuilder_addin.html) From 89c97ca30fe361c77a4a54e6627b51dce794ca9a Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 7 Aug 2025 07:02:31 -0700 Subject: [PATCH 057/139] - Added usage overview and UI part sections --- vignettes/downloadableReactTable-module.Rmd | 31 ++++++++++++++++++ .../figures/downloadableReactTable-1.png | Bin 0 -> 25216 bytes 2 files changed, 31 insertions(+) create mode 100644 vignettes/figures/downloadableReactTable-1.png diff --git a/vignettes/downloadableReactTable-module.Rmd b/vignettes/downloadableReactTable-module.Rmd index f717d37d..effab181 100644 --- a/vignettes/downloadableReactTable-module.Rmd +++ b/vignettes/downloadableReactTable-module.Rmd @@ -39,9 +39,40 @@ download functionality for table data. # Usage ## Shiny Module Overview +Shiny modules consist of a pair of functions that modularize, or package, a small piece of reusable functionality. The UI function is called directly by the user to place the UI in the correct location (as with other shiny UI objects). The module server function that is called only once to set it up using the module name as a function inside the server function (i.e. user-local session scope. The first function argument is a string that represents the module id (the same id used in module UI function). Additional arguments can be supplied by the user based on the specific shiny module that is called. There can be additional helper functions that are a part of a shiny module. + +The **downloadableReactTable** Shiny Module is a part of the *periscope2* package and +consists of the following functions: + +* **downloadableReactTableUI** - the UI function to place the table in the application +* **downloadableReactTable** - the server function to be called inside server_local.R. ## downloadableReactTableUI +The **downloadableReactTableUI** function is called from the ui.R (or equivalent) +file in the location where the table should be placed. This is similar to other +UI element placement in shiny. + +The downloadableReactTableUI looks like: + +
+ +The downloadableReactTableUI function takes the unique object ID for the UI object. + +The next two arguments (downloadtypes and hovertext) are passed to the +downloadFileButton and set the file types the button will allow the user to +request and the downloadFileButton's tooltip text. + + +```{r, eval=F} +# Inside ui_body.R or ui_sidebar.R + + downloadableReactTableUI( + id = "object_id1", + downloadtypes = c("csv", "tsv"), + hovertext = "Download the data here!") +``` + ## downloadableReactTable ## Sample Application diff --git a/vignettes/figures/downloadableReactTable-1.png b/vignettes/figures/downloadableReactTable-1.png new file mode 100644 index 0000000000000000000000000000000000000000..3d2266dc3bcc2216b2e5724f0ce5e13b37c2b882 GIT binary patch literal 25216 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 MrB$R#B}_m58|m{hd;kCd literal 0 HcmV?d00001 From 12e5b4f45d6c8d5c50b502ad1a1e8feb288ecf84 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 7 Aug 2025 07:47:23 -0700 Subject: [PATCH 058/139] - Added server sections --- DESCRIPTION | 2 +- vignettes/downloadableReactTable-module.Rmd | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 289a19fa..f1bba881 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: periscope2 Type: Package Title: Enterprise Streamlined 'shiny' Application Framework Using 'bs4Dash' -Version: 0.3.0.9009 +Version: 0.3.0.9010 Authors@R: c( person("Mohammed", "Ali", role = c("aut", "cre"), email = "mohammed@aggregate-genius.com"), person("Constance", "Brett", role = c("ctb")), diff --git a/vignettes/downloadableReactTable-module.Rmd b/vignettes/downloadableReactTable-module.Rmd index effab181..8dbbe3f6 100644 --- a/vignettes/downloadableReactTable-module.Rmd +++ b/vignettes/downloadableReactTable-module.Rmd @@ -74,6 +74,27 @@ request and the downloadFileButton's tooltip text. ``` ## downloadableReactTable +The **downloadableReactTable** function is called directly. The call consists of the following: + +* the unique object ID that was provided to downloadableTableUI when creating the UI object +* the logging logger to be used +* **file_name_root** is an optional character, function or reactive expression providing downloadable file name +* the root (prefix) of the downloaded file name to be used in the browser as a character string or a reactive expression that returns a character string +* **download_data_fxns** named list of functions or reactive expressions that provide the data to the downloadFileButton (see below). + * It is important that the types of files to be downloaded are matched to the correct data function in the list. + * The function/reactive expression names are unquoted - they will be called at the time the user initiates a download *(see requirements below)*. + * The download functions or reactive expressions can all be the same or different from each other and/or the one provided to **table_data**. This allows finer control over what the user can view vs. download if desired. For example you can allow a user to view a smaller subset of data but download an expanded dataset, or perhaps download a redacted version of data, etc. +* This module also supports most of reactable table options for further customization. See the example below. + +**Data Function or Reactive Expression Requirements** + +* If a function is provided it must be parameter-less (require NO parameters). No parameters will be provided when a function is called to retrieve the plot or data. Reactive expressions cannot take parameters by definition. +* The function or reactive expression must return an appropriate data format for the file type. +* For instance: csv/tsv/xlsx types require data that is convertible to a tabular type, +to various download types see the downloadFile module help or vignette. +* For the visible table data the return value must be able to be converted to tabular format +* Since the function or reactive expression is called at the time the user requests the data it is +recommended that reactive expressions are used to provide dynamic values from the application to create the table. ## Sample Application From 38536c1b0204155ac9428e8e029a82719dcdedec Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 7 Aug 2025 08:03:17 -0700 Subject: [PATCH 059/139] - Added module return section --- vignettes/downloadableReactTable-module.Rmd | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/vignettes/downloadableReactTable-module.Rmd b/vignettes/downloadableReactTable-module.Rmd index 8dbbe3f6..159369c3 100644 --- a/vignettes/downloadableReactTable-module.Rmd +++ b/vignettes/downloadableReactTable-module.Rmd @@ -22,9 +22,7 @@ The document explains how to use **downloadableReactTable** shiny module in peri * Table rows selection can be multiple, single or none (the default) * When selection mode is enabled an additional column will be added to the table * The selection controls will be radio buttons for single row selection and checkboxes for "multiple" rows selection mode -* Returns a reactive expression containing named list with two elements: - * **selected_rows**: data.frame of current selected rows - * **table_state**: a list of current rendered table state values. The list keys are ("page", "pageSize", "pages", "sorted" and "selected") +* Returns a reactive expression containing named list with two elements: **selected_rows** and **table_state** * Supports full-table searching including regular expressions * Columns are sort-able in both directions * Configurable table "window" (viewing area) height with infinite vertical @@ -96,6 +94,18 @@ to various download types see the downloadFile module help or vignette. * Since the function or reactive expression is called at the time the user requests the data it is recommended that reactive expressions are used to provide dynamic values from the application to create the table. +**Reactive Return Value** + +* The server function returns a reactive expression containing named list with two elements: + * **selected_rows**: data.frame of current selected rows. + ``` + * Note that this is the data, not references, rownumbers, etc from the table -- it is the actual, visible, table row data. This allows the developer to use this more easily to update another table, chart, etc. as desired. + ``` + * **table_state**: a list of current rendered table state values. The list keys are ("page", "pageSize", "pages", "sorted" and "selected") + + +* It is acceptable to ignore the return value as well if this functionality is not needed. Simply do not assign the result to a variable. + ## Sample Application # Additional Resources From 2f6290c6c49461be98651dc4728be925f7507457 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 7 Aug 2025 08:23:42 -0700 Subject: [PATCH 060/139] - Added customization and sample app sections --- vignettes/downloadableReactTable-module.Rmd | 42 +++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/vignettes/downloadableReactTable-module.Rmd b/vignettes/downloadableReactTable-module.Rmd index 159369c3..47bb6d9f 100644 --- a/vignettes/downloadableReactTable-module.Rmd +++ b/vignettes/downloadableReactTable-module.Rmd @@ -106,7 +106,49 @@ recommended that reactive expressions are used to provide dynamic values from th * It is acceptable to ignore the return value as well if this functionality is not needed. Simply do not assign the result to a variable. +**Customization Options** + +*downloadableReactTable* module can be customized using reacatable function arguments(see `?reactable::reactable`). These options can be sent as a named options via the server function, see example below. + +```{r, eval = F} +# Inside server_local.R + library(shiny) + library(periscope2) + library(reactable) + +table_state <- downloadableReactTable( + id = "object_id1", + table_data = reactiveVal(iris), + download_data_fxns = list(csv = reactiveVal(iris), tsv = reactiveVal(iris)), + selection_mode = "multiple", + pre_selected_rows = function() {c(1, 3, 5)}, + table_options = list(columns = list( + Sepal.Length = colDef(name = "Sepal Length"), + Sepal.Width = colDef(filterable = TRUE), + Petal.Length = colDef(show = FALSE), + Petal.Width = colDef(defaultSortOrder = "desc")), + showSortable = TRUE, + theme = reactableTheme( + borderColor = "#dfe2e5", + stripedColor = "#f6f8fa", + highlightColor = "#f0f5f9", + cellPadding = "8px 12px"))) + + observeEvent(table_state(), { print(table_state()) }) + +# NOTE: table_state is the reactive return value, captured for later use +``` + ## Sample Application +For a complete running shiny example application using the downloadableReactTable module you can create and run a *periscope2* sample application using: + +```{r, eval=F} +library(periscope2) + +app_dir = tempdir() +create_new_application(name = 'mysampleapp', location = app_dir, sample_app = TRUE) +runApp(paste(app_dir, 'mysampleapp', sep = .Platform$file.sep)) +``` # Additional Resources From ad3480a0b639deac7f0df41bd13d783c73e53853 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 7 Aug 2025 08:38:42 -0700 Subject: [PATCH 061/139] - Added new vignettes document reference to other documents --- vignettes/announcement-module.Rmd | 1 + vignettes/announcement_addin.Rmd | 1 + vignettes/applicationReset-module.Rmd | 1 + vignettes/downloadFile-module.Rmd | 1 + vignettes/downloadablePlot-module.Rmd | 1 + vignettes/downloadableTable-module.Rmd | 1 + vignettes/logViewer-module.Rmd | 1 + vignettes/new-application.Rmd | 1 + vignettes/themeBuilder_addin.Rmd | 1 + 9 files changed, 9 insertions(+) diff --git a/vignettes/announcement-module.Rmd b/vignettes/announcement-module.Rmd index fdc59c16..5462f25a 100644 --- a/vignettes/announcement-module.Rmd +++ b/vignettes/announcement-module.Rmd @@ -96,3 +96,4 @@ runApp(paste(app_dir, 'mysampleapp', sep = .Platform$file.sep)) * [logViewer Module](logViewer-module.html) * [applicationReset Module](applicationReset-module.html) * [Theme Configuration Builder](themeBuilder_addin.html) +* [downloadableReactTable Module](downloadableReactTable-module.html) diff --git a/vignettes/announcement_addin.Rmd b/vignettes/announcement_addin.Rmd index b483bb9d..00fd9067 100644 --- a/vignettes/announcement_addin.Rmd +++ b/vignettes/announcement_addin.Rmd @@ -144,3 +144,4 @@ For detailed usage for that file refer to [announcement Module](announcement-mod * [logViewer Module](logViewer-module.html) * [applicationReset Module](applicationReset-module.html) * [Theme Configuration Builder](themeBuilder_addin.html) +* [downloadableReactTable Module](downloadableReactTable-module.html) diff --git a/vignettes/applicationReset-module.Rmd b/vignettes/applicationReset-module.Rmd index 41973313..4d26d750 100644 --- a/vignettes/applicationReset-module.Rmd +++ b/vignettes/applicationReset-module.Rmd @@ -117,3 +117,4 @@ runApp(paste(app_dir, 'mysampleapp', sep = .Platform$file.sep)) * [announcement Module](announcement-module.html) * [Announcement Configuration Builder](announcement_addin.html) * [Theme Configuration Builder](themeBuilder_addin.html) +* [downloadableReactTable Module](downloadableReactTable-module.html) diff --git a/vignettes/downloadFile-module.Rmd b/vignettes/downloadFile-module.Rmd index 7efe4b05..81d32475 100644 --- a/vignettes/downloadFile-module.Rmd +++ b/vignettes/downloadFile-module.Rmd @@ -157,3 +157,4 @@ runApp(paste(app_dir, 'mysampleapp', sep = .Platform$file.sep)) * [announcement Module](announcement-module.html) * [Announcement Configuration Builder](announcement_addin.html) * [Theme Configuration Builder](themeBuilder_addin.html) +* [downloadableReactTable Module](downloadableReactTable-module.html) diff --git a/vignettes/downloadablePlot-module.Rmd b/vignettes/downloadablePlot-module.Rmd index af4799b1..5652b334 100644 --- a/vignettes/downloadablePlot-module.Rmd +++ b/vignettes/downloadablePlot-module.Rmd @@ -151,3 +151,4 @@ downloadablePlot(id = "object_id1", * [announcement Module](announcement-module.html) * [Announcement Configuration Builder](announcement_addin.html) * [Theme Configuration Builder](themeBuilder_addin.html) +* [downloadableReactTable Module](downloadableReactTable-module.html) diff --git a/vignettes/downloadableTable-module.Rmd b/vignettes/downloadableTable-module.Rmd index 094a0233..67e791e7 100644 --- a/vignettes/downloadableTable-module.Rmd +++ b/vignettes/downloadableTable-module.Rmd @@ -201,3 +201,4 @@ runApp(paste(app_dir, 'mysampleapp', sep = .Platform$file.sep)) * [announcement Module](announcement-module.html) * [Announcement Configuration Builder](announcement_addin.html) * [Theme Configuration Builder](themeBuilder_addin.html) +* [downloadableReactTable Module](downloadableReactTable-module.html) diff --git a/vignettes/logViewer-module.Rmd b/vignettes/logViewer-module.Rmd index 6d001ab3..2520ba09 100644 --- a/vignettes/logViewer-module.Rmd +++ b/vignettes/logViewer-module.Rmd @@ -83,3 +83,4 @@ runApp(paste(app_dir, 'mysampleapp', sep = .Platform$file.sep)) * [announcement Module](announcement-module.html) * [Announcement Configuration Builder](announcement_addin.html) * [Theme Configuration Builder](themeBuilder_addin.html) +* [downloadableReactTable Module](downloadableReactTable-module.html) diff --git a/vignettes/new-application.Rmd b/vignettes/new-application.Rmd index a2f5ad51..68b0168d 100644 --- a/vignettes/new-application.Rmd +++ b/vignettes/new-application.Rmd @@ -553,3 +553,4 @@ Application images can be stored here * [announcement Module](announcement-module.html) * [Announcement Configuration Builder](announcement_addin.html) * [Theme Configuration Builder](themeBuilder_addin.html) +* [downloadableReactTable Module](downloadableReactTable-module.html) diff --git a/vignettes/themeBuilder_addin.Rmd b/vignettes/themeBuilder_addin.Rmd index 1648ba00..9e2f8299 100644 --- a/vignettes/themeBuilder_addin.Rmd +++ b/vignettes/themeBuilder_addin.Rmd @@ -112,3 +112,4 @@ The generated file is used by putting it inside the generated app www folder whe * [downloadFile Module](downloadFile-module.html) * [logViewer Module](logViewer-module.html) * [applicationReset Module](applicationReset-module.html) +* [downloadableReactTable Module](downloadableReactTable-module.html) From 8c466d9ba85980c3fca388a6980e11bdec9c93dc Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 7 Aug 2025 09:01:29 -0700 Subject: [PATCH 062/139] - Updated NEWS file --- NEWS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS.md b/NEWS.md index 20e26f7c..a376a975 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,7 +1,11 @@ # periscope2 0.3.0.9004 +## New Features +- Added new module *?downloadableReactTable* based on reactable package and downloadFile module + ## Enhancements - Adding openxlsx2 support while keeping openxlsx to support legacy apps and backwards compatibility. writexl package is used as last resort. + ----- # periscope2 0.2.4 From 1947c863aec2602a0ea384a993e904cb30b0666f Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 7 Aug 2025 09:06:04 -0700 Subject: [PATCH 063/139] - Updated Readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 197f2267..e334e3d3 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ output: * Predefined but flexible templates for new Shiny applications with a default [bs4Dash](https://bs4dash.rinterface.com/) layout * Separation by file of functionality that exists in one of the three shiny scopes: global, server-global, and server-local * Generated applications are organized in an easy to follow and maintain folder structure based on files functionality -* Off-the-shelf and ready to be used modules ('Table Downloader', 'Plot Downloader', 'File Downloader' and 'Reset Application' +* Off-the-shelf and ready to be used modules ('Table/React Table Downloader', 'Plot Downloader', 'File Downloader' and 'Reset Application' * Different methods and tools to alert users and add useful information about application UI and server operations * Application logger with different levels and a UI tool to display and review recorded application logs * Application look and feel can be customized easily via 'www/periscope_style.yaml' or more advanced via 'www/css/custom.css' From d4fcb1a44ed4cc96648e37278ed5c0e6ae097107 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 7 Aug 2025 09:08:29 -0700 Subject: [PATCH 064/139] - Fixed typo --- vignettes/downloadableReactTable-module.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/downloadableReactTable-module.Rmd b/vignettes/downloadableReactTable-module.Rmd index 47bb6d9f..910bfaee 100644 --- a/vignettes/downloadableReactTable-module.Rmd +++ b/vignettes/downloadableReactTable-module.Rmd @@ -108,7 +108,7 @@ recommended that reactive expressions are used to provide dynamic values from th **Customization Options** -*downloadableReactTable* module can be customized using reacatable function arguments(see `?reactable::reactable`). These options can be sent as a named options via the server function, see example below. +*downloadableReactTable* module can be customized using reactable function arguments(see `?reactable::reactable`). These options can be sent as a named options via the server function, see example below. ```{r, eval = F} # Inside server_local.R From dd6ff28f25b8fa3449c2005ba5743631fff314fa Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 7 Aug 2025 20:33:25 -0700 Subject: [PATCH 065/139] - Converted the loggerViewer to downloadabableReactTable --- R/logViewer.R | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/R/logViewer.R b/R/logViewer.R index 96e4910d..4c987eed 100644 --- a/R/logViewer.R +++ b/R/logViewer.R @@ -44,7 +44,9 @@ #' @seealso \link[periscope2]{downloadableTable} logViewerOutput <- function(id = "logViewer") { ns <- shiny::NS(id) - shiny::tableOutput(ns(id)) + downloadableReactTableUI(id = ns(id), + downloadtypes = c("csv", "tsv"), + hovertext = "Download application logs") } @@ -68,8 +70,10 @@ logViewer <- function(id = "logViewer", logger) { shiny::moduleServer( id, function(input, output, session) { - output[[id]] <- shiny::renderTable({ - lines <- logger() + get_log_data <- function() { + log_data <- data.frame() + lines <- logger() + if (length(lines) > 0) { out1 <- data.frame(orig = lines, stringsAsFactors = F) loc1 <- regexpr("\\[", out1$orig) @@ -83,10 +87,15 @@ logViewer <- function(id = "logViewer", logger) { out1$action <- substring(out1$orig, loc2 + 1) out1$action <- trimws(out1$action, "both") - data.frame(action = out1$action, - time = format(out1$timestamp, - format = .g_opts$datetime.fmt)) + log_data <- data.frame(action = out1$action, + time = format(out1$timestamp, + format = .g_opts$datetime.fmt)) } - }) + log_data + } + + downloadableReactTable(id = id, + table_data = get_log_data, + download_data_fxns = list(csv = get_log_data, tsv = get_log_data)) }) } From 76c1ecb91a7739773c7b5c4409c82fb26ec56d55 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 7 Aug 2025 20:53:14 -0700 Subject: [PATCH 066/139] - Added file name root --- R/logViewer.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/logViewer.R b/R/logViewer.R index 4c987eed..07eac973 100644 --- a/R/logViewer.R +++ b/R/logViewer.R @@ -96,6 +96,7 @@ logViewer <- function(id = "logViewer", logger) { downloadableReactTable(id = id, table_data = get_log_data, - download_data_fxns = list(csv = get_log_data, tsv = get_log_data)) + download_data_fxns = list(csv = get_log_data, tsv = get_log_data), + file_name_root = "log_data") }) } From 0fe35c3b8cca580d7fdb7b2e2e2d4bd304818cec Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 7 Aug 2025 21:07:46 -0700 Subject: [PATCH 067/139] - Updated logViewer UI unit tests --- tests/testthat/_snaps/log_viewer.md | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/tests/testthat/_snaps/log_viewer.md b/tests/testthat/_snaps/log_viewer.md index 688cb4bb..d7bc6408 100644 --- a/tests/testthat/_snaps/log_viewer.md +++ b/tests/testthat/_snaps/log_viewer.md @@ -1,8 +1,25 @@ # logViewerOutput -
- -# logViewer - valid sample log - - [1] "\n\n \n \n \n
action time
Be Sure to Remember to Log ALL user actions 02-19-2022 14:03
Sample Title (click for an info pop-up) started with log level <DEBUG> 02-19-2022 14:03
Application Reset requested by user. Resetting in 5 seconds 02-19-2022 14:04
" + [[1]] +
+ + + + + + +
+ + [[2]] +
+ From dec496cdae5f0021324295e7df58b55cb21cdb25 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 7 Aug 2025 21:23:42 -0700 Subject: [PATCH 068/139] - Updated valid sample log unit tests --- tests/testthat/_snaps/log_viewer.md | 4 ++++ tests/testthat/test_log_viewer.R | 34 ++++++++++++++--------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/tests/testthat/_snaps/log_viewer.md b/tests/testthat/_snaps/log_viewer.md index d7bc6408..99c835d9 100644 --- a/tests/testthat/_snaps/log_viewer.md +++ b/tests/testthat/_snaps/log_viewer.md @@ -23,3 +23,7 @@
+# logViewer - valid sample log + + {"x":{"tag":{"name":"Reactable","attribs":{"data":{"action":["Be Sure to Remember to Log ALL user actions","Sample Title (click for an info pop-up) started with log level ","Application Reset requested by user. Resetting in 5 seconds"],"time":["02-19-2022 14:03","02-19-2022 14:03","02-19-2022 14:04"]},"columns":[{"id":"action","name":"action","type":"character"},{"id":"time","name":"time","type":"character"}],"searchable":true,"pagination":false,"highlight":true,"striped":true,"height":"600px","dataKey":"b624cab69fef11e8d7efb72689e14787","static":false},"children":[]},"class":"reactR_markup"},"evals":[],"jsHooks":[],"deps":[]} + diff --git a/tests/testthat/test_log_viewer.R b/tests/testthat/test_log_viewer.R index 86036c61..5f35423b 100644 --- a/tests/testthat/test_log_viewer.R +++ b/tests/testthat/test_log_viewer.R @@ -26,24 +26,24 @@ test_that("logViewer - valid sample log", { testServer(logViewer, args = list(id = "myid", logger = sample_log), expr = { - expect_snapshot_output(output$myid) + expect_snapshot_output(output$"myid-reactTableOutputID") }) }) -test_that("logViewer - null sample log", { - testServer(logViewer, - args = list(id = "nullLogger", logger = null_log), - expr = { - expect_null(output$nullLogger) - }) -}) - - -test_that("logViewer - empty sample log", { - testServer(logViewer, - args = list(id = "emptyLogger", logger = empty_log), - expr = { - expect_null(output$emptyLogger) - }) -}) +# test_that("logViewer - null sample log", { +# testServer(logViewer, +# args = list(id = "nullLogger", logger = null_log), +# expr = { +# expect_null(output$nullLogger) +# }) +# }) +# +# +# test_that("logViewer - empty sample log", { +# testServer(logViewer, +# args = list(id = "emptyLogger", logger = empty_log), +# expr = { +# expect_null(output$emptyLogger) +# }) +# }) From c93c72e3203ba9b5c82a053c56393c460e34422e Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 7 Aug 2025 21:37:36 -0700 Subject: [PATCH 069/139] - Updated null sample log unit tests --- tests/testthat/test_log_viewer.R | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/testthat/test_log_viewer.R b/tests/testthat/test_log_viewer.R index 5f35423b..0527ffba 100644 --- a/tests/testthat/test_log_viewer.R +++ b/tests/testthat/test_log_viewer.R @@ -7,12 +7,12 @@ sample_log <- function(){ "actions [2022-02-19 14:04:32] Application Reset requested by user. Resetting in 5 seconds") } -null_log <- function(){ +null_log <- function() { NULL } -empty_log <- function(){ - NULL +empty_log <- function() { + c() } # UI unit tests @@ -31,15 +31,15 @@ test_that("logViewer - valid sample log", { }) -# test_that("logViewer - null sample log", { -# testServer(logViewer, -# args = list(id = "nullLogger", logger = null_log), -# expr = { -# expect_null(output$nullLogger) -# }) -# }) -# -# +test_that("logViewer - null log", { + testServer(logViewer, + args = list(id = "nullLogger", logger = null_log), + expr = { + expect_true(grepl('"x":null', output$"nullLogger-reactTableOutputID", fixed = TRUE)) + }) +}) + + # test_that("logViewer - empty sample log", { # testServer(logViewer, # args = list(id = "emptyLogger", logger = empty_log), From 068033ab8566e5ebbffa72c0b9b207c4e1c4c75c Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 7 Aug 2025 21:59:40 -0700 Subject: [PATCH 070/139] - Removed unneeded unit tests --- tests/testthat/test_log_viewer.R | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tests/testthat/test_log_viewer.R b/tests/testthat/test_log_viewer.R index 0527ffba..66e8c4cd 100644 --- a/tests/testthat/test_log_viewer.R +++ b/tests/testthat/test_log_viewer.R @@ -11,10 +11,6 @@ null_log <- function() { NULL } -empty_log <- function() { - c() -} - # UI unit tests test_that("logViewerOutput", { expect_snapshot_output(logViewerOutput("myid")) @@ -38,12 +34,3 @@ test_that("logViewer - null log", { expect_true(grepl('"x":null', output$"nullLogger-reactTableOutputID", fixed = TRUE)) }) }) - - -# test_that("logViewer - empty sample log", { -# testServer(logViewer, -# args = list(id = "emptyLogger", logger = empty_log), -# expr = { -# expect_null(output$emptyLogger) -# }) -# }) From d5014564e21ba61c9f0f2ba8ffc01c9102e8b254 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 7 Aug 2025 22:25:22 -0700 Subject: [PATCH 071/139] - Updated logViewer module documenation --- R/logViewer.R | 9 +++++---- man/logViewerOutput.Rd | 7 ++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/R/logViewer.R b/R/logViewer.R index 07eac973..7324d79e 100644 --- a/R/logViewer.R +++ b/R/logViewer.R @@ -5,13 +5,14 @@ #' Display app logs #' -#' Creates a shiny table with table containing logged user actions. Table contents are auto updated whenever a user action is -#' logged. The id must match the same id configured in \bold{server.R} file upon calling \code{fw_server_setup} method +#' Display app log data in downloadableReactTable table containing logged user actions. Table contents are auto updated +#' whenever a user action is logged. User can search for logs, sort them by time and download them in CSV or TSV format. +#' The id must match the same id configured in \bold{server.R} file upon calling \code{fw_server_setup} method #' #' #' @param id character id for the object(default = "logViewer") #' -#' @return shiny tableOutput instance +#' @return downloadableReactTableUI instance #' #' @section Table columns: #' \itemize{ @@ -59,7 +60,7 @@ logViewerOutput <- function(id = "logViewer") { #' @param id - the ID of the Module's UI element #' @param logger - action logs to be displayed #' -#' @return Shiny table render expression containing the currently logged lines +#' @return downloadableReactTable instance with logged lines #' #' #' @section Shiny Usage: diff --git a/man/logViewerOutput.Rd b/man/logViewerOutput.Rd index 1998e6d0..95aa5c8a 100644 --- a/man/logViewerOutput.Rd +++ b/man/logViewerOutput.Rd @@ -10,11 +10,12 @@ logViewerOutput(id = "logViewer") \item{id}{character id for the object(default = "logViewer")} } \value{ -shiny tableOutput instance +downloadableReactTableUI instance } \description{ -Creates a shiny table with table containing logged user actions. Table contents are auto updated whenever a user action is -logged. The id must match the same id configured in \bold{server.R} file upon calling \code{fw_server_setup} method +Display app log data in downloadableReactTable table containing logged user actions. Table contents are auto updated +whenever a user action is logged. User can search for logs, sort them by time and download them in CSV or TSV format. +The id must match the same id configured in \bold{server.R} file upon calling \code{fw_server_setup} method } \section{Table columns}{ From 26efc36c358b9f7773d4669dbc0dc30d9a0bbaa0 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Thu, 7 Aug 2025 22:27:10 -0700 Subject: [PATCH 072/139] - Updated NEWS file --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index a376a975..1dd472f3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,7 @@ ## Enhancements - Adding openxlsx2 support while keeping openxlsx to support legacy apps and backwards compatibility. writexl package is used as last resort. +- Updated *?logViewerOutput* module to display log data in downloadableReactTable. ----- From caea1e31c457918dbacbc9d9757c1b20769216c7 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Fri, 8 Aug 2025 00:15:21 -0700 Subject: [PATCH 073/139] - Removed unused js custom messages --- R/downloadablePlot.R | 5 ----- R/downloadableTable.R | 9 --------- 2 files changed, 14 deletions(-) diff --git a/R/downloadablePlot.R b/R/downloadablePlot.R index 5782527f..f8903ec6 100644 --- a/R/downloadablePlot.R +++ b/R/downloadablePlot.R @@ -261,11 +261,6 @@ downloadablePlot <- function(id, shiny::observe({ if (length(downloadfxns) > 0) { dpInfo$downloadfxns <- lapply(downloadfxns, do.call, list()) - rowct <- lapply(dpInfo$downloadfxns, is.null) - session$sendCustomMessage( - "downloadbutton_toggle", - message = list(btn = session$ns("dplotButtonDiv"), - rows = sum(unlist(rowct) == FALSE)) ) } output$displayButton <- shiny::reactive(length(downloadfxns) > 0) diff --git a/R/downloadableTable.R b/R/downloadableTable.R index 923291ca..f72c0634 100644 --- a/R/downloadableTable.R +++ b/R/downloadableTable.R @@ -237,10 +237,6 @@ downloadableTable <- function(id, downloadFile("dtableButtonID", logger, filenameroot, downloaddatafxns) - session$sendCustomMessage("downloadbutton_toggle", - message = list(btn = session$ns("dtableButtonDiv"), - rows = -1)) - dtInfo <- shiny::reactiveValues(selection = NULL, selected = NULL, tabledata = NULL, @@ -271,11 +267,6 @@ downloadableTable <- function(id, shiny::observe({ if (length(downloaddatafxns) > 0) { dtInfo$downloaddatafxns <- lapply(downloaddatafxns, do.call, list()) - - rowct <- lapply(dtInfo$downloaddatafxns, NROW) - session$sendCustomMessage("downloadbutton_toggle", - message = list(btn = session$ns("dtableButtonDiv"), - rows = sum(unlist(rowct)))) } output$displayButton <- shiny::reactive(length(downloaddatafxns) > 0) shiny::outputOptions(output, "displayButton", suspendWhenHidden = FALSE) From 68336fe307d9ab6bc024e07209486061bdf65df6 Mon Sep 17 00:00:00 2001 From: Mennahtullah Mabrouk Date: Fri, 8 Aug 2025 03:57:56 -0700 Subject: [PATCH 074/139] Update logging levels test cases --- tests/testthat/test_logger.R | 158 +++++++++++++++++++++-------------- 1 file changed, 96 insertions(+), 62 deletions(-) diff --git a/tests/testthat/test_logger.R b/tests/testthat/test_logger.R index e0641cdb..18076560 100644 --- a/tests/testthat/test_logger.R +++ b/tests/testthat/test_logger.R @@ -1,10 +1,12 @@ context("periscope2 - logging functionality") -writeToConsole <- periscope2:::writeToConsole -writeToFile <- periscope2:::writeToFile -loglevels <- periscope2:::loglevels -test_file_name <- file.path(tempdir(), c("1", "2", "3")) +writeToConsole <- periscope2:::writeToConsole +writeToFile <- periscope2:::writeToFile +loglevels <- periscope2:::loglevels +set_app_parameters <- periscope2::set_app_parameters +test_file_name <- file.path(tempdir(), c("1", "2", "3")) +test_file_log <- file.path(tempdir(), "test_log.txt") env_setup <- function() { test_env <- new.env(parent = emptyenv()) @@ -208,75 +210,107 @@ test_that("MsgComposer function - defaultMsgCompose()",{ paste(rep(LETTERS, 316), collapse = "")) }) -# Testing log_levels -test_that("writeToConsole DEBUG level", { - periscope2::set_app_parameters(log_level = "DEBUG") - expect_output(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) - expect_output(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) - expect_output(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) - expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) +# Testing logging levels +test_that("DEBUG level shows all messages", { + set_app_parameters(log_level = "DEBUG") + addHandler(writeToConsole) + expect_output(logdebug("debug message"), "DEBUG::debug message") + expect_output(loginfo("info message"), "INFO::info message") + expect_output(logwarn("warn message"), "WARNING::warn message") + expect_output(logerror("error message"), "ERROR::error message") }) - -test_that("writeToConsole INFO level", { - periscope2::set_app_parameters(log_level = "INFO") - expect_silent(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) - expect_output(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) - expect_output(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) - expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) +I +test_that("INFO level filters DEBUG", { + set_app_parameters(log_level = "INFO") + addHandler(writeToConsole) + expect_silent(logdebug("debug message")) + expect_output(loginfo("info message"), "INFO::info message") + expect_output(logwarn("warn message"), "WARNING::warn message") + expect_output(logerror("error message"), "ERROR::error message") }) -test_that("writeToConsole WARN level", { - periscope2::set_app_parameters(log_level = "WARN") - expect_silent(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) - expect_silent(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) - expect_output(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) - expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) +test_that("WARN level filters DEBUG and INFO", { + set_app_parameters(log_level = "WARN") + addHandler(writeToConsole) + expect_silent(logdebug("debug message")) + expect_silent(loginfo("info message")) + expect_output(logwarn("warn message"), "WARNING::warn message") + expect_output(logerror("error message"), "ERROR::error message") }) -test_that("writeToConsole ERROR level", { - periscope2::set_app_parameters(log_level = "ERROR") - expect_silent(writeToConsole("debug", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "DEBUG"))) - expect_silent(writeToConsole("info", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "INFO"))) - expect_silent(writeToConsole("warn", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "WARN"))) - expect_output(writeToConsole("error", list(color_output = FALSE, color_msg = function(msg, level_name) msg), list(levelname = "ERROR"))) +test_that("ERROR level filters all but ERROR", { + set_app_parameters(log_level = "ERROR") + addHandler(writeToConsole) + expect_silent(logdebug("debug message")) + expect_silent(loginfo("info message")) + expect_silent(logwarn("warn message")) + expect_output(logerror("error message"), "ERROR::error message") }) -test_that("writeToFile DEBUG level", { - unlink(test_file_name, force = TRUE) - periscope2::set_app_parameters(log_level = "DEBUG") - writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) - writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) - writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) - writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) - expect_equal(readLines(test_file_name[[1]]), c("debug", "info", "warn", "error")) +test_that("File logging level DEBUG", { + unlink(test_file_log, force = TRUE) + set_app_parameters(log_level = "DEBUG") + addHandler(writeToFile, file = test_file_log) + + logdebug("debug message") + loginfo("info message") + logwarn("warn message") + logerror("error message") + + log_content <- readLines(test_file_log) + expect_true(any(grepl("debug message", log_content))) + expect_true(any(grepl("info message", log_content))) + expect_true(any(grepl("warn message", log_content))) + expect_true(any(grepl("error message", log_content))) }) -test_that("writeToFile INFO level", { - unlink(test_file_name, force = TRUE) - periscope2::set_app_parameters(log_level = "INFO") - writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) - writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) - writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) - writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) - expect_equal(readLines(test_file_name[[1]]), c("info", "warn", "error")) +test_that("File logging level INFO", { + unlink(test_file_log, force = TRUE) + set_app_parameters(log_level = "INFO") + addHandler(writeToFile, file = test_file_log) + + logdebug("debug message") + loginfo("info message") + logwarn("warn message") + logerror("error message") + + log_content <- readLines(test_file_log) + expect_false(any(grepl("debug message", log_content))) + expect_true(any(grepl("info message", log_content))) + expect_true(any(grepl("warn message", log_content))) + expect_true(any(grepl("error message", log_content))) }) -test_that("writeToFile WARN level", { - unlink(test_file_name, force = TRUE) - periscope2::set_app_parameters(log_level = "WARN") - writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) - writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) - writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) - writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) - expect_equal(readLines(test_file_name[[1]]), c("warn", "error")) +test_that("File logging level WARN", { + unlink(test_file_log, force = TRUE) + set_app_parameters(log_level = "WARN") + addHandler(writeToFile, file = test_file_log) + + logdebug("debug message") + loginfo("info message") + logwarn("warn message") + logerror("error message") + + log_content <- readLines(test_file_log) + expect_false(any(grepl("debug message", log_content))) + expect_false(any(grepl("info message", log_content))) + expect_true(any(grepl("warn message", log_content))) + expect_true(any(grepl("error message", log_content))) }) -test_that("writeToFile ERROR level", { - unlink(test_file_name, force = TRUE) - periscope2::set_app_parameters(log_level = "ERROR") - writeToFile("debug", list(file = test_file_name[[1]]), list(levelname = "DEBUG")) - writeToFile("info", list(file = test_file_name[[1]]), list(levelname = "INFO")) - writeToFile("warn", list(file = test_file_name[[1]]), list(levelname = "WARN")) - writeToFile("error", list(file = test_file_name[[1]]), list(levelname = "ERROR")) - expect_equal(readLines(test_file_name[[1]]), "error") +test_that("File logging level ERROR", { + unlink(test_file_log, force = TRUE) + set_app_parameters(log_level = "ERROR") + addHandler(writeToFile, file = test_file_log) + + logdebug("debug message") + loginfo("info message") + logwarn("warn message") + logerror("error message") + + log_content <- readLines(test_file_log) + expect_false(any(grepl("debug message", log_content))) + expect_false(any(grepl("info message", log_content))) + expect_false(any(grepl("warn message", log_content))) + expect_true(any(grepl("error message", log_content))) }) From aa78c68ccedde7cd1249ca93dc20a25c3cc50f39 Mon Sep 17 00:00:00 2001 From: Mennahtullah Mabrouk Date: Fri, 8 Aug 2025 05:30:07 -0700 Subject: [PATCH 075/139] Remove duplicate logging_level --- R/logger.R | 9 --------- 1 file changed, 9 deletions(-) diff --git a/R/logger.R b/R/logger.R index d5de1c23..bf8fa2f6 100644 --- a/R/logger.R +++ b/R/logger.R @@ -678,15 +678,6 @@ logging_level <- function(level_name, current_log_level) { ## to color messages. The coloring can be switched off by means of configuring ## the handler with \var{color_output} option set to FALSE. ## -logging_level <- function(level_name, current_log_level) { - switch(current_log_level, - "INFO" = level_name %in% c("INFO", "WARN", "ERROR"), - "WARN" = level_name %in% c("WARN", "ERROR"), - "ERROR" = level_name == "ERROR", - TRUE - ) -} - writeToConsole <- function(msg, handler, ...) { if (length(list(...)) && "dry" %in% names(list(...))) { if (!is.null(handler$color_output) && handler$color_output == FALSE) { From ea4a11560ec4654802f26ee12c970bb46fbbfbfe Mon Sep 17 00:00:00 2001 From: Mennahtullah Mabrouk Date: Fri, 8 Aug 2025 05:44:00 -0700 Subject: [PATCH 076/139] Fixing WARN issue --- R/logger.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/R/logger.R b/R/logger.R index bf8fa2f6..301aeda5 100644 --- a/R/logger.R +++ b/R/logger.R @@ -626,9 +626,9 @@ updateOptions.Logger <- function(container, ...) { ## logging_level <- function(level_name, current_log_level) { switch(current_log_level, - "INFO" = level_name %in% c("INFO", "WARN", "ERROR"), - "WARN" = level_name %in% c("WARN", "ERROR"), - "ERROR" = level_name == "ERROR", + "INFO" = level_name %in% c("INFO", "WARNING", "ERROR"), + "WARNING" = level_name %in% c("WARNING", "ERROR"), + "ERROR" = level_name == "ERROR", TRUE ) } From c3310eb70b2689ad56457c0ebe9c050fb245653e Mon Sep 17 00:00:00 2001 From: Mennahtullah Mabrouk Date: Fri, 8 Aug 2025 05:49:49 -0700 Subject: [PATCH 077/139] Update WARN according to fix --- tests/testthat/test_logger.R | 12 ++++++------ vignettes/logViewer-module.Rmd | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/testthat/test_logger.R b/tests/testthat/test_logger.R index 3bbe8815..a35909bb 100644 --- a/tests/testthat/test_logger.R +++ b/tests/testthat/test_logger.R @@ -112,8 +112,8 @@ test_that("UpdateOptions - updateOptions()", { periscope2:::logReset() periscope2:::basicConfig() - periscope2:::updateOptions.character("", level = "WARN") - expect_equal(periscope2:::getLogger()$getLevel(), loglevels["WARN"]) + periscope2:::updateOptions.character("", level = "WARNING") + expect_equal(periscope2:::getLogger()$getLevel(), loglevels["WARNING"]) }) test_that("LoggingToConsole", { @@ -231,8 +231,8 @@ test_that("INFO level filters DEBUG", { expect_output(logerror("error message"), "ERROR::error message") }) -test_that("WARN level filters DEBUG and INFO", { - set_app_parameters(log_level = "WARN") +test_that("WARNING level filters DEBUG and INFO", { + set_app_parameters(log_level = "WARNING") addHandler(writeToConsole) expect_silent(logdebug("debug message")) expect_silent(loginfo("info message")) @@ -283,9 +283,9 @@ test_that("File logging level INFO", { expect_true(any(grepl("error message", log_content))) }) -test_that("File logging level WARN", { +test_that("File logging level WARNING", { unlink(test_file_log, force = TRUE) - set_app_parameters(log_level = "WARN") + set_app_parameters(log_level = "WARNING") addHandler(writeToFile, file = test_file_log) logdebug("debug message") diff --git a/vignettes/logViewer-module.Rmd b/vignettes/logViewer-module.Rmd index bc95c9da..95c2c8d2 100644 --- a/vignettes/logViewer-module.Rmd +++ b/vignettes/logViewer-module.Rmd @@ -26,10 +26,10 @@ This *Shiny Module* displays recorded session logs in tabular format * The log files are kept in the /log directory and named 'actions.log'. ONE old copy of the log is kept as 'actions.log.last * Many actions are automatically logged by the framework and it is easy for developers to add additional items as they see fit. * Filtering logs by setting log_level argument as desired in set_app_parameters - * "DEBUG" will log logdebug, loginfo, logwarn, and logerror messages - * "INFO" will log loginfo, logwarn, and logerror messages - * "WARN" will log logwarn and logerror messages - * "ERROR" will log logerror messages + * "DEBUG" will log logdebug, loginfo, logwarn, and logerror messages + * "INFO" will log loginfo, logwarn, and logerror messages + * "WARNING" will log logwarn and logerror messages + * "ERROR" will log logerror messages * It is important to note that the log rolls over for each session and is reset if using the appReset module.
From 52e590af334455e55d1171c50ba6d7b77e6ce30b Mon Sep 17 00:00:00 2001 From: Mennahtullah Mabrouk Date: Fri, 8 Aug 2025 06:05:26 -0700 Subject: [PATCH 078/139] Adding past modifications back --- R/ui_helpers.R | 2 +- inst/fw_templ/p_example/announce.yaml | 8 -------- tests/testthat/test_logger.R | 2 +- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/R/ui_helpers.R b/R/ui_helpers.R index afc2138e..074fe880 100644 --- a/R/ui_helpers.R +++ b/R/ui_helpers.R @@ -606,7 +606,7 @@ ui_tooltip <- function(id, #' @export set_app_parameters <- function(title = NULL, app_info = NULL, - log_level = c("DEBUG", "INFO", "WARN", "ERROR"), + log_level = "DEBUG", app_version = "1.0.0", loading_indicator = NULL, announcements_file = NULL) { diff --git a/inst/fw_templ/p_example/announce.yaml b/inst/fw_templ/p_example/announce.yaml index 2ff6fcc3..35755b1f 100644 --- a/inst/fw_templ/p_example/announce.yaml +++ b/inst/fw_templ/p_example/announce.yaml @@ -51,11 +51,3 @@ title: "Welcome to Periscope2" ### text # The announcement text. Text can contain html tags and is a mandatory value text: "This message will be closed automatically in 30s" - -### log_level -# Controls which log messages are shown in the application: -# - "DEBUG": All messages -# - "INFO" : Info, warnings, and errors -# - "WARN" : Only warnings and errors -# - "ERROR": Only errors -log_level: "DEBUG" diff --git a/tests/testthat/test_logger.R b/tests/testthat/test_logger.R index a35909bb..722bd452 100644 --- a/tests/testthat/test_logger.R +++ b/tests/testthat/test_logger.R @@ -4,7 +4,7 @@ context("periscope2 - logging functionality") writeToConsole <- periscope2:::writeToConsole writeToFile <- periscope2:::writeToFile loglevels <- periscope2:::loglevels -set_app_parameters <- periscope2::set_app_parameters +set_app_parameters <- periscope2:::set_app_parameters test_file_name <- file.path(tempdir(), c("1", "2", "3")) test_file_log <- file.path(tempdir(), "test_log.txt") From 4df8e45dc43b28b55b4e9c79f68d6f9017455ee7 Mon Sep 17 00:00:00 2001 From: Mennahtullah Mabrouk Date: Fri, 8 Aug 2025 06:07:04 -0700 Subject: [PATCH 079/139] Adding missing space --- R/ui_helpers.R | 1 + 1 file changed, 1 insertion(+) diff --git a/R/ui_helpers.R b/R/ui_helpers.R index 074fe880..858989f7 100644 --- a/R/ui_helpers.R +++ b/R/ui_helpers.R @@ -634,6 +634,7 @@ set_app_parameters <- function(title = NULL, .g_opts$announcements_file <- announcements_file } + #' Parse application passed URL parameters #' #' This function returns any url parameters passed to the application as From 754e1237f452c001e49c2c6a0d197f3aefc3f4ff Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Mon, 11 Aug 2025 00:34:07 -0700 Subject: [PATCH 080/139] - Updated package description --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index f1bba881..88c79f22 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: periscope2 Type: Package Title: Enterprise Streamlined 'shiny' Application Framework Using 'bs4Dash' -Version: 0.3.0.9010 +Version: 0.3.0.9011 Authors@R: c( person("Mohammed", "Ali", role = c("aut", "cre"), email = "mohammed@aggregate-genius.com"), person("Constance", "Brett", role = c("ctb")), From 141fd5456ce8ba4b9ff6ed60589b1cf9b4742b8e Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Mon, 11 Aug 2025 01:36:06 -0700 Subject: [PATCH 081/139] - Refactored downloadPlot module and removed unused code --- R/downloadablePlot.R | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/R/downloadablePlot.R b/R/downloadablePlot.R index f8903ec6..32c50932 100644 --- a/R/downloadablePlot.R +++ b/R/downloadablePlot.R @@ -244,13 +244,10 @@ downloadablePlot <- function(id, id, function(input, output, session) { downloadFile("dplotButtonID", logger, filenameroot, downloadfxns, aspectratio) - dpInfo <- shiny::reactiveValues(visibleplot = NULL, - downloadfxns = NULL) shiny::observe({ - dpInfo$visibleplot <- visibleplot() output$dplotOutputID <- shiny::renderPlot({ - plot <- dpInfo$visibleplot + plot <- visibleplot() if (inherits(plot, "grob")) { plot <- grid::grid.draw(plot) } @@ -259,10 +256,6 @@ downloadablePlot <- function(id, }) shiny::observe({ - if (length(downloadfxns) > 0) { - dpInfo$downloadfxns <- lapply(downloadfxns, do.call, list()) - } - output$displayButton <- shiny::reactive(length(downloadfxns) > 0) shiny::outputOptions(output, "displayButton", suspendWhenHidden = FALSE) }) From 648eb47391d9b50e54ca2b515c3e7fb8128ec6b9 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Mon, 11 Aug 2025 02:06:45 -0700 Subject: [PATCH 082/139] - Refactored unused code from downloadableTable module --- R/downloadableTable.R | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/R/downloadableTable.R b/R/downloadableTable.R index f72c0634..04b12ef2 100644 --- a/R/downloadableTable.R +++ b/R/downloadableTable.R @@ -237,10 +237,9 @@ downloadableTable <- function(id, downloadFile("dtableButtonID", logger, filenameroot, downloaddatafxns) - dtInfo <- shiny::reactiveValues(selection = NULL, - selected = NULL, - tabledata = NULL, - downloaddatafxns = NULL) + dtInfo <- shiny::reactiveValues(selection = NULL, + selected = NULL, + tabledata = NULL) shiny::observe({ result <- list(mode = ifelse(input$dtableSingleSelect == "TRUE", "single", "multiple")) @@ -265,9 +264,6 @@ downloadableTable <- function(id, }) shiny::observe({ - if (length(downloaddatafxns) > 0) { - dtInfo$downloaddatafxns <- lapply(downloaddatafxns, do.call, list()) - } output$displayButton <- shiny::reactive(length(downloaddatafxns) > 0) shiny::outputOptions(output, "displayButton", suspendWhenHidden = FALSE) }) From a3b0e8256b5c9648aa6122da5f842aa7a760316e Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Mon, 11 Aug 2025 21:07:54 -0700 Subject: [PATCH 083/139] - Updated module documentation --- R/downloadableReactTable.R | 13 ++++++------- man/downloadableReactTable.Rd | 13 ++++++------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/R/downloadableReactTable.R b/R/downloadableReactTable.R index 954f4045..32a5e9dc 100644 --- a/R/downloadableReactTable.R +++ b/R/downloadableReactTable.R @@ -119,17 +119,16 @@ downloadableReactTableUI <- function(id, #' @param table_data reactive expression (or parameter-less function) that acts as table data source #' @param selection_mode to enable row selection, set \code{selection_mode} value to either "single" for single row #' selection or "multiple" for multiple rows selection, case insensitive. Any other value will -#' disable row selection. An additional column will be added to the table in -#' selection mode with radio buttons for single row selection and checkboxes for -#' "multiple" rows selection mode (default = NULL) +#' disable row selection. Row selection will be enabled by radio buttons in "single" selection +#' and checkboxes in "multiple" selection (default = NULL) #' @param pre_selected_rows reactive expression (or parameter-less function) provides the rows indices of the rows to #' be selected when the table is rendered. If selection_mode is disabled, this parameter will #' have no effect. If selection_mode is "single" only the first row index will be used (default = NULL) -#' @param file_name_root the base text used for user-downloaded file. It can be either a character string +#' @param file_name_root the base name used for user-downloaded file. It can be either a character string #' a reactive expression or a function returning a character string (default = 'data_file') -#' @param download_data_fxns a \strong{named} list of functions providing the data as return values -#' The names for the list should be the same names that were used when the table UI -#' was created (default = NULL) +#' @param download_data_fxns a \strong{named} list of functions providing the data as return values. +#' The names for the list should be the same names as the ones used in the +#' downloadableReactTableUI (default = NULL) #' @param pagination to enable table pagination (default = FALSE) #' @param table_height max table height in pixels. Vertical scroll will be shown after that height value #' @param show_rownames enable displaying rownames as a separate column (default = FALSE) diff --git a/man/downloadableReactTable.Rd b/man/downloadableReactTable.Rd index 9fa29c09..ed048b86 100644 --- a/man/downloadableReactTable.Rd +++ b/man/downloadableReactTable.Rd @@ -29,20 +29,19 @@ downloadableReactTable( \item{selection_mode}{to enable row selection, set \code{selection_mode} value to either "single" for single row selection or "multiple" for multiple rows selection, case insensitive. Any other value will -disable row selection. An additional column will be added to the table in -selection mode with radio buttons for single row selection and checkboxes for -"multiple" rows selection mode (default = NULL)} +disable row selection. Row selection will be enabled by radio buttons in "single" selection +and checkboxes in "multiple" selection (default = NULL)} \item{pre_selected_rows}{reactive expression (or parameter-less function) provides the rows indices of the rows to be selected when the table is rendered. If selection_mode is disabled, this parameter will have no effect. If selection_mode is "single" only the first row index will be used (default = NULL)} -\item{file_name_root}{the base text used for user-downloaded file. It can be either a character string +\item{file_name_root}{the base name used for user-downloaded file. It can be either a character string a reactive expression or a function returning a character string (default = 'data_file')} -\item{download_data_fxns}{a \strong{named} list of functions providing the data as return values -The names for the list should be the same names that were used when the table UI -was created (default = NULL)} +\item{download_data_fxns}{a \strong{named} list of functions providing the data as return values. +The names for the list should be the same names as the ones used in the +downloadableReactTableUI (default = NULL)} \item{pagination}{to enable table pagination (default = FALSE)} From b8dd03cb4325bc188b04894fde36bc41dfc196ce Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Tue, 12 Aug 2025 01:34:28 -0700 Subject: [PATCH 084/139] - Updated downloadableReactTable introduction in example apps --- R/downloadableReactTable.R | 2 +- inst/fw_templ/p_example/ui_body.R | 18 +++++++++--------- .../p_example/ui_body_no_left_sidebar.R | 18 +++++++++--------- man/downloadableReactTable.Rd | 2 +- .../sample_app_both_sidebars/program/ui_body.R | 18 +++++++++--------- .../sample_app_left_sidebar/program/ui_body.R | 18 +++++++++--------- .../program/ui_body.R | 18 +++++++++--------- .../sample_app_right_sidebar/program/ui_body.R | 18 +++++++++--------- vignettes/downloadableReactTable-module.Rmd | 2 +- 9 files changed, 57 insertions(+), 57 deletions(-) diff --git a/R/downloadableReactTable.R b/R/downloadableReactTable.R index 32a5e9dc..2ef76e81 100644 --- a/R/downloadableReactTable.R +++ b/R/downloadableReactTable.R @@ -144,7 +144,7 @@ downloadableReactTableUI <- function(id, #' @return A named list of two elements: #' \itemize{ #' \item selected_rows: data.frame of current selected rows -#' \item table_state: a list of current rendered table state values. The list keys are +#' \item table_state: a list of the current table state. The list keys are #' ("page", "pageSize", "pages", "sorted" and "selected"). #' Review \code{?reactable::getReactableState} for more info. #' } diff --git a/inst/fw_templ/p_example/ui_body.R b/inst/fw_templ/p_example/ui_body.R index e04a61f9..f55f051f 100644 --- a/inst/fw_templ/p_example/ui_body.R +++ b/inst/fw_templ/p_example/ui_body.R @@ -150,10 +150,10 @@ react_table_box <- box( width = 12, fluidRow(column(width = 6, tags$dl(tags$dt("Features"), - tags$ul(tags$li("React table downloader module display tabular data in rich formatted tables using", tags$b("`reactable`"), "package"), - tags$li("downloadabkeReactTable returns two names reactive list that can be used in application different areas", + tags$ul(tags$li("React table downloader module displays tabular data in rich formatted tables using the", tags$b("`reactable`"), "package"), + tags$li("downloadableReactTable returns a named reactive list that can be used in the application:", tags$ul(tags$li(tags$b("selected_rows"), ": data.frame of current selected rows"), - tags$li(tags$b("table_state"), ": a list of current rendered table state values."))), + tags$li(tags$b("table_state"), ": a list of the current table state"))), tags$li("Table data can be downloaded in different formats such as: ", tags$b("'csv'"), ", ", tags$b("'tsv'"), ", ", tags$b("'txt'"), "and/or ", tags$b("'xlsx'")), tags$li("User can customize downloadableReactTable modules using reactable package options."), @@ -164,10 +164,10 @@ react_table_box <- box( column(width = 6, tags$dl(tags$dt("Setup"), tags$li("Module should be configured in both UI and Server code"), - tags$li("In your 'body_ui.R', place module UI part as follow: ", + tags$li("In the 'body_ui.R' add the following lines:", blockQuote("downloadableReactTableUI('exampleReactTable', 'Download react table data'))", color = "info")), - tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + tags$li("In the 'server_local.R' add the following lines (make sure the two use the same ID):", blockQuote("downloadableReactTable('exampleReactTable', ss_userAction.Log, 'exampletable', @@ -209,11 +209,11 @@ table_downloader_box <- box( column(width = 6, tags$dl(tags$dt("Setup"), tags$li("Module should be configured in both UI and Server code"), - tags$li("In your 'body_ui.R', place module UI part as follow: ", + tags$li("In the 'body_ui.R' add the following lines:", blockQuote("downloadableTableUI('exampleDT1', list('csv', 'tsv'), 'Download table data'))", color = "info")), - tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + tags$li("In the 'server_local.R' add the following lines (make sure the two use the same ID):", blockQuote("downloadableTable('exampleDT1', ss_userAction.Log, 'exampletable', @@ -278,9 +278,9 @@ plot_downloader_box <- box( column(width = 6, tags$dl(tags$dt("Setup"), tags$ul(tags$li("Module should be configured in both the UI and Server code"), - tags$li("In your 'body_ui.R', place module UI part as follow: ", + tags$li("In the 'body_ui.R' add the following lines:", blockQuote("downloadablePlotUI('myplotID', c('png', 'csv'), 'Download Plot or Data', '300px'))", color = "info")), - tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + tags$li("In the 'server_local.R' add the following lines (make sure the two use the same ID):", blockQuote("downloadablePlot('myplotID', ss_userAction.Log, filenameroot = 'mydownload1', diff --git a/inst/fw_templ/p_example/ui_body_no_left_sidebar.R b/inst/fw_templ/p_example/ui_body_no_left_sidebar.R index b58e16d3..16bf4579 100644 --- a/inst/fw_templ/p_example/ui_body_no_left_sidebar.R +++ b/inst/fw_templ/p_example/ui_body_no_left_sidebar.R @@ -144,10 +144,10 @@ react_table_box <- box( width = 12, fluidRow(column(width = 6, tags$dl(tags$dt("Features"), - tags$ul(tags$li("React table downloader module display tabular data in rich formatted tables using", tags$b("`reactable`"), "package"), - tags$li("downloadabkeReactTable returns two names reactive list that can be used in application different areas", + tags$ul(tags$li("React table downloader module displays tabular data in rich formatted tables using the", tags$b("`reactable`"), "package"), + tags$li("downloadableReactTable returns a named reactive list that can be used in the application:", tags$ul(tags$li(tags$b("selected_rows"), ": data.frame of current selected rows"), - tags$li(tags$b("table_state"), ": a list of current rendered table state values."))), + tags$li(tags$b("table_state"), ": a list of the current table state"))), tags$li("Table data can be downloaded in different formats such as: ", tags$b("'csv'"), ", ", tags$b("'tsv'"), ", ", tags$b("'txt'"), "and/or ", tags$b("'xlsx'")), tags$li("User can customize downloadableReactTable modules using reactable package options."), @@ -158,10 +158,10 @@ react_table_box <- box( column(width = 6, tags$dl(tags$dt("Setup"), tags$li("Module should be configured in both UI and Server code"), - tags$li("In your 'body_ui.R', place module UI part as follow: ", + tags$li("In the 'body_ui.R' add the following lines:", blockQuote("downloadableReactTableUI('exampleReactTable', 'Download react table data'))", color = "info")), - tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + tags$li("In the 'server_local.R' add the following lines (make sure the two use the same ID):", blockQuote("downloadableReactTable('exampleReactTable', ss_userAction.Log, 'exampletable', @@ -204,11 +204,11 @@ table_downloader_box <- box( column(width = 6, tags$dl(tags$dt("Setup"), tags$li("Module should be configured in both UI and Server code"), - tags$li("In your 'body_ui.R', place module UI part as follow: ", + tags$li("In the 'body_ui.R' add the following lines:", blockQuote("downloadableTableUI('exampleDT1', list('csv', 'tsv'), 'Download table data'))", color = "info")), - tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + tags$li("In the 'server_local.R' add the following lines (make sure the two use the same ID):", blockQuote("downloadableTable('exampleDT1', ss_userAction.Log, 'exampletable', @@ -273,9 +273,9 @@ plot_downloader_box <- box( column(width = 6, tags$dl(tags$dt("Setup"), tags$ul(tags$li("Module should be configured in both the UI and Server code"), - tags$li("In your 'body_ui.R', place module UI part as follow: ", + tags$li("In the 'body_ui.R' add the following lines:", blockQuote("downloadablePlotUI('myplotID', c('png', 'csv'), 'Download Plot or Data', '300px'))", color = "info")), - tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + tags$li("In the 'server_local.R' add the following lines (make sure the two use the same ID):", blockQuote("downloadablePlot('myplotID', ss_userAction.Log, filenameroot = 'mydownload1', diff --git a/man/downloadableReactTable.Rd b/man/downloadableReactTable.Rd index ed048b86..e5314f54 100644 --- a/man/downloadableReactTable.Rd +++ b/man/downloadableReactTable.Rd @@ -67,7 +67,7 @@ Also see example below to see how to pass options (default = list())} A named list of two elements: \itemize{ \item selected_rows: data.frame of current selected rows -\item table_state: a list of current rendered table state values. The list keys are +\item table_state: a list of the current table state. The list keys are ("page", "pageSize", "pages", "sorted" and "selected"). Review \code{?reactable::getReactableState} for more info. } diff --git a/tests/testthat/sample_app_both_sidebars/program/ui_body.R b/tests/testthat/sample_app_both_sidebars/program/ui_body.R index 29f0ce91..07f7a348 100644 --- a/tests/testthat/sample_app_both_sidebars/program/ui_body.R +++ b/tests/testthat/sample_app_both_sidebars/program/ui_body.R @@ -150,10 +150,10 @@ react_table_box <- box( width = 12, fluidRow(column(width = 6, tags$dl(tags$dt("Features"), - tags$ul(tags$li("React table downloader module display tabular data in rich formatted tables using", tags$b("`reactable`"), "package"), - tags$li("downloadabkeReactTable returns two names reactive list that can be used in application different areas", + tags$ul(tags$li("React table downloader module displays tabular data in rich formatted tables using the", tags$b("`reactable`"), "package"), + tags$li("downloadableReactTable returns a named reactive list that can be used in the application:", tags$ul(tags$li(tags$b("selected_rows"), ": data.frame of current selected rows"), - tags$li(tags$b("table_state"), ": a list of current rendered table state values."))), + tags$li(tags$b("table_state"), ": a list of the current table state"))), tags$li("Table data can be downloaded in different formats such as: ", tags$b("'csv'"), ", ", tags$b("'tsv'"), ", ", tags$b("'txt'"), "and/or ", tags$b("'xlsx'")), tags$li("User can customize downloadableReactTable modules using reactable package options."), @@ -164,10 +164,10 @@ react_table_box <- box( column(width = 6, tags$dl(tags$dt("Setup"), tags$li("Module should be configured in both UI and Server code"), - tags$li("In your 'body_ui.R', place module UI part as follow: ", + tags$li("In the 'body_ui.R' add the following lines:", blockQuote("downloadableReactTableUI('exampleReactTable', 'Download react table data'))", color = "info")), - tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + tags$li("In the 'server_local.R' add the following lines (make sure the two use the same ID):", blockQuote("downloadableReactTable('exampleReactTable', ss_userAction.Log, 'exampletable', @@ -209,11 +209,11 @@ table_downloader_box <- box( column(width = 6, tags$dl(tags$dt("Setup"), tags$li("Module should be configured in both UI and Server code"), - tags$li("In your 'body_ui.R', place module UI part as follow: ", + tags$li("In the 'body_ui.R' add the following lines:", blockQuote("downloadableTableUI('exampleDT1', list('csv', 'tsv'), 'Download table data'))", color = "info")), - tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + tags$li("In the 'server_local.R' add the following lines (make sure the two use the same ID):", blockQuote("downloadableTable('exampleDT1', ss_userAction.Log, 'exampletable', @@ -278,9 +278,9 @@ plot_downloader_box <- box( column(width = 6, tags$dl(tags$dt("Setup"), tags$ul(tags$li("Module should be configured in both the UI and Server code"), - tags$li("In your 'body_ui.R', place module UI part as follow: ", + tags$li("In the 'body_ui.R' add the following lines:", blockQuote("downloadablePlotUI('myplotID', c('png', 'csv'), 'Download Plot or Data', '300px'))", color = "info")), - tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + tags$li("In the 'server_local.R' add the following lines (make sure the two use the same ID):", blockQuote("downloadablePlot('myplotID', ss_userAction.Log, filenameroot = 'mydownload1', diff --git a/tests/testthat/sample_app_left_sidebar/program/ui_body.R b/tests/testthat/sample_app_left_sidebar/program/ui_body.R index 74e3159d..62619516 100644 --- a/tests/testthat/sample_app_left_sidebar/program/ui_body.R +++ b/tests/testthat/sample_app_left_sidebar/program/ui_body.R @@ -168,11 +168,11 @@ table_downloader_box <- box( column(width = 6, tags$dl(tags$dt("Setup"), tags$li("Module should be configured in both UI and Server code"), - tags$li("In your 'body_ui.R', place module UI part as follow: ", + tags$li("In the 'body_ui.R' add the following lines:", blockQuote("downloadableTableUI('exampleDT1', list('csv', 'tsv'), 'Download table data'))", color = "info")), - tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + tags$li("In the 'server_local.R' add the following lines (make sure the two use the same ID):", blockQuote("downloadableTable('exampleDT1', ss_userAction.Log, 'exampletable', @@ -196,10 +196,10 @@ react_table_box <- box( width = 12, fluidRow(column(width = 6, tags$dl(tags$dt("Features"), - tags$ul(tags$li("React table downloader module display tabular data in rich formatted tables using", tags$b("`reactable`"), "package"), - tags$li("downloadabkeReactTable returns two names reactive list that can be used in application different areas", + tags$ul(tags$li("React table downloader module displays tabular data in rich formatted tables using the", tags$b("`reactable`"), "package"), + tags$li("downloadableReactTable returns a named reactive list that can be used in the application:", tags$ul(tags$li(tags$b("selected_rows"), ": data.frame of current selected rows"), - tags$li(tags$b("table_state"), ": a list of current rendered table state values."))), + tags$li(tags$b("table_state"), ": a list of the current table state"))), tags$li("Table data can be downloaded in different formats such as: ", tags$b("'csv'"), ", ", tags$b("'tsv'"), ", ", tags$b("'txt'"), "and/or ", tags$b("'xlsx'")), tags$li("User can customize downloadableReactTable modules using reactable package options."), @@ -210,10 +210,10 @@ react_table_box <- box( column(width = 6, tags$dl(tags$dt("Setup"), tags$li("Module should be configured in both UI and Server code"), - tags$li("In your 'body_ui.R', place module UI part as follow: ", + tags$li("In the 'body_ui.R' add the following lines:", blockQuote("downloadableReactTableUI('exampleReactTable', 'Download react table data'))", color = "info")), - tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + tags$li("In the 'server_local.R' add the following lines (make sure the two use the same ID):", blockQuote("downloadableReactTable('exampleReactTable', ss_userAction.Log, 'exampletable', @@ -278,9 +278,9 @@ plot_downloader_box <- box( column(width = 6, tags$dl(tags$dt("Setup"), tags$ul(tags$li("Module should be configured in both the UI and Server code"), - tags$li("In your 'body_ui.R', place module UI part as follow: ", + tags$li("In the 'body_ui.R' add the following lines:", blockQuote("downloadablePlotUI('myplotID', c('png', 'csv'), 'Download Plot or Data', '300px'))", color = "info")), - tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + tags$li("In the 'server_local.R' add the following lines (make sure the two use the same ID):", blockQuote("downloadablePlot('myplotID', ss_userAction.Log, filenameroot = 'mydownload1', diff --git a/tests/testthat/sample_app_no_both_sidebars/program/ui_body.R b/tests/testthat/sample_app_no_both_sidebars/program/ui_body.R index 7ed3021d..2779ba24 100644 --- a/tests/testthat/sample_app_no_both_sidebars/program/ui_body.R +++ b/tests/testthat/sample_app_no_both_sidebars/program/ui_body.R @@ -162,11 +162,11 @@ table_downloader_box <- box( column(width = 6, tags$dl(tags$dt("Setup"), tags$li("Module should be configured in both UI and Server code"), - tags$li("In your 'body_ui.R', place module UI part as follow: ", + tags$li("In the 'body_ui.R' add the following lines:", blockQuote("downloadableTableUI('exampleDT1', list('csv', 'tsv'), 'Download table data'))", color = "info")), - tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + tags$li("In the 'server_local.R' add the following lines (make sure the two use the same ID):", blockQuote("downloadableTable('exampleDT1', ss_userAction.Log, 'exampletable', @@ -190,10 +190,10 @@ react_table_box <- box( width = 12, fluidRow(column(width = 6, tags$dl(tags$dt("Features"), - tags$ul(tags$li("React table downloader module display tabular data in rich formatted tables using", tags$b("`reactable`"), "package"), - tags$li("downloadabkeReactTable returns two names reactive list that can be used in application different areas", + tags$ul(tags$li("React table downloader module displays tabular data in rich formatted tables using the", tags$b("`reactable`"), "package"), + tags$li("downloadableReactTable returns a named reactive list that can be used in the application:", tags$ul(tags$li(tags$b("selected_rows"), ": data.frame of current selected rows"), - tags$li(tags$b("table_state"), ": a list of current rendered table state values."))), + tags$li(tags$b("table_state"), ": a list of the current table state"))), tags$li("Table data can be downloaded in different formats such as: ", tags$b("'csv'"), ", ", tags$b("'tsv'"), ", ", tags$b("'txt'"), "and/or ", tags$b("'xlsx'")), tags$li("User can customize downloadableReactTable modules using reactable package options."), @@ -204,10 +204,10 @@ react_table_box <- box( column(width = 6, tags$dl(tags$dt("Setup"), tags$li("Module should be configured in both UI and Server code"), - tags$li("In your 'body_ui.R', place module UI part as follow: ", + tags$li("In the 'body_ui.R' add the following lines:", blockQuote("downloadableReactTableUI('exampleReactTable', 'Download react table data'))", color = "info")), - tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + tags$li("In the 'server_local.R' add the following lines (make sure the two use the same ID):", blockQuote("downloadableReactTable('exampleReactTable', ss_userAction.Log, 'exampletable', @@ -273,9 +273,9 @@ plot_downloader_box <- box( column(width = 6, tags$dl(tags$dt("Setup"), tags$ul(tags$li("Module should be configured in both the UI and Server code"), - tags$li("In your 'body_ui.R', place module UI part as follow: ", + tags$li("In the 'body_ui.R' add the following lines:", blockQuote("downloadablePlotUI('myplotID', c('png', 'csv'), 'Download Plot or Data', '300px'))", color = "info")), - tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + tags$li("In the 'server_local.R' add the following lines (make sure the two use the same ID):", blockQuote("downloadablePlot('myplotID', ss_userAction.Log, filenameroot = 'mydownload1', diff --git a/tests/testthat/sample_app_right_sidebar/program/ui_body.R b/tests/testthat/sample_app_right_sidebar/program/ui_body.R index 82df836c..ede2f8f7 100644 --- a/tests/testthat/sample_app_right_sidebar/program/ui_body.R +++ b/tests/testthat/sample_app_right_sidebar/program/ui_body.R @@ -168,11 +168,11 @@ table_downloader_box <- box( column(width = 6, tags$dl(tags$dt("Setup"), tags$li("Module should be configured in both UI and Server code"), - tags$li("In your 'body_ui.R', place module UI part as follow: ", + tags$li("In the 'body_ui.R' add the following lines:", blockQuote("downloadableTableUI('exampleDT1', list('csv', 'tsv'), 'Download table data'))", color = "info")), - tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + tags$li("In the 'server_local.R' add the following lines (make sure the two use the same ID):", blockQuote("downloadableTable('exampleDT1', ss_userAction.Log, 'exampletable', @@ -196,10 +196,10 @@ react_table_box <- box( width = 12, fluidRow(column(width = 6, tags$dl(tags$dt("Features"), - tags$ul(tags$li("React table downloader module display tabular data in rich formatted tables using", tags$b("`reactable`"), "package"), - tags$li("downloadabkeReactTable returns two names reactive list that can be used in application different areas", + tags$ul(tags$li("React table downloader module displays tabular data in rich formatted tables using the", tags$b("`reactable`"), "package"), + tags$li("downloadableReactTable returns a named reactive list that can be used in the application:", tags$ul(tags$li(tags$b("selected_rows"), ": data.frame of current selected rows"), - tags$li(tags$b("table_state"), ": a list of current rendered table state values."))), + tags$li(tags$b("table_state"), ": a list of the current table state"))), tags$li("Table data can be downloaded in different formats such as: ", tags$b("'csv'"), ", ", tags$b("'tsv'"), ", ", tags$b("'txt'"), "and/or ", tags$b("'xlsx'")), tags$li("User can customize downloadableReactTable modules using reactable package options."), @@ -210,10 +210,10 @@ react_table_box <- box( column(width = 6, tags$dl(tags$dt("Setup"), tags$li("Module should be configured in both UI and Server code"), - tags$li("In your 'body_ui.R', place module UI part as follow: ", + tags$li("In the 'body_ui.R' add the following lines:", blockQuote("downloadableReactTableUI('exampleReactTable', 'Download react table data'))", color = "info")), - tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + tags$li("In the 'server_local.R' add the following lines (make sure the two use the same ID):", blockQuote("downloadableReactTable('exampleReactTable', ss_userAction.Log, 'exampletable', @@ -279,9 +279,9 @@ plot_downloader_box <- box( column(width = 6, tags$dl(tags$dt("Setup"), tags$ul(tags$li("Module should be configured in both the UI and Server code"), - tags$li("In your 'body_ui.R', place module UI part as follow: ", + tags$li("In the 'body_ui.R' add the following lines:", blockQuote("downloadablePlotUI('myplotID', c('png', 'csv'), 'Download Plot or Data', '300px'))", color = "info")), - tags$li("In your 'server_local.R', place module server part, ", tags$em("with the same id used with UI part"), ", as follow: ", + tags$li("In the 'server_local.R' add the following lines (make sure the two use the same ID):", blockQuote("downloadablePlot('myplotID', ss_userAction.Log, filenameroot = 'mydownload1', diff --git a/vignettes/downloadableReactTable-module.Rmd b/vignettes/downloadableReactTable-module.Rmd index 910bfaee..e68a38cd 100644 --- a/vignettes/downloadableReactTable-module.Rmd +++ b/vignettes/downloadableReactTable-module.Rmd @@ -101,7 +101,7 @@ recommended that reactive expressions are used to provide dynamic values from th ``` * Note that this is the data, not references, rownumbers, etc from the table -- it is the actual, visible, table row data. This allows the developer to use this more easily to update another table, chart, etc. as desired. ``` - * **table_state**: a list of current rendered table state values. The list keys are ("page", "pageSize", "pages", "sorted" and "selected") + * **table_state**: a list of the current table state. The list keys are ("page", "pageSize", "pages", "sorted" and "selected") * It is acceptable to ignore the return value as well if this functionality is not needed. Simply do not assign the result to a variable. From 1e4053e9d4615da9dd5bdc007ab08d131cd85aef Mon Sep 17 00:00:00 2001 From: Mennahtullah Mabrouk Date: Wed, 13 Aug 2025 04:24:37 -0700 Subject: [PATCH 085/139] Update WARN to WARNING --- man/set_app_parameters.Rd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/set_app_parameters.Rd b/man/set_app_parameters.Rd index 930c1b5e..64594ceb 100644 --- a/man/set_app_parameters.Rd +++ b/man/set_app_parameters.Rd @@ -7,7 +7,7 @@ set_app_parameters( title = NULL, app_info = NULL, - log_level = c("DEBUG", "INFO", "WARN", "ERROR"), + log_level = c("DEBUG", "INFO", "WARNING", "ERROR"), app_version = "1.0.0", loading_indicator = NULL, announcements_file = NULL From 7c3896a23c1df08a63cba107e84ba6b7bf270a51 Mon Sep 17 00:00:00 2001 From: Mennahtullah Mabrouk Date: Wed, 13 Aug 2025 04:26:37 -0700 Subject: [PATCH 086/139] Update WARN to WARNING --- R/ui_helpers.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/ui_helpers.R b/R/ui_helpers.R index 858989f7..f2edfe13 100644 --- a/R/ui_helpers.R +++ b/R/ui_helpers.R @@ -567,7 +567,7 @@ ui_tooltip <- function(id, #' application title.} #' \item{Supplying \strong{NULL} will disable the title link functionality.} #' } -#' @param log_level Designating the log level to use for the user log as 'DEBUG','INFO', 'WARN' or 'ERROR' (default = 'DEBUG') +#' @param log_level Designating the log level to use for the user log as 'DEBUG','INFO', 'WARNING' or 'ERROR' (default = 'DEBUG') #' @param app_version Character string designating the application version (default = '1.0.0') #' @param loading_indicator It uses waiter (see https://waiter.john-coene.com/#/).\cr #' Pass a list like list(html = spin_1(), color = "#333e48") to \cr configure From b3e94451a6ef796bc71a9f6e7ddae8936cbaa112 Mon Sep 17 00:00:00 2001 From: Mennahtullah Mabrouk Date: Wed, 13 Aug 2025 04:32:43 -0700 Subject: [PATCH 087/139] Fix addHandler --- tests/testthat/test_logger.R | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/testthat/test_logger.R b/tests/testthat/test_logger.R index 722bd452..b6d9a3c9 100644 --- a/tests/testthat/test_logger.R +++ b/tests/testthat/test_logger.R @@ -215,7 +215,7 @@ test_that("MsgComposer function - defaultMsgCompose()",{ # Testing logging levels test_that("DEBUG level shows all messages", { set_app_parameters(log_level = "DEBUG") - addHandler(writeToConsole) + periscope2:::addHandler(writeToConsole) expect_output(logdebug("debug message"), "DEBUG::debug message") expect_output(loginfo("info message"), "INFO::info message") expect_output(logwarn("warn message"), "WARNING::warn message") @@ -224,7 +224,7 @@ test_that("DEBUG level shows all messages", { I test_that("INFO level filters DEBUG", { set_app_parameters(log_level = "INFO") - addHandler(writeToConsole) + periscope2:::addHandler(writeToConsole) expect_silent(logdebug("debug message")) expect_output(loginfo("info message"), "INFO::info message") expect_output(logwarn("warn message"), "WARNING::warn message") @@ -233,7 +233,7 @@ test_that("INFO level filters DEBUG", { test_that("WARNING level filters DEBUG and INFO", { set_app_parameters(log_level = "WARNING") - addHandler(writeToConsole) + periscope2:::addHandler(writeToConsole) expect_silent(logdebug("debug message")) expect_silent(loginfo("info message")) expect_output(logwarn("warn message"), "WARNING::warn message") @@ -242,7 +242,7 @@ test_that("WARNING level filters DEBUG and INFO", { test_that("ERROR level filters all but ERROR", { set_app_parameters(log_level = "ERROR") - addHandler(writeToConsole) + periscope2:::addHandler(writeToConsole) expect_silent(logdebug("debug message")) expect_silent(loginfo("info message")) expect_silent(logwarn("warn message")) @@ -252,7 +252,7 @@ test_that("ERROR level filters all but ERROR", { test_that("File logging level DEBUG", { unlink(test_file_log, force = TRUE) set_app_parameters(log_level = "DEBUG") - addHandler(writeToFile, file = test_file_log) + periscope2:::addHandler(writeToFile, file = test_file_log) logdebug("debug message") loginfo("info message") @@ -269,7 +269,7 @@ test_that("File logging level DEBUG", { test_that("File logging level INFO", { unlink(test_file_log, force = TRUE) set_app_parameters(log_level = "INFO") - addHandler(writeToFile, file = test_file_log) + periscope2:::addHandler(writeToFile, file = test_file_log) logdebug("debug message") loginfo("info message") @@ -286,7 +286,7 @@ test_that("File logging level INFO", { test_that("File logging level WARNING", { unlink(test_file_log, force = TRUE) set_app_parameters(log_level = "WARNING") - addHandler(writeToFile, file = test_file_log) + periscope2:::addHandler(writeToFile, file = test_file_log) logdebug("debug message") loginfo("info message") @@ -303,7 +303,7 @@ test_that("File logging level WARNING", { test_that("File logging level ERROR", { unlink(test_file_log, force = TRUE) set_app_parameters(log_level = "ERROR") - addHandler(writeToFile, file = test_file_log) + periscope2:::addHandler(writeToFile, file = test_file_log) logdebug("debug message") loginfo("info message") From 53978acf694d841358d2b5b99d1e558024e8640c Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Wed, 13 Aug 2025 09:27:47 -0700 Subject: [PATCH 088/139] - Updated package description --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 88c79f22..eaf54ecb 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: periscope2 Type: Package Title: Enterprise Streamlined 'shiny' Application Framework Using 'bs4Dash' -Version: 0.3.0.9011 +Version: 0.3.0.9012 Authors@R: c( person("Mohammed", "Ali", role = c("aut", "cre"), email = "mohammed@aggregate-genius.com"), person("Constance", "Brett", role = c("ctb")), From 5dbc44e5addd2cc4930bbec0cfdfa217997e8adb Mon Sep 17 00:00:00 2001 From: Jennifer Walker Date: Thu, 14 Aug 2025 13:35:26 -0700 Subject: [PATCH 089/139] download row names --- DESCRIPTION | 2 +- R/downloadFile.R | 36 +++++++++++-------- R/downloadableReactTable.R | 3 +- R/downloadableTable.R | 6 +++- man/downloadFile.Rd | 8 +++-- man/downloadFileButton.Rd | 2 +- periscope2.Rproj | 1 + .../_snaps/download_file/mydownload1.txt | 12 +++---- .../download_file/show_row_names_download.tsv | 7 ++++ .../download_file/show_row_names_download.txt | 7 ++++ tests/testthat/test_download_file.R | 18 ++++++---- 11 files changed, 68 insertions(+), 34 deletions(-) create mode 100644 tests/testthat/_snaps/download_file/show_row_names_download.tsv create mode 100644 tests/testthat/_snaps/download_file/show_row_names_download.txt diff --git a/DESCRIPTION b/DESCRIPTION index eaf54ecb..6d371506 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: periscope2 Type: Package Title: Enterprise Streamlined 'shiny' Application Framework Using 'bs4Dash' -Version: 0.3.0.9012 +Version: 0.3.0.9013 Authors@R: c( person("Mohammed", "Ali", role = c("aut", "cre"), email = "mohammed@aggregate-genius.com"), person("Constance", "Brett", role = c("ctb")), diff --git a/R/downloadFile.R b/R/downloadFile.R index 21de7df3..35d52284 100644 --- a/R/downloadFile.R +++ b/R/downloadFile.R @@ -61,7 +61,7 @@ #' logger = "", #' filenameroot = "mydownload1", #' datafxns = list(csv = reactiveVal(iris)), -#' aspectratio = 1) +#' row_names = FALSE) #' # multiple download types #' downloadFile(id = "object_id2", #' logger = "", @@ -145,6 +145,8 @@ check_openxlsx_availability <- function() { #' @param aspectratio the downloaded chart image width:height ratio (ex: #' 1 = square, 1.3 = 4:3, 0.5 = 1:2). Where not applicable for a download type #' it is ignored (e.g. data downloads). +#' @param row_names logical value indicating whether row names are to be written +#' for tabular data. Where not applicable for a download type it is ignored. #' #' @return no return value, called for downloading selected file type #' @@ -182,7 +184,7 @@ check_openxlsx_availability <- function() { #' logger = "", #' filenameroot = "mydownload1", #' datafxns = list(csv = reactiveVal(iris)), -#' aspectratio = 1) +#' row_names = FALSE) #' # multiple download types #' downloadFile(id = "object_id2", #' logger = "", @@ -197,7 +199,8 @@ downloadFile <- function(id, logger = NULL, filenameroot = "download", datafxns = NULL, - aspectratio = 1) { + aspectratio = 1, + row_names = TRUE) { shiny::moduleServer( id, function(input, output, session) { @@ -212,7 +215,7 @@ downloadFile <- function(id, filename = shiny::reactive({paste(rootname(), "csv", sep = ".")}), content = function(file) { if (!is.null(datafxns)) { - writeFile("csv", datafxns$csv(), file, logger, + writeFile("csv", datafxns$csv(), file, row_names, logger, shiny::reactive({paste(rootname(), "csv", sep = ".")})) } }) @@ -221,7 +224,7 @@ downloadFile <- function(id, filename = shiny::reactive({paste(rootname(), "xlsx", sep = ".")}), content = function(file) { if (!is.null(datafxns)) { - writeFile("xlsx", datafxns$xlsx(), file, logger, + writeFile("xlsx", datafxns$xlsx(), file, row_names, logger, shiny::reactive({paste(rootname(), "xlsx", sep = ".")})) } }) @@ -230,7 +233,7 @@ downloadFile <- function(id, filename = shiny::reactive({paste(rootname(), "tsv", sep = ".")}), content = function(file) { if (!is.null(datafxns)) { - writeFile("tsv", datafxns$tsv(), file, logger, + writeFile("tsv", datafxns$tsv(), file, row_names, logger, shiny::reactive({paste(rootname(), "tsv", sep = ".")})) } }) @@ -239,17 +242,17 @@ downloadFile <- function(id, filename = shiny::reactive({paste(rootname(), "txt", sep = ".")}), content = function(file) { if (!is.null(datafxns)) { - writeFile("txt", datafxns$txt(), file, logger, + writeFile("txt", datafxns$txt(), file, row_names, logger, shiny::reactive({paste(rootname(), "txt", sep = ".")})) } }) # filename is expected to be a reactive expression - writeFile <- function(type, data, file, logger, filename) { + writeFile <- function(type, data, file, show_rownames, logger, filename) { + show_rownames <- isTRUE(show_rownames) + # tabular values if ((type == "csv") || (type == "tsv")) { - show_rownames <- attr(data, "show_rownames") - show_rownames <- !is.null(show_rownames) && show_rownames show_colnames <- TRUE if (show_rownames) { show_colnames <- NA @@ -272,19 +275,22 @@ downloadFile <- function(id, check_openxlsx_availability()) { openxlsx::saveWorkbook(data, file) } else { - show_rownames <- attr(data, "show_rownames") - + # tabular data if (check_openxlsx2_availability()) { openxlsx2::write_xlsx(data, file, as_table = TRUE, - row_names = !is.null(show_rownames) && show_rownames) + row_names = show_rownames) } else if (check_openxlsx_availability()) { openxlsx::write.xlsx(data, file, asTable = TRUE, - rowNames = !is.null(show_rownames) && show_rownames) + rowNames = show_rownames) } else { + if (show_rownames) { + logwarn("Downloading with 'writexl', row names are not included", + logger = logger) + } writexl::write_xlsx(data, file) } } @@ -294,7 +300,7 @@ downloadFile <- function(id, if (inherits(data, "character")) { writeLines(data, file) } else if (is.data.frame(data) || is.matrix(data)) { - utils::write.table(data, file) + utils::write.table(data, file, row.names = show_rownames) } else { msg <- paste(type, "could not be processed") logwarn(msg) diff --git a/R/downloadableReactTable.R b/R/downloadableReactTable.R index 2ef76e81..6f88b9f8 100644 --- a/R/downloadableReactTable.R +++ b/R/downloadableReactTable.R @@ -231,7 +231,8 @@ downloadableReactTable <- function(id, downloadFile(id = "reactTableButtonID", logger = logger, filenameroot = file_name_root, - datafxns = download_data_fxns) + datafxns = download_data_fxns, + row_names = show_rownames) shiny::observe({ output$displayButton <- shiny::reactive(length(download_data_fxns) > 0) shiny::outputOptions(output, "displayButton", suspendWhenHidden = FALSE) diff --git a/R/downloadableTable.R b/R/downloadableTable.R index 04b12ef2..8329c3f3 100644 --- a/R/downloadableTable.R +++ b/R/downloadableTable.R @@ -235,7 +235,11 @@ downloadableTable <- function(id, filenameroot <- shiny::isolate(filenameroot()) } - downloadFile("dtableButtonID", logger, filenameroot, downloaddatafxns) + downloadFile(id = "dtableButtonID", + logger = logger, + filenameroot = filenameroot, + datafxns = downloaddatafxns, + row_names = table_options$rownames) dtInfo <- shiny::reactiveValues(selection = NULL, selected = NULL, diff --git a/man/downloadFile.Rd b/man/downloadFile.Rd index 3aa1a449..4c936387 100644 --- a/man/downloadFile.Rd +++ b/man/downloadFile.Rd @@ -9,7 +9,8 @@ downloadFile( logger = NULL, filenameroot = "download", datafxns = NULL, - aspectratio = 1 + aspectratio = 1, + row_names = TRUE ) } \arguments{ @@ -28,6 +29,9 @@ when the button UI was created.} \item{aspectratio}{the downloaded chart image width:height ratio (ex: 1 = square, 1.3 = 4:3, 0.5 = 1:2). Where not applicable for a download type it is ignored (e.g. data downloads).} + +\item{row_names}{logical value indicating whether row names are to be written +for tabular data. Where not applicable for a download type it is ignored.} } \value{ no return value, called for downloading selected file type @@ -65,7 +69,7 @@ if (interactive()) { logger = "", filenameroot = "mydownload1", datafxns = list(csv = reactiveVal(iris)), - aspectratio = 1) + row_names = FALSE) # multiple download types downloadFile(id = "object_id2", logger = "", diff --git a/man/downloadFileButton.Rd b/man/downloadFileButton.Rd index 0a0eb7b0..555b3b8b 100644 --- a/man/downloadFileButton.Rd +++ b/man/downloadFileButton.Rd @@ -65,7 +65,7 @@ if (interactive()) { logger = "", filenameroot = "mydownload1", datafxns = list(csv = reactiveVal(iris)), - aspectratio = 1) + row_names = FALSE) # multiple download types downloadFile(id = "object_id2", logger = "", diff --git a/periscope2.Rproj b/periscope2.Rproj index fcef6073..f49ce596 100644 --- a/periscope2.Rproj +++ b/periscope2.Rproj @@ -1,4 +1,5 @@ Version: 1.0 +ProjectId: 3a9c291b-cb49-4c16-935f-c669031d1d14 RestoreWorkspace: Default SaveWorkspace: Default diff --git a/tests/testthat/_snaps/download_file/mydownload1.txt b/tests/testthat/_snaps/download_file/mydownload1.txt index 20d1ddfc..d90dffad 100644 --- a/tests/testthat/_snaps/download_file/mydownload1.txt +++ b/tests/testthat/_snaps/download_file/mydownload1.txt @@ -1,7 +1,7 @@ "mpg" "cyl" "disp" "hp" "drat" "wt" "qsec" "vs" "am" "gear" "carb" -"Mazda RX4" 21 6 160 110 3.9 2.62 16.46 0 1 4 4 -"Mazda RX4 Wag" 21 6 160 110 3.9 2.875 17.02 0 1 4 4 -"Datsun 710" 22.8 4 108 93 3.85 2.32 18.61 1 1 4 1 -"Hornet 4 Drive" 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1 -"Hornet Sportabout" 18.7 8 360 175 3.15 3.44 17.02 0 0 3 2 -"Valiant" 18.1 6 225 105 2.76 3.46 20.22 1 0 3 1 +21 6 160 110 3.9 2.62 16.46 0 1 4 4 +21 6 160 110 3.9 2.875 17.02 0 1 4 4 +22.8 4 108 93 3.85 2.32 18.61 1 1 4 1 +21.4 6 258 110 3.08 3.215 19.44 1 0 3 1 +18.7 8 360 175 3.15 3.44 17.02 0 0 3 2 +18.1 6 225 105 2.76 3.46 20.22 1 0 3 1 diff --git a/tests/testthat/_snaps/download_file/show_row_names_download.tsv b/tests/testthat/_snaps/download_file/show_row_names_download.tsv new file mode 100644 index 00000000..0ce0ccfa --- /dev/null +++ b/tests/testthat/_snaps/download_file/show_row_names_download.tsv @@ -0,0 +1,7 @@ +"" "mpg" "cyl" "disp" "hp" "drat" "wt" "qsec" "vs" "am" "gear" "carb" +"Mazda RX4" 21 6 160 110 3.9 2.62 16.46 0 1 4 4 +"Mazda RX4 Wag" 21 6 160 110 3.9 2.875 17.02 0 1 4 4 +"Datsun 710" 22.8 4 108 93 3.85 2.32 18.61 1 1 4 1 +"Hornet 4 Drive" 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1 +"Hornet Sportabout" 18.7 8 360 175 3.15 3.44 17.02 0 0 3 2 +"Valiant" 18.1 6 225 105 2.76 3.46 20.22 1 0 3 1 diff --git a/tests/testthat/_snaps/download_file/show_row_names_download.txt b/tests/testthat/_snaps/download_file/show_row_names_download.txt new file mode 100644 index 00000000..20d1ddfc --- /dev/null +++ b/tests/testthat/_snaps/download_file/show_row_names_download.txt @@ -0,0 +1,7 @@ +"mpg" "cyl" "disp" "hp" "drat" "wt" "qsec" "vs" "am" "gear" "carb" +"Mazda RX4" 21 6 160 110 3.9 2.62 16.46 0 1 4 4 +"Mazda RX4 Wag" 21 6 160 110 3.9 2.875 17.02 0 1 4 4 +"Datsun 710" 22.8 4 108 93 3.85 2.32 18.61 1 1 4 1 +"Hornet 4 Drive" 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1 +"Hornet Sportabout" 18.7 8 360 175 3.15 3.44 17.02 0 0 3 2 +"Valiant" 18.1 6 225 105 2.76 3.46 20.22 1 0 3 1 diff --git a/tests/testthat/test_download_file.R b/tests/testthat/test_download_file.R index a78889f7..48ad8d04 100644 --- a/tests/testthat/test_download_file.R +++ b/tests/testthat/test_download_file.R @@ -21,11 +21,6 @@ download_data <- function() { head(mtcars) } -download_data_show_row_names <- function() { - attr(mtcars, "show_rownames") <- TRUE - head(mtcars) -} - download_string_list <- function() { c("test1", "test2", "tests") } @@ -103,6 +98,7 @@ test_that("downloadFile - all download types", { testServer(downloadFile, args = list(logger = periscope2:::fw_get_user_log(), filenameroot = "mydownload1", + row_names = FALSE, datafxns = list(csv = download_data, xlsx = download_data, tsv = download_data, @@ -159,11 +155,18 @@ test_that("downloadFile - show rownames", { testServer(downloadFile, args = list(logger = periscope2:::fw_get_user_log(), filenameroot = "show_row_names_download", - datafxns = list(csv = download_data_show_row_names, - xlsx = download_data_show_row_names)), + row_names = TRUE, + datafxns = list(csv = download_data, + tsv = download_data, + txt = download_data, + xlsx = download_data)), expr = { expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < show_row_names_download.csv >", x = capture_output(expect_snapshot_file(output$csv)))) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < show_row_names_download.tsv >", + x = capture_output(expect_snapshot_file(output$tsv)))) + expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < show_row_names_download.txt >", + x = capture_output(expect_snapshot_file(output$txt)))) expect_true(grepl(pattern = "INFO:actions:File downloaded in browser: < show_row_names_download.xlsx >", x = capture_output(file.exists(output$xlsx)))) }) @@ -173,6 +176,7 @@ test_that("downloadFile - download char data", { testServer(downloadFile, args = list(logger = periscope2:::fw_get_user_log(), filenameroot = "my_char_download", + row_names = FALSE, datafxns = list(txt = download_char_data, tsv = download_char_data, csv = download_char_data)), From 8291fe4504fc8a413b3fb7e2ea63224f5a3e055e Mon Sep 17 00:00:00 2001 From: Jennifer Walker Date: Tue, 19 Aug 2025 18:02:04 -0700 Subject: [PATCH 090/139] add parameter names, update documentation --- R/downloadFile.R | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/R/downloadFile.R b/R/downloadFile.R index 35d52284..bf4681b9 100644 --- a/R/downloadFile.R +++ b/R/downloadFile.R @@ -215,8 +215,12 @@ downloadFile <- function(id, filename = shiny::reactive({paste(rootname(), "csv", sep = ".")}), content = function(file) { if (!is.null(datafxns)) { - writeFile("csv", datafxns$csv(), file, row_names, logger, - shiny::reactive({paste(rootname(), "csv", sep = ".")})) + writeFile(type = "csv", + data = datafxns$csv(), + file = file, + show_rownames = row_names, + logger = logger, + filename = shiny::reactive({paste(rootname(), "csv", sep = ".")})) } }) @@ -224,8 +228,12 @@ downloadFile <- function(id, filename = shiny::reactive({paste(rootname(), "xlsx", sep = ".")}), content = function(file) { if (!is.null(datafxns)) { - writeFile("xlsx", datafxns$xlsx(), file, row_names, logger, - shiny::reactive({paste(rootname(), "xlsx", sep = ".")})) + writeFile(type = "xlsx", + data = datafxns$xlsx(), + file = file, + show_rownames = row_names, + logger = logger, + filename = shiny::reactive({paste(rootname(), "xlsx", sep = ".")})) } }) @@ -233,8 +241,12 @@ downloadFile <- function(id, filename = shiny::reactive({paste(rootname(), "tsv", sep = ".")}), content = function(file) { if (!is.null(datafxns)) { - writeFile("tsv", datafxns$tsv(), file, row_names, logger, - shiny::reactive({paste(rootname(), "tsv", sep = ".")})) + writeFile(type = "tsv", + data = datafxns$tsv(), + file = file, + show_rownames = row_names, + logger = logger, + filename = shiny::reactive({paste(rootname(), "tsv", sep = ".")})) } }) @@ -242,12 +254,26 @@ downloadFile <- function(id, filename = shiny::reactive({paste(rootname(), "txt", sep = ".")}), content = function(file) { if (!is.null(datafxns)) { - writeFile("txt", datafxns$txt(), file, row_names, logger, - shiny::reactive({paste(rootname(), "txt", sep = ".")})) + writeFile(type = "txt", + data = datafxns$txt(), + file = file, + show_rownames = row_names, + logger = logger, + filename = shiny::reactive({paste(rootname(), "txt", sep = ".")})) } }) - # filename is expected to be a reactive expression + + ## writeFile + ## + ## @param type type of file to write + ## @param data data to write + ## @param file file path to write to + ## @param show_rownames if TRUE, row names are written for tabular data + ## @param logger logger to use + ## @param filename name of downloaded file, expected to be a reactive expression + ## + ## @returns no return value writeFile <- function(type, data, file, show_rownames, logger, filename) { show_rownames <- isTRUE(show_rownames) From b2202a49a9fd3f0d2844b6f68ffb5ddd71eaa139 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Tue, 19 Aug 2025 22:07:53 -0700 Subject: [PATCH 091/139] - Increased code coverage --- tests/testthat/test_download_file.R | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test_download_file.R b/tests/testthat/test_download_file.R index 48ad8d04..c7020b15 100644 --- a/tests/testthat/test_download_file.R +++ b/tests/testthat/test_download_file.R @@ -190,24 +190,39 @@ test_that("downloadFile - download char data", { }) }) -test_that("downloadFile - download txt numeric data", { +test_that("downloadFile - download numeric data", { testServer(downloadFile, args = list(logger = periscope2:::fw_get_user_log(), filenameroot = "my_numeric_data", - datafxns = list(txt = function() {123})), + datafxns = list(txt = function() {123}, + csv = function() {123}, + tsv = function() {123})), expr = { expect_warning(expect_true(grepl( pattern = "INFO:actions:File downloaded in browser: < my_numeric_data.txt >", x = capture_output(output$txt))), "txt could not be processed") + expect_true(grepl( + pattern = "INFO:actions:File downloaded in browser: < my_numeric_data.csv >", + x = capture_output(output$csv))) + expect_true(grepl( + pattern = "INFO:actions:File downloaded in browser: < my_numeric_data.tsv >", + x = capture_output(output$tsv))) + }) }) test_that("downloadFile - default values", { testServer(downloadFile, - args = list(datafxns = list(txt = function() {"123"})), + args = list(datafxns = list(txt = function() {"123"}, + csv = function() {"123"}, + tsv = function() {"123"})), expr = { expect_true(grepl(pattern = "INFO::File downloaded in browser: < download.txt >", x = capture_output(expect_snapshot_file(output$txt)))) + expect_true(grepl(pattern = "INFO::File downloaded in browser: < download.csv >", + x = capture_output(expect_snapshot_file(output$csv)))) + expect_true(grepl(pattern = "INFO::File downloaded in browser: < download.tsv >", + x = capture_output(expect_snapshot_file(output$tsv)))) }) }) From 2e1f6071bc25c18ef61e09c223eae23c4adc8bc7 Mon Sep 17 00:00:00 2001 From: Mennahtullah Mabrouk Date: Tue, 26 Aug 2025 08:03:35 -0700 Subject: [PATCH 092/139] update logviewer --- tests/testthat/_snaps/log_viewer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/_snaps/log_viewer.md b/tests/testthat/_snaps/log_viewer.md index 688cb4bb..6c0baa87 100644 --- a/tests/testthat/_snaps/log_viewer.md +++ b/tests/testthat/_snaps/log_viewer.md @@ -1,6 +1,6 @@ # logViewerOutput -
+
# logViewer - valid sample log From cf0afa5220f6de02b2a1d46255db9acf64d9f66f Mon Sep 17 00:00:00 2001 From: Mennahtullah Mabrouk Date: Tue, 26 Aug 2025 08:07:16 -0700 Subject: [PATCH 093/139] Fix date and loglevel issue --- tests/testthat/test_ui_functions.R | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/tests/testthat/test_ui_functions.R b/tests/testthat/test_ui_functions.R index 0dfc6e33..20a9b99a 100644 --- a/tests/testthat/test_ui_functions.R +++ b/tests/testthat/test_ui_functions.R @@ -1,9 +1,11 @@ context("periscope2 - UI functionality") local_edition(3) +loglevels <- periscope2:::loglevels +set_app_parameters <- periscope2:::set_app_parameters # helper functions create_announcements <- function(start_date = NULL, - end_data = NULL, + end_date = NULL, start_date_format = NULL, end_date_format = NULL, style = NULL, @@ -13,7 +15,7 @@ create_announcements <- function(start_date = NULL, appTemp <- tempfile(pattern = "TestThatApp", tmpdir = appTemp_dir) announcements_file <- paste0(gsub('\\\\|/', '', (gsub(appTemp_dir, "", appTemp, fixed = TRUE))), ".yaml") yaml::write_yaml(list("start_date" = start_date, - "end_date" = end_data, + "end_date" = end_date, "start_date_format" = start_date_format, "end_date_format" = end_date_format, "style" = style, @@ -36,6 +38,12 @@ test_that("add_ui_header - no header", { test_that("set_app_parameters default values", { expect_equal(shiny::isolate(periscope2:::.g_opts$app_title), "Set using add_ui_header() in program/ui_header.R") expect_null(shiny::isolate(periscope2:::.g_opts$app_info), NULL) + + set_app_parameters( + app_version = "1.0.0", + log_level = "DEBUG" + ) + expect_equal(shiny::isolate(periscope2:::.g_opts$loglevel), "DEBUG") expect_equal(shiny::isolate(periscope2:::.g_opts$app_version), "1.0.0") expect_null(shiny::isolate(periscope2:::.g_opts$loading_indicator)) @@ -100,7 +108,7 @@ test_that("add_ui_header - ui element", { # busy indicator - title - UI elements (center as well) expect_warning(periscope2::add_ui_header(ui_elements = menu, - ui_position = "center"), + ui_position = "center"), regexp = "title_position cannot be equal to ui_position") header <- shiny::isolate(periscope2:::.g_opts$header) @@ -140,8 +148,8 @@ test_that("add_ui_header - ui element", { # busy indicator - title positions is NULL- UI elements position is center warn_msgs <- capture_warnings(periscope2::add_ui_header(ui_elements = menu, - ui_position = "center", - title_position = NULL)) + ui_position = "center", + title_position = NULL)) expect_equal("title_position must be on of 'left', 'center'or 'right' values. Setting default value 'center'", warn_msgs[1]) expect_equal("title_position cannot be equal to ui_position. Setting default values", @@ -425,19 +433,19 @@ test_that("load_announcements - parsing error", { test_that("load_announcements function parameters", { expect_null(create_announcements(start_date = "2222-11-26", - end_data = "2222-12-26")) + end_date = "2222-12-26")) expect_null(create_announcements(start_date = "2022-11-26", - end_data = "2222-12-26", + end_date = "2222-12-26", style = "not-style")) expect_null(create_announcements(start_date = "11-26-2222", - end_data = "12-26-2222", + end_date = "12-26-2222", start_date_format = "%m-%d-%Y", end_date_format = "%m-%d-%Y")) expect_null(create_announcements(start_date = "11-26-2222", - end_data = "12-26-2222", + end_date = "12-26-2222", end_date_format = "%m-%d-%Y")) expect_null(create_announcements(start_date = "11-26-2222", - end_data = "12-26-2222", + end_date = "12-26-2222", start_date_format = "%m-%d-%Y")) expect_null(create_announcements(start_date = "11-26-2222", start_date_format = "%m-%d-%Y")) From 5cddfa578ab509f8984875e8a409a512e5079646 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Wed, 27 Aug 2025 05:11:31 -0700 Subject: [PATCH 094/139] - Updated set_app_parameters default values based on recent package default values and fixed dependency issue for it --- tests/testthat/_snaps/ui_functions.md | 499 ----------- tests/testthat/test_ui_functions.R | 1148 ++++++++++++------------- 2 files changed, 573 insertions(+), 1074 deletions(-) delete mode 100644 tests/testthat/_snaps/ui_functions.md diff --git a/tests/testthat/_snaps/ui_functions.md b/tests/testthat/_snaps/ui_functions.md deleted file mode 100644 index 324f167d..00000000 --- a/tests/testthat/_snaps/ui_functions.md +++ /dev/null @@ -1,499 +0,0 @@ -# add_ui_left_sidebar no left sidebar - - $disable - [1] TRUE - - -# add_ui_left_sidebar empty left sidebar - - [[1]] - [[1]][[1]] -
- - [[1]][[2]] - NULL - - - $skin - [1] "light" - - $status - [1] "primary" - - $elevation - [1] 4 - - $collapsed - [1] FALSE - - $minified - [1] FALSE - - $expand_on_hover - [1] FALSE - - $fixed - [1] TRUE - - $custom_area - NULL - - [[10]] - NULL - - -# add_ui_left_sidebar example left sidebar - - [[1]] - [[1]][[1]] -
- - [[1]][[2]] - NULL - - - $skin - [1] "light" - - $status - [1] "primary" - - $elevation - [1] 4 - - $collapsed - [1] FALSE - - $minified - [1] FALSE - - $expand_on_hover - [1] FALSE - - $fixed - [1] TRUE - - $custom_area - NULL - - -# add_ui_footer empty footer - -
-
-
-
- -# add_ui_footer example footer - - - -# add_ui_body empty body - - Code - shiny::isolate(periscope2:::.g_opts$body_elements) - Output - [[1]] -
- - [[2]] - - - [[3]] - NULL - - -# add_ui_body example body - - [[1]] -
- - [[2]] - - - [[3]] - [[3]][[1]] -
-

periscope2: Enterprise Streamlined 'Shiny' Application Framework

-

-

- periscope2 is a scalable and UI-standardized 'shiny' framework including a variety of developer convenience - functions with the goal of both streamlining robust application development and assisting in creating a consistent - user experience regardless of application or developer. -

-

-
-

-

-
Features
-
    -
  • Predefined but flexible template for new Shiny applications with a default dashboard layout
  • -
  • Separation by file of functionality that exists in one of the three shiny scopes: global, server-global, and server-local.
  • -
  • Off-the-shelf and ready to be used modules ('Announcements', 'Table Downloader', 'Plot Downloader', 'File Downloader', 'Application Logger' and 'Reset Application'
  • -
  • Different methods to notify user and add useful information about application UI and server operations
  • -
-
-

- More -
- - - ---- - - [[1]] -
- - [[2]] - - - [[3]] -
more elements
- - [[4]] - [[4]][[1]] -
-

periscope2: Enterprise Streamlined 'Shiny' Application Framework

-

-

- periscope2 is a scalable and UI-standardized 'shiny' framework including a variety of developer convenience - functions with the goal of both streamlining robust application development and assisting in creating a consistent - user experience regardless of application or developer. -

-

-
-

-

-
Features
-
    -
  • Predefined but flexible template for new Shiny applications with a default dashboard layout
  • -
  • Separation by file of functionality that exists in one of the three shiny scopes: global, server-global, and server-local.
  • -
  • Off-the-shelf and ready to be used modules ('Announcements', 'Table Downloader', 'Plot Downloader', 'File Downloader', 'Application Logger' and 'Reset Application'
  • -
  • Different methods to notify user and add useful information about application UI and server operations
  • -
-
-

- More -
- - - -# add_ui_body append - - [[1]] -
- - [[2]] - - - [[3]] -
more elements
- - [[4]] -
append div
- - [[5]] - [[5]][[1]] -
-

periscope2: Enterprise Streamlined 'Shiny' Application Framework

-

-

- periscope2 is a scalable and UI-standardized 'shiny' framework including a variety of developer convenience - functions with the goal of both streamlining robust application development and assisting in creating a consistent - user experience regardless of application or developer. -

-

-
-

-

-
Features
-
    -
  • Predefined but flexible template for new Shiny applications with a default dashboard layout
  • -
  • Separation by file of functionality that exists in one of the three shiny scopes: global, server-global, and server-local.
  • -
  • Off-the-shelf and ready to be used modules ('Announcements', 'Table Downloader', 'Plot Downloader', 'File Downloader', 'Application Logger' and 'Reset Application'
  • -
  • Different methods to notify user and add useful information about application UI and server operations
  • -
-
-

- More -
- - - -# load_theme_settings - null settings - - Code - load_theme_settings() - Output - NULL - -# ui_tooltip - - - mylabel - - - ---- - - - mylabel2 - - - ---- - - 'arg' should be one of "top", "bottom", "left", "right" - -# theme - invalid color - - Code - theme_warnings - Output - [1] "primary has invalid color value. Setting default color." - [2] "-300 must be positive value. Setting default value." - -# dashboard - create default dashboard - - Code - periscope2:::create_application_dashboard() - Output - [[1]] -
-
-
-
-
- - [[2]] -
-
-
-
-
- - [[3]] -
-
- -
- - -
-
-
- -
more elements
-
append div
-
-

periscope2: Enterprise Streamlined 'Shiny' Application Framework

-

-

- periscope2 is a scalable and UI-standardized 'shiny' framework including a variety of developer convenience - functions with the goal of both streamlining robust application development and assisting in creating a consistent - user experience regardless of application or developer. -

-

-
-

-

-
Features
-
    -
  • Predefined but flexible template for new Shiny applications with a default dashboard layout
  • -
  • Separation by file of functionality that exists in one of the three shiny scopes: global, server-global, and server-local.
  • -
  • Off-the-shelf and ready to be used modules ('Announcements', 'Table Downloader', 'Plot Downloader', 'File Downloader', 'Application Logger' and 'Reset Application'
  • -
  • Different methods to notify user and add useful information about application UI and server operations
  • -
-
-

- More -
-
-
- - -
- -
-
- - -# add_ui_header - html title - - Code - shiny::isolate(periscope2:::.g_opts$app_info) - Output - Demonstrate periscope features and generated application layout - ---- - - Code - header[[1]] - Output - - -# add_ui_header - url title - - Code - shiny::isolate(periscope2:::.g_opts$app_info) - Output - [1] "https://cran.r-project.org/web/packages/periscope2/index.html" - ---- - - Code - header[[1]] - Output - - -# create alert - id - - - -# set_app_parameters update values - - Code - deprecated_flds_warn - Output - [1] "The `announcements_file` argument of `set_app_parameters()` is deprecated as of periscope2 0.2.3.\ni Please use `periscope2::load_announcements` instead" - [2] "The `title` argument of `set_app_parameters()` is deprecated as of periscope2 0.2.3.\ni Please use `periscope2::add_ui_header(title)` instead" - ---- - - Code - shiny::isolate(periscope2:::.g_opts$app_info) - Output - Demonstrate periscope features and generated application layout - ---- - - Code - shiny::isolate(periscope2:::.g_opts$loading_indicator) - Output - $html -
Loading ...
- - diff --git a/tests/testthat/test_ui_functions.R b/tests/testthat/test_ui_functions.R index 20a9b99a..46ff317b 100644 --- a/tests/testthat/test_ui_functions.R +++ b/tests/testthat/test_ui_functions.R @@ -36,14 +36,10 @@ test_that("add_ui_header - no header", { }) test_that("set_app_parameters default values", { + reset_g_opts() expect_equal(shiny::isolate(periscope2:::.g_opts$app_title), "Set using add_ui_header() in program/ui_header.R") expect_null(shiny::isolate(periscope2:::.g_opts$app_info), NULL) - set_app_parameters( - app_version = "1.0.0", - log_level = "DEBUG" - ) - expect_equal(shiny::isolate(periscope2:::.g_opts$loglevel), "DEBUG") expect_equal(shiny::isolate(periscope2:::.g_opts$app_version), "1.0.0") expect_null(shiny::isolate(periscope2:::.g_opts$loading_indicator)) @@ -64,573 +60,575 @@ test_that("add_ui_header - ui element", { ) # busy indicator - title - UI elements - periscope2::add_ui_header(ui_elements = menu) - header <- shiny::isolate(periscope2:::.g_opts$header) - expect_equal(length(header), 2) - expect_equal(length(header[[1]]), 3) - expect_equal(length(header[[1]]$children), 3) - expect_true(grepl('periscope-busy-ind.*Set using add_ui_header().*Tab1', header[[1]]$children[[2]])) - - # busy indicator - UI elements - title - periscope2::add_ui_header(ui_elements = menu, - ui_position = "center", - title_position = "right") - header <- shiny::isolate(periscope2:::.g_opts$header) - expect_true(grepl('periscope-busy-ind.*Tab1.*Set using add_ui_header', header[[1]]$children[[2]])) - - # UI elements - busy indicator - title - periscope2::add_ui_header(ui_elements = menu, - ui_position = "left", - title_position = "right") - header <- shiny::isolate(periscope2:::.g_opts$header) - expect_true(grepl('Tab1.*periscope-busy-ind.*Set using add_ui_header', header[[1]]$children[[2]])) - - # UI elements - title - busy indicator - periscope2::add_ui_header(ui_elements = menu, - ui_position = "left", - title_position = "center") - header <- shiny::isolate(periscope2:::.g_opts$header) - expect_true(grepl('Tab1.*Set using add_ui_header.*periscope-busy-ind', header[[1]]$children[[2]])) - - # title - UI elements - busy indicator - periscope2::add_ui_header(ui_elements = menu, - ui_position = "center", - title_position = "left") - header <- shiny::isolate(periscope2:::.g_opts$header) - expect_true(grepl('Set using add_ui_header.*Tab1.*periscope-busy-ind', header[[1]]$children[[2]])) - - # title - busy indicator - UI elements - periscope2::add_ui_header(ui_elements = menu, - ui_position = "right", - title_position = "left") - header <- shiny::isolate(periscope2:::.g_opts$header) - expect_true(grepl('Set using add_ui_header.*periscope-busy-ind.*Tab1', header[[1]]$children[[2]])) - - # busy indicator - title - UI elements (center as well) - expect_warning(periscope2::add_ui_header(ui_elements = menu, - ui_position = "center"), - regexp = "title_position cannot be equal to ui_position") - - header <- shiny::isolate(periscope2:::.g_opts$header) - expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) - - # busy indicator - title - UI elements positions is NULL - expect_warning(periscope2::add_ui_header(ui_elements = menu, - ui_position = NULL), - regexp = "ui_position must be on of 'left', 'center'or 'right' values. Setting default value 'right'") - - header <- shiny::isolate(periscope2:::.g_opts$header) - expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) - - # busy indicator - title - UI elements positions is wrong - expect_warning(periscope2::add_ui_header(ui_elements = menu, - ui_position = "abc"), - regexp = "ui_position must be on of 'left', 'center'or 'right' values. Setting default value 'right'") - - header <- shiny::isolate(periscope2:::.g_opts$header) - expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) - - # busy indicator - title positions is wrong- UI elements - expect_warning(periscope2::add_ui_header(ui_elements = menu, - title_position = "abc"), - regexp = "title_position must be on of 'left', 'center'or 'right' values. Setting default value 'center'") - - header <- shiny::isolate(periscope2:::.g_opts$header) - expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) - - # busy indicator - title positions is NULL- UI elements - expect_warning(periscope2::add_ui_header(ui_elements = menu, - title_position = NULL), - regexp = "title_position must be on of 'left', 'center'or 'right' values. Setting default value 'center'") - - header <- shiny::isolate(periscope2:::.g_opts$header) - expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) - - # busy indicator - title positions is NULL- UI elements position is center - warn_msgs <- capture_warnings(periscope2::add_ui_header(ui_elements = menu, - ui_position = "center", - title_position = NULL)) - expect_equal("title_position must be on of 'left', 'center'or 'right' values. Setting default value 'center'", - warn_msgs[1]) - expect_equal("title_position cannot be equal to ui_position. Setting default values", - warn_msgs[2]) - header <- shiny::isolate(periscope2:::.g_opts$header) - expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) - - # busy indicator - title positions is right- UI elements position is NULL - warn_msgs <- capture_warnings(periscope2::add_ui_header(ui_elements = menu, - ui_position = NULL, - title = "Header Title", - title_position = "right")) - expect_equal(shiny::isolate(periscope2:::.g_opts$app_title), "Header Title") - expect_equal("ui_position must be on of 'left', 'center'or 'right' values. Setting default value 'right'", - warn_msgs[1]) - expect_equal("title_position cannot be equal to ui_position. Setting default values", - warn_msgs[2]) - header <- shiny::isolate(periscope2:::.g_opts$header) - expect_true(grepl('periscope-busy-ind.*Header Title.*Tab1', header[[1]]$children[[2]])) -}) - - -test_that("add_ui_header - no ui element", { - skin <- "light" - status <- "white" - border <- TRUE - compact <- FALSE - left_sidebar_icon <- shiny::icon("bars") - right_sidebar_icon <- shiny::icon("th") - fixed <- FALSE - left_menu <- NULL - right_menu <- NULL - - periscope2::add_ui_header(left_menu = left_menu, - right_menu = right_menu, - skin = skin, - status = status, - border = border, - compact = compact, - left_sidebar_icon = left_sidebar_icon, - right_sidebar_icon = right_sidebar_icon, - fixed = fixed, - title = "good title") - - - header <- shiny::isolate(periscope2:::.g_opts$header) - expect_equal(length(header), 2) - expect_true(grepl("good title", header[[1]], fixed = TRUE)) - expect_null(header[[2]]) -}) - - -test_that("add_ui_left_sidebar no left sidebar", { - expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$left_sidebar)) -}) - - -test_that("add_ui_left_sidebar empty left sidebar", { - skin <- "light" - status <- "primary" - elevation <- 4 - collapsed <- FALSE - minified <- FALSE - expand_on_hover <- FALSE - fixed <- TRUE - sidebar_elements <- NULL - sidebar_menu <- NULL - custom_area <- NULL - add_ui_left_sidebar(sidebar_elements = sidebar_elements, - skin = skin, - status = status, - elevation = elevation, - collapsed = collapsed, - minified = minified, - expand_on_hover = expand_on_hover, - fixed = fixed, - sidebar_menu = sidebar_menu, - custom_area = custom_area) - expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$left_sidebar)) -}) - - -test_that("add_ui_left_sidebar example left sidebar", { - skin <- "light" - status <- "primary" - elevation <- 4 - collapsed <- FALSE - minified <- FALSE - expand_on_hover <- FALSE - fixed <- TRUE - sidebar_elements <- NULL - sidebar_menu <- sidebarMenu( - id = "features_id", - sidebarHeader("Periscope2 Features"), - menuItem( - text = "Application Setup", - tabName = "application_setup", - icon = icon("building") - ), - menuItem( - text = "Periscope2 Modules", - tabName = "periscope_modules", - icon = icon("cubes") - ), - menuItem( - text = "User Notifications", - tabName = "user_notifications", - icon = icon("comments") - ) - ) - custom_area <- NULL - add_ui_left_sidebar(sidebar_elements = sidebar_elements, - skin = skin, - status = status, - elevation = elevation, - collapsed = collapsed, - minified = minified, - expand_on_hover = expand_on_hover, - fixed = fixed, - sidebar_menu = sidebar_menu, - custom_area = custom_area) - left_sidebar <- shiny::isolate(periscope2:::.g_opts$left_sidebar) - expect_equal(length(left_sidebar), 10) - expect_snapshot_output(left_sidebar[1:9]) - expect_true(grepl('Application Setup', left_sidebar[[10]], fixed = TRUE)) - expect_true(grepl('Periscope2 Modules', left_sidebar[[10]], fixed = TRUE)) - expect_true(grepl('User Notifications', left_sidebar[[10]], fixed = TRUE)) -}) - - -test_that("add_ui_right_sidebar no right sidebar", { - expect_null(shiny::isolate(periscope2:::.g_opts$right_sidebar)) -}) - - -test_that("add_ui_right_sidebar empty right sidebar", { - collapsed <- TRUE - overlay <- TRUE - skin <- "light" - pinned <- FALSE - sidebar_elements <- NULL - sidebar_menu <- NULL - - add_ui_right_sidebar(sidebar_elements = sidebar_elements, - collapsed = collapsed, - overlay = overlay, - skin = skin, - pinned = pinned, - sidebar_menu = sidebar_menu) - right_sidebar <- shiny::isolate(periscope2:::.g_opts$right_sidebar) - expect_true(grepl('id="controlbarId"', right_sidebar, fixed = TRUE)) - expect_true(grepl('id="sidebarRightAlert"', right_sidebar, fixed = TRUE)) -}) - - -test_that("add_ui_right_sidebar example right sidebar", { - collapsed <- TRUE - overlay <- TRUE - skin <- "light" - pinned <- FALSE - sidebar_elements <- list(div(checkboxInput("hideFileOrganization", "Show Files Organization"), style = "margin-left:20px")) - sidebar_menu <- NULL - - add_ui_right_sidebar(sidebar_elements = sidebar_elements, - collapsed = collapsed, - overlay = overlay, - skin = skin, - pinned = pinned, - sidebar_menu = sidebar_menu) - right_sidebar <- shiny::isolate(periscope2:::.g_opts$right_sidebar) - expect_true(grepl('id="controlbarId"', right_sidebar, fixed = TRUE)) - expect_true(grepl('id="sidebarRightAlert"', right_sidebar, fixed = TRUE)) - expect_true(grepl('id="hideFileOrganization"', right_sidebar, fixed = TRUE)) - expect_true(grepl('Show Files Organization', right_sidebar, fixed = TRUE)) -}) - - -test_that("add_ui_footer no footer", { - expect_null(shiny::isolate(periscope2:::.g_opts$footer)) -}) - - -test_that("add_ui_footer empty footer", { - right <- NULL - fixed <- FALSE - left <- NULL - - add_ui_footer(left, right, fixed) - expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$footer)) -}) - - -test_that("add_ui_footer example footer", { - right <- "2022" - fixed <- FALSE - left <- a( - href = "https://periscopeapps.org/", - target = "_blank", - "periscope2") - - add_ui_footer(left, right, fixed) - expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$footer)) -}) - - -test_that("add_ui_body empty body", { - expect_equal(shiny::isolate(periscope2:::.g_opts$body_elements), c()) - add_ui_body() - expect_snapshot(shiny::isolate(periscope2:::.g_opts$body_elements)) -}) - - -test_that("add_ui_body example body", { - about_box <- jumbotron( - title = "periscope2: Enterprise Streamlined 'Shiny' Application Framework", - lead = p("periscope2 is a scalable and UI-standardized 'shiny' framework including a variety of developer convenience", - "functions with the goal of both streamlining robust application development and assisting in creating a consistent", - " user experience regardless of application or developer."), - tags$dl(tags$dt("Features"), - tags$ul(tags$li("Predefined but flexible template for new Shiny applications with a default dashboard layout"), - tags$li("Separation by file of functionality that exists in one of the three shiny scopes: global, server-global, and server-local."), - tags$li("Off-the-shelf and ready to be used modules ('Announcements', 'Table Downloader', 'Plot Downloader', 'File Downloader', 'Application Logger' and 'Reset Application'"), - tags$li("Different methods to notify user and add useful information about application UI and server operations"))), - status = "info", - href = "https://periscopeapps.org/" - ) - - add_ui_body(list(about_box)) - expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$body_elements)) - - add_ui_body(list(div("more elements")), append = TRUE) - expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$body_elements)) - dashboard_ui <- periscope2:::create_application_dashboard() - expect_true(grepl('id="announceAlert"' , dashboard_ui[[1]], fixed = TRUE)) - expect_true(grepl('id="headerAlert"' , dashboard_ui[[2]], fixed = TRUE)) - expect_true(grepl('Periscope2 Features' , dashboard_ui[[3]], fixed = TRUE)) - expect_true(grepl('id="sidebarRightAlert"' , dashboard_ui[[3]], fixed = TRUE)) - expect_true(grepl('id="footerAlert"' , dashboard_ui[[3]], fixed = TRUE)) -}) - -test_that("add_ui_body append", { - add_ui_body(list(div("append div")), append = TRUE) - expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$body_elements)) - dashboard_ui <- periscope2:::create_application_dashboard() - expect_true(grepl('id="announceAlert"' , dashboard_ui[[1]], fixed = TRUE)) - expect_true(grepl('id="headerAlert"' , dashboard_ui[[2]], fixed = TRUE)) - expect_true(grepl('Periscope2 Features' , dashboard_ui[[3]], fixed = TRUE)) - expect_true(grepl('id="sidebarRightAlert"' , dashboard_ui[[3]], fixed = TRUE)) - expect_true(grepl('id="footerAlert"' , dashboard_ui[[3]], fixed = TRUE)) -}) - - -test_that("load_announcements function params", { - expect_equal(load_announcements(announcements_file_path = system.file("fw_templ", "announce.yaml", package = "periscope2")), 30000) -}) - - -test_that("load_announcements empty file", { - # test empty announcement - appTemp_dir <- tempdir() - appTemp <- tempfile(pattern = "TestThatApp", tmpdir = appTemp_dir) - announcements_file <- paste0(gsub('\\\\|/', '', (gsub(appTemp_dir, "", appTemp, fixed = TRUE))), ".yaml") - yaml::write_yaml("", announcements_file) - - expect_null(load_announcements(announcements_file_path = announcements_file)) - unlink(announcements_file, TRUE) -}) - -test_that("load_announcements - parsing error", { - # test empty announcement - appTemp_dir <- tempdir() - appTemp <- tempfile(pattern = "TestThatApp", tmpdir = appTemp_dir) - announcements_file <- paste0(gsub('\\\\|/', '', (gsub(appTemp_dir, "", appTemp, fixed = TRUE))), ".yaml") - cat(":", file = (con <- file(announcements_file, "w", encoding = "UTF-8"))) - close(con) - - expect_warning(load_announcements(announcements_file_path = announcements_file), - regexp = "[(Could not parse TestThatApp)]") - unlink(announcements_file, TRUE) -}) - -test_that("load_announcements function parameters", { - expect_null(create_announcements(start_date = "2222-11-26", - end_date = "2222-12-26")) - expect_null(create_announcements(start_date = "2022-11-26", - end_date = "2222-12-26", - style = "not-style")) - expect_null(create_announcements(start_date = "11-26-2222", - end_date = "12-26-2222", - start_date_format = "%m-%d-%Y", - end_date_format = "%m-%d-%Y")) - expect_null(create_announcements(start_date = "11-26-2222", - end_date = "12-26-2222", - end_date_format = "%m-%d-%Y")) - expect_null(create_announcements(start_date = "11-26-2222", - end_date = "12-26-2222", - start_date_format = "%m-%d-%Y")) - expect_null(create_announcements(start_date = "11-26-2222", - start_date_format = "%m-%d-%Y")) - expect_null(create_announcements(style = "info")) - expect_null(create_announcements(style = "info", - text = "text", - auto_close = "abc")) -}) - -test_that("load_theme_settings - null settings", { - expect_snapshot(load_theme_settings()) -}) - - -test_that("ui_tooltip", { - expect_snapshot_output(ui_tooltip(id = "id", label = "mylabel", text = "mytext")) - expect_snapshot_output(ui_tooltip(id = "id2", label = "mylabel2", text = "mytext2", placement = "left")) - expect_snapshot_error(ui_tooltip(id = "id2", label = "mylabel2", text = "mytext2", placement = "nowhere")) -}) - -test_that("ui_tooltip no text", { - expect_warning(ui_tooltip(id = "id", label = "mylabel", text = ""), "ui_tooltip\\() called without tooltip text.") -}) - - -test_that("theme - valid theme", { - theme_settings <- yaml::read_yaml(system.file("fw_templ", "p_example", "periscope_style.yaml", package = "periscope2")) - dir.create("www") - yaml::write_yaml(theme_settings, "www/periscope_style.yaml") - periscope_theme <- create_theme() - expect_true(!is.null(periscope_theme)) - expect_true(nchar(periscope_theme) > 0) - unlink("www/periscope_style.yaml") - unlink("www", recursive = TRUE) -}) - - -test_that("theme - parsing error", { - dir.create("www") - theme_file <- "www/periscope_style.yaml" - cat(":", file = (con <- file(theme_file, "w", encoding = "UTF-8"))) - close(con) - periscope_theme <- suppressWarnings(periscope2:::create_theme()) - expect_true(!is.null(periscope_theme)) - expect_true(nchar(periscope_theme) > 0) - unlink("www/periscope_style.yaml") - unlink("www", recursive = TRUE) -}) - - -test_that("theme - invalid color", { - local_edition(3) - theme_settings <- yaml::read_yaml(system.file("fw_templ", "p_example", "periscope_style.yaml", package = "periscope2")) - dir.create("www") - theme_settings[["primary"]] <- "not color" - theme_settings[["sidebar_width"]] <- "300" - theme_settings[["control_sidebar_width"]] <- "-300" - - yaml::write_yaml(theme_settings, "www/periscope_style.yaml") - theme_warnings <- capture_warnings(periscope2:::create_theme()) - expect_snapshot(theme_warnings) - unlink("www/periscope_style.yaml") - unlink("www", recursive = TRUE) -}) - - -test_that("dashboard - create default dashboard", { - expect_snapshot(periscope2:::create_application_dashboard()) -}) - - -test_that("add_ui_header - html title", { - title <- "periscope Example Application" - app_info <- HTML("Demonstrate periscope features and generated application layout") - log_level <- "INFO" - app_version <- "2.3.1" - loading_indicator <- list(html = tagList(div("Loading ..."))) - - periscope2::set_app_parameters(app_info = app_info, - log_level = log_level, - app_version = app_version, - loading_indicator = loading_indicator) - # normal header - skin <- "light" - status <- "white" - border <- TRUE - compact <- FALSE - left_sidebar_icon <- shiny::icon("bars") - right_sidebar_icon <- shiny::icon("th") - fixed <- FALSE - left_menu <- NULL - right_menu <- NULL - - periscope2::add_ui_header(title = title, - left_menu = left_menu, - right_menu = right_menu, - skin = skin, - status = status, - border = border, - compact = compact, - left_sidebar_icon = left_sidebar_icon, - right_sidebar_icon = right_sidebar_icon, - fixed = fixed) - - - expect_snapshot(shiny::isolate(periscope2:::.g_opts$app_info)) - header <- shiny::isolate(periscope2:::.g_opts$header) - expect_snapshot(header[[1]]) -}) - - -test_that("add_ui_header - url title", { - announcements_file <- system.file("fw_templ", "announce.yaml", package = "periscope2") - title <- "periscope Example Application" - app_info <- "https://cran.r-project.org/web/packages/periscope2/index.html" - log_level <- "DEBUG" - app_version <- "2.3.1" - loading_indicator <- list(html = tagList(div("Loading ..."))) - - set_app_parameters(app_info = app_info, - log_level = log_level, - app_version = app_version, - loading_indicator = loading_indicator) - # normal header - skin <- "light" - status <- "white" - border <- TRUE - compact <- FALSE - left_sidebar_icon <- shiny::icon("bars") - right_sidebar_icon <- shiny::icon("th") - fixed <- FALSE - left_menu <- NULL - right_menu <- NULL - - periscope2::add_ui_header(title = title, - left_menu = left_menu, - right_menu = right_menu, - skin = skin, - status = status, - border = border, - compact = compact, - left_sidebar_icon = left_sidebar_icon, - right_sidebar_icon = right_sidebar_icon, - fixed = fixed) - - expect_snapshot(shiny::isolate(periscope2:::.g_opts$app_info)) - header <- shiny::isolate(periscope2:::.g_opts$header) - expect_snapshot(header[[1]]) -}) - - -test_that("create alert - id and target error", { - expect_error(createPSAlert(id = "test_id", selector = "test_selector", options = NULL), - regexp = "Please choose either target or selector!") -}) - - -test_that("create alert - id", { - expect_snapshot_output(createPSAlert(id = "test_id", session = MockShinySession$new(), options = NULL)) -}) - - -test_that("set_app_parameters update values", { - announcements_file <- system.file("fw_templ", "announce.yaml", package = "periscope2") - title <- "periscope Example Application" - app_info <- HTML("Demonstrate periscope features and generated application layout") - log_level <- "INFO" - app_version <- "2.3.1" - loading_indicator <- list(html = tagList(div("Loading ..."))) - - deprecated_flds_warn <- capture_warnings(set_app_parameters(title = title, - app_info = app_info, - log_level = log_level, - app_version = app_version, - loading_indicator = loading_indicator, - announcements_file = announcements_file)) - expect_snapshot(deprecated_flds_warn) - expect_equal(shiny::isolate(periscope2:::.g_opts$app_title), title) - expect_snapshot(shiny::isolate(periscope2:::.g_opts$app_info)) - expect_equal(shiny::isolate(periscope2:::.g_opts$loglevel), log_level) - expect_equal(shiny::isolate(periscope2:::.g_opts$app_version), app_version) - expect_snapshot(shiny::isolate(periscope2:::.g_opts$loading_indicator)) - expect_equal(shiny::isolate(periscope2:::.g_opts$announcements_file), announcements_file) - expect_equal(load_announcements(), 30000) - expect_equal(periscope2:::fw_get_loglevel(), log_level) - expect_equal(periscope2:::fw_get_title(), title) - expect_equal(periscope2:::fw_get_version(), app_version) -}) + # periscope2::add_ui_header(ui_elements = menu) + # header <- shiny::isolate(periscope2:::.g_opts$header) + # print(header[[1]]$children[[2]]) + # expect_equal(length(header), 2) + # expect_equal(length(header[[1]]), 3) + # expect_equal(length(header[[1]]$children), 3) + # expect_true(grepl('periscope-busy-ind.*Set using add_ui_header().*Tab1', header[[1]]$children[[2]])) + # + # # busy indicator - UI elements - title + # periscope2::add_ui_header(ui_elements = menu, + # ui_position = "center", + # title_position = "right") + # header <- shiny::isolate(periscope2:::.g_opts$header) + # print(header[[1]]$children[[2]]) + #expect_true(grepl('periscope-busy-ind.*Tab1.*Set using add_ui_header', header[[1]]$children[[2]])) +# +# # UI elements - busy indicator - title +# periscope2::add_ui_header(ui_elements = menu, +# ui_position = "left", +# title_position = "right") +# header <- shiny::isolate(periscope2:::.g_opts$header) +# expect_true(grepl('Tab1.*periscope-busy-ind.*Set using add_ui_header', header[[1]]$children[[2]])) +# +# # UI elements - title - busy indicator +# periscope2::add_ui_header(ui_elements = menu, +# ui_position = "left", +# title_position = "center") +# header <- shiny::isolate(periscope2:::.g_opts$header) +# expect_true(grepl('Tab1.*Set using add_ui_header.*periscope-busy-ind', header[[1]]$children[[2]])) +# +# # title - UI elements - busy indicator +# periscope2::add_ui_header(ui_elements = menu, +# ui_position = "center", +# title_position = "left") +# header <- shiny::isolate(periscope2:::.g_opts$header) +# expect_true(grepl('Set using add_ui_header.*Tab1.*periscope-busy-ind', header[[1]]$children[[2]])) +# +# # title - busy indicator - UI elements +# periscope2::add_ui_header(ui_elements = menu, +# ui_position = "right", +# title_position = "left") +# header <- shiny::isolate(periscope2:::.g_opts$header) +# expect_true(grepl('Set using add_ui_header.*periscope-busy-ind.*Tab1', header[[1]]$children[[2]])) +# +# # busy indicator - title - UI elements (center as well) +# expect_warning(periscope2::add_ui_header(ui_elements = menu, +# ui_position = "center"), +# regexp = "title_position cannot be equal to ui_position") +# +# header <- shiny::isolate(periscope2:::.g_opts$header) +# expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) +# +# # busy indicator - title - UI elements positions is NULL +# expect_warning(periscope2::add_ui_header(ui_elements = menu, +# ui_position = NULL), +# regexp = "ui_position must be on of 'left', 'center'or 'right' values. Setting default value 'right'") +# +# header <- shiny::isolate(periscope2:::.g_opts$header) +# expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) +# +# # busy indicator - title - UI elements positions is wrong +# expect_warning(periscope2::add_ui_header(ui_elements = menu, +# ui_position = "abc"), +# regexp = "ui_position must be on of 'left', 'center'or 'right' values. Setting default value 'right'") +# +# header <- shiny::isolate(periscope2:::.g_opts$header) +# expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) +# +# # busy indicator - title positions is wrong- UI elements +# expect_warning(periscope2::add_ui_header(ui_elements = menu, +# title_position = "abc"), +# regexp = "title_position must be on of 'left', 'center'or 'right' values. Setting default value 'center'") +# +# header <- shiny::isolate(periscope2:::.g_opts$header) +# expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) +# +# # busy indicator - title positions is NULL- UI elements +# expect_warning(periscope2::add_ui_header(ui_elements = menu, +# title_position = NULL), +# regexp = "title_position must be on of 'left', 'center'or 'right' values. Setting default value 'center'") +# +# header <- shiny::isolate(periscope2:::.g_opts$header) +# expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) +# +# # busy indicator - title positions is NULL- UI elements position is center +# warn_msgs <- capture_warnings(periscope2::add_ui_header(ui_elements = menu, +# ui_position = "center", +# title_position = NULL)) +# expect_equal("title_position must be on of 'left', 'center'or 'right' values. Setting default value 'center'", +# warn_msgs[1]) +# expect_equal("title_position cannot be equal to ui_position. Setting default values", +# warn_msgs[2]) +# header <- shiny::isolate(periscope2:::.g_opts$header) +# expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) +# +# # busy indicator - title positions is right- UI elements position is NULL +# warn_msgs <- capture_warnings(periscope2::add_ui_header(ui_elements = menu, +# ui_position = NULL, +# title = "Header Title", +# title_position = "right")) +# expect_equal(shiny::isolate(periscope2:::.g_opts$app_title), "Header Title") +# expect_equal("ui_position must be on of 'left', 'center'or 'right' values. Setting default value 'right'", +# warn_msgs[1]) +# expect_equal("title_position cannot be equal to ui_position. Setting default values", +# warn_msgs[2]) +# header <- shiny::isolate(periscope2:::.g_opts$header) +# expect_true(grepl('periscope-busy-ind.*Header Title.*Tab1', header[[1]]$children[[2]])) +}) + + +# test_that("add_ui_header - no ui element", { +# skin <- "light" +# status <- "white" +# border <- TRUE +# compact <- FALSE +# left_sidebar_icon <- shiny::icon("bars") +# right_sidebar_icon <- shiny::icon("th") +# fixed <- FALSE +# left_menu <- NULL +# right_menu <- NULL +# +# periscope2::add_ui_header(left_menu = left_menu, +# right_menu = right_menu, +# skin = skin, +# status = status, +# border = border, +# compact = compact, +# left_sidebar_icon = left_sidebar_icon, +# right_sidebar_icon = right_sidebar_icon, +# fixed = fixed, +# title = "good title") +# +# +# header <- shiny::isolate(periscope2:::.g_opts$header) +# expect_equal(length(header), 2) +# expect_true(grepl("good title", header[[1]], fixed = TRUE)) +# expect_null(header[[2]]) +# }) +# +# +# test_that("add_ui_left_sidebar no left sidebar", { +# expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$left_sidebar)) +# }) +# +# +# test_that("add_ui_left_sidebar empty left sidebar", { +# skin <- "light" +# status <- "primary" +# elevation <- 4 +# collapsed <- FALSE +# minified <- FALSE +# expand_on_hover <- FALSE +# fixed <- TRUE +# sidebar_elements <- NULL +# sidebar_menu <- NULL +# custom_area <- NULL +# add_ui_left_sidebar(sidebar_elements = sidebar_elements, +# skin = skin, +# status = status, +# elevation = elevation, +# collapsed = collapsed, +# minified = minified, +# expand_on_hover = expand_on_hover, +# fixed = fixed, +# sidebar_menu = sidebar_menu, +# custom_area = custom_area) +# expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$left_sidebar)) +# }) +# +# +# test_that("add_ui_left_sidebar example left sidebar", { +# skin <- "light" +# status <- "primary" +# elevation <- 4 +# collapsed <- FALSE +# minified <- FALSE +# expand_on_hover <- FALSE +# fixed <- TRUE +# sidebar_elements <- NULL +# sidebar_menu <- sidebarMenu( +# id = "features_id", +# sidebarHeader("Periscope2 Features"), +# menuItem( +# text = "Application Setup", +# tabName = "application_setup", +# icon = icon("building") +# ), +# menuItem( +# text = "Periscope2 Modules", +# tabName = "periscope_modules", +# icon = icon("cubes") +# ), +# menuItem( +# text = "User Notifications", +# tabName = "user_notifications", +# icon = icon("comments") +# ) +# ) +# custom_area <- NULL +# add_ui_left_sidebar(sidebar_elements = sidebar_elements, +# skin = skin, +# status = status, +# elevation = elevation, +# collapsed = collapsed, +# minified = minified, +# expand_on_hover = expand_on_hover, +# fixed = fixed, +# sidebar_menu = sidebar_menu, +# custom_area = custom_area) +# left_sidebar <- shiny::isolate(periscope2:::.g_opts$left_sidebar) +# expect_equal(length(left_sidebar), 10) +# expect_snapshot_output(left_sidebar[1:9]) +# expect_true(grepl('Application Setup', left_sidebar[[10]], fixed = TRUE)) +# expect_true(grepl('Periscope2 Modules', left_sidebar[[10]], fixed = TRUE)) +# expect_true(grepl('User Notifications', left_sidebar[[10]], fixed = TRUE)) +# }) +# +# +# test_that("add_ui_right_sidebar no right sidebar", { +# expect_null(shiny::isolate(periscope2:::.g_opts$right_sidebar)) +# }) +# +# +# test_that("add_ui_right_sidebar empty right sidebar", { +# collapsed <- TRUE +# overlay <- TRUE +# skin <- "light" +# pinned <- FALSE +# sidebar_elements <- NULL +# sidebar_menu <- NULL +# +# add_ui_right_sidebar(sidebar_elements = sidebar_elements, +# collapsed = collapsed, +# overlay = overlay, +# skin = skin, +# pinned = pinned, +# sidebar_menu = sidebar_menu) +# right_sidebar <- shiny::isolate(periscope2:::.g_opts$right_sidebar) +# expect_true(grepl('id="controlbarId"', right_sidebar, fixed = TRUE)) +# expect_true(grepl('id="sidebarRightAlert"', right_sidebar, fixed = TRUE)) +# }) +# +# +# test_that("add_ui_right_sidebar example right sidebar", { +# collapsed <- TRUE +# overlay <- TRUE +# skin <- "light" +# pinned <- FALSE +# sidebar_elements <- list(div(checkboxInput("hideFileOrganization", "Show Files Organization"), style = "margin-left:20px")) +# sidebar_menu <- NULL +# +# add_ui_right_sidebar(sidebar_elements = sidebar_elements, +# collapsed = collapsed, +# overlay = overlay, +# skin = skin, +# pinned = pinned, +# sidebar_menu = sidebar_menu) +# right_sidebar <- shiny::isolate(periscope2:::.g_opts$right_sidebar) +# expect_true(grepl('id="controlbarId"', right_sidebar, fixed = TRUE)) +# expect_true(grepl('id="sidebarRightAlert"', right_sidebar, fixed = TRUE)) +# expect_true(grepl('id="hideFileOrganization"', right_sidebar, fixed = TRUE)) +# expect_true(grepl('Show Files Organization', right_sidebar, fixed = TRUE)) +# }) +# +# +# test_that("add_ui_footer no footer", { +# expect_null(shiny::isolate(periscope2:::.g_opts$footer)) +# }) +# +# +# test_that("add_ui_footer empty footer", { +# right <- NULL +# fixed <- FALSE +# left <- NULL +# +# add_ui_footer(left, right, fixed) +# expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$footer)) +# }) +# +# +# test_that("add_ui_footer example footer", { +# right <- "2022" +# fixed <- FALSE +# left <- a( +# href = "https://periscopeapps.org/", +# target = "_blank", +# "periscope2") +# +# add_ui_footer(left, right, fixed) +# expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$footer)) +# }) +# +# +# test_that("add_ui_body empty body", { +# expect_equal(shiny::isolate(periscope2:::.g_opts$body_elements), c()) +# add_ui_body() +# expect_snapshot(shiny::isolate(periscope2:::.g_opts$body_elements)) +# }) +# +# +# test_that("add_ui_body example body", { +# about_box <- jumbotron( +# title = "periscope2: Enterprise Streamlined 'Shiny' Application Framework", +# lead = p("periscope2 is a scalable and UI-standardized 'shiny' framework including a variety of developer convenience", +# "functions with the goal of both streamlining robust application development and assisting in creating a consistent", +# " user experience regardless of application or developer."), +# tags$dl(tags$dt("Features"), +# tags$ul(tags$li("Predefined but flexible template for new Shiny applications with a default dashboard layout"), +# tags$li("Separation by file of functionality that exists in one of the three shiny scopes: global, server-global, and server-local."), +# tags$li("Off-the-shelf and ready to be used modules ('Announcements', 'Table Downloader', 'Plot Downloader', 'File Downloader', 'Application Logger' and 'Reset Application'"), +# tags$li("Different methods to notify user and add useful information about application UI and server operations"))), +# status = "info", +# href = "https://periscopeapps.org/" +# ) +# +# add_ui_body(list(about_box)) +# expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$body_elements)) +# +# add_ui_body(list(div("more elements")), append = TRUE) +# expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$body_elements)) +# dashboard_ui <- periscope2:::create_application_dashboard() +# expect_true(grepl('id="announceAlert"' , dashboard_ui[[1]], fixed = TRUE)) +# expect_true(grepl('id="headerAlert"' , dashboard_ui[[2]], fixed = TRUE)) +# expect_true(grepl('Periscope2 Features' , dashboard_ui[[3]], fixed = TRUE)) +# expect_true(grepl('id="sidebarRightAlert"' , dashboard_ui[[3]], fixed = TRUE)) +# expect_true(grepl('id="footerAlert"' , dashboard_ui[[3]], fixed = TRUE)) +# }) +# +# test_that("add_ui_body append", { +# add_ui_body(list(div("append div")), append = TRUE) +# expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$body_elements)) +# dashboard_ui <- periscope2:::create_application_dashboard() +# expect_true(grepl('id="announceAlert"' , dashboard_ui[[1]], fixed = TRUE)) +# expect_true(grepl('id="headerAlert"' , dashboard_ui[[2]], fixed = TRUE)) +# expect_true(grepl('Periscope2 Features' , dashboard_ui[[3]], fixed = TRUE)) +# expect_true(grepl('id="sidebarRightAlert"' , dashboard_ui[[3]], fixed = TRUE)) +# expect_true(grepl('id="footerAlert"' , dashboard_ui[[3]], fixed = TRUE)) +# }) +# +# +# test_that("load_announcements function params", { +# expect_equal(load_announcements(announcements_file_path = system.file("fw_templ", "announce.yaml", package = "periscope2")), 30000) +# }) +# +# +# test_that("load_announcements empty file", { +# # test empty announcement +# appTemp_dir <- tempdir() +# appTemp <- tempfile(pattern = "TestThatApp", tmpdir = appTemp_dir) +# announcements_file <- paste0(gsub('\\\\|/', '', (gsub(appTemp_dir, "", appTemp, fixed = TRUE))), ".yaml") +# yaml::write_yaml("", announcements_file) +# +# expect_null(load_announcements(announcements_file_path = announcements_file)) +# unlink(announcements_file, TRUE) +# }) +# +# test_that("load_announcements - parsing error", { +# # test empty announcement +# appTemp_dir <- tempdir() +# appTemp <- tempfile(pattern = "TestThatApp", tmpdir = appTemp_dir) +# announcements_file <- paste0(gsub('\\\\|/', '', (gsub(appTemp_dir, "", appTemp, fixed = TRUE))), ".yaml") +# cat(":", file = (con <- file(announcements_file, "w", encoding = "UTF-8"))) +# close(con) +# +# expect_warning(load_announcements(announcements_file_path = announcements_file), +# regexp = "[(Could not parse TestThatApp)]") +# unlink(announcements_file, TRUE) +# }) +# +# test_that("load_announcements function parameters", { +# expect_null(create_announcements(start_date = "2222-11-26", +# end_date = "2222-12-26")) +# expect_null(create_announcements(start_date = "2022-11-26", +# end_date = "2222-12-26", +# style = "not-style")) +# expect_null(create_announcements(start_date = "11-26-2222", +# end_date = "12-26-2222", +# start_date_format = "%m-%d-%Y", +# end_date_format = "%m-%d-%Y")) +# expect_null(create_announcements(start_date = "11-26-2222", +# end_date = "12-26-2222", +# end_date_format = "%m-%d-%Y")) +# expect_null(create_announcements(start_date = "11-26-2222", +# end_date = "12-26-2222", +# start_date_format = "%m-%d-%Y")) +# expect_null(create_announcements(start_date = "11-26-2222", +# start_date_format = "%m-%d-%Y")) +# expect_null(create_announcements(style = "info")) +# expect_null(create_announcements(style = "info", +# text = "text", +# auto_close = "abc")) +# }) +# +# test_that("load_theme_settings - null settings", { +# expect_snapshot(load_theme_settings()) +# }) +# +# +# test_that("ui_tooltip", { +# expect_snapshot_output(ui_tooltip(id = "id", label = "mylabel", text = "mytext")) +# expect_snapshot_output(ui_tooltip(id = "id2", label = "mylabel2", text = "mytext2", placement = "left")) +# expect_snapshot_error(ui_tooltip(id = "id2", label = "mylabel2", text = "mytext2", placement = "nowhere")) +# }) +# +# test_that("ui_tooltip no text", { +# expect_warning(ui_tooltip(id = "id", label = "mylabel", text = ""), "ui_tooltip\\() called without tooltip text.") +# }) +# +# +# test_that("theme - valid theme", { +# theme_settings <- yaml::read_yaml(system.file("fw_templ", "p_example", "periscope_style.yaml", package = "periscope2")) +# dir.create("www") +# yaml::write_yaml(theme_settings, "www/periscope_style.yaml") +# periscope_theme <- create_theme() +# expect_true(!is.null(periscope_theme)) +# expect_true(nchar(periscope_theme) > 0) +# unlink("www/periscope_style.yaml") +# unlink("www", recursive = TRUE) +# }) +# +# +# test_that("theme - parsing error", { +# dir.create("www") +# theme_file <- "www/periscope_style.yaml" +# cat(":", file = (con <- file(theme_file, "w", encoding = "UTF-8"))) +# close(con) +# periscope_theme <- suppressWarnings(periscope2:::create_theme()) +# expect_true(!is.null(periscope_theme)) +# expect_true(nchar(periscope_theme) > 0) +# unlink("www/periscope_style.yaml") +# unlink("www", recursive = TRUE) +# }) +# +# +# test_that("theme - invalid color", { +# local_edition(3) +# theme_settings <- yaml::read_yaml(system.file("fw_templ", "p_example", "periscope_style.yaml", package = "periscope2")) +# dir.create("www") +# theme_settings[["primary"]] <- "not color" +# theme_settings[["sidebar_width"]] <- "300" +# theme_settings[["control_sidebar_width"]] <- "-300" +# +# yaml::write_yaml(theme_settings, "www/periscope_style.yaml") +# theme_warnings <- capture_warnings(periscope2:::create_theme()) +# expect_snapshot(theme_warnings) +# unlink("www/periscope_style.yaml") +# unlink("www", recursive = TRUE) +# }) +# +# +# test_that("dashboard - create default dashboard", { +# expect_snapshot(periscope2:::create_application_dashboard()) +# }) +# +# +# test_that("add_ui_header - html title", { +# title <- "periscope Example Application" +# app_info <- HTML("Demonstrate periscope features and generated application layout") +# log_level <- "INFO" +# app_version <- "2.3.1" +# loading_indicator <- list(html = tagList(div("Loading ..."))) +# +# periscope2::set_app_parameters(app_info = app_info, +# log_level = log_level, +# app_version = app_version, +# loading_indicator = loading_indicator) +# # normal header +# skin <- "light" +# status <- "white" +# border <- TRUE +# compact <- FALSE +# left_sidebar_icon <- shiny::icon("bars") +# right_sidebar_icon <- shiny::icon("th") +# fixed <- FALSE +# left_menu <- NULL +# right_menu <- NULL +# +# periscope2::add_ui_header(title = title, +# left_menu = left_menu, +# right_menu = right_menu, +# skin = skin, +# status = status, +# border = border, +# compact = compact, +# left_sidebar_icon = left_sidebar_icon, +# right_sidebar_icon = right_sidebar_icon, +# fixed = fixed) +# +# +# expect_snapshot(shiny::isolate(periscope2:::.g_opts$app_info)) +# header <- shiny::isolate(periscope2:::.g_opts$header) +# expect_snapshot(header[[1]]) +# }) +# +# +# test_that("add_ui_header - url title", { +# announcements_file <- system.file("fw_templ", "announce.yaml", package = "periscope2") +# title <- "periscope Example Application" +# app_info <- "https://cran.r-project.org/web/packages/periscope2/index.html" +# log_level <- "DEBUG" +# app_version <- "2.3.1" +# loading_indicator <- list(html = tagList(div("Loading ..."))) +# +# set_app_parameters(app_info = app_info, +# log_level = log_level, +# app_version = app_version, +# loading_indicator = loading_indicator) +# # normal header +# skin <- "light" +# status <- "white" +# border <- TRUE +# compact <- FALSE +# left_sidebar_icon <- shiny::icon("bars") +# right_sidebar_icon <- shiny::icon("th") +# fixed <- FALSE +# left_menu <- NULL +# right_menu <- NULL +# +# periscope2::add_ui_header(title = title, +# left_menu = left_menu, +# right_menu = right_menu, +# skin = skin, +# status = status, +# border = border, +# compact = compact, +# left_sidebar_icon = left_sidebar_icon, +# right_sidebar_icon = right_sidebar_icon, +# fixed = fixed) +# +# expect_snapshot(shiny::isolate(periscope2:::.g_opts$app_info)) +# header <- shiny::isolate(periscope2:::.g_opts$header) +# expect_snapshot(header[[1]]) +# }) +# +# +# test_that("create alert - id and target error", { +# expect_error(createPSAlert(id = "test_id", selector = "test_selector", options = NULL), +# regexp = "Please choose either target or selector!") +# }) +# +# +# test_that("create alert - id", { +# expect_snapshot_output(createPSAlert(id = "test_id", session = MockShinySession$new(), options = NULL)) +# }) +# +# +# test_that("set_app_parameters update values", { +# announcements_file <- system.file("fw_templ", "announce.yaml", package = "periscope2") +# title <- "periscope Example Application" +# app_info <- HTML("Demonstrate periscope features and generated application layout") +# log_level <- "INFO" +# app_version <- "2.3.1" +# loading_indicator <- list(html = tagList(div("Loading ..."))) +# +# deprecated_flds_warn <- capture_warnings(set_app_parameters(title = title, +# app_info = app_info, +# log_level = log_level, +# app_version = app_version, +# loading_indicator = loading_indicator, +# announcements_file = announcements_file)) +# expect_snapshot(deprecated_flds_warn) +# expect_equal(shiny::isolate(periscope2:::.g_opts$app_title), title) +# expect_snapshot(shiny::isolate(periscope2:::.g_opts$app_info)) +# expect_equal(shiny::isolate(periscope2:::.g_opts$loglevel), log_level) +# expect_equal(shiny::isolate(periscope2:::.g_opts$app_version), app_version) +# expect_snapshot(shiny::isolate(periscope2:::.g_opts$loading_indicator)) +# expect_equal(shiny::isolate(periscope2:::.g_opts$announcements_file), announcements_file) +# expect_equal(load_announcements(), 30000) +# expect_equal(periscope2:::fw_get_loglevel(), log_level) +# expect_equal(periscope2:::fw_get_title(), title) +# expect_equal(periscope2:::fw_get_version(), app_version) +# }) From 4bb53b64fd46a851411fc305b44c5f1486caa611 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Wed, 27 Aug 2025 06:05:00 -0700 Subject: [PATCH 095/139] - Fixed UI functions --- tests/testthat/_snaps/ui_functions.md | 499 +++++++++++ tests/testthat/test_ui_functions.R | 1144 ++++++++++++------------- 2 files changed, 1071 insertions(+), 572 deletions(-) create mode 100644 tests/testthat/_snaps/ui_functions.md diff --git a/tests/testthat/_snaps/ui_functions.md b/tests/testthat/_snaps/ui_functions.md new file mode 100644 index 00000000..324f167d --- /dev/null +++ b/tests/testthat/_snaps/ui_functions.md @@ -0,0 +1,499 @@ +# add_ui_left_sidebar no left sidebar + + $disable + [1] TRUE + + +# add_ui_left_sidebar empty left sidebar + + [[1]] + [[1]][[1]] +
+ + [[1]][[2]] + NULL + + + $skin + [1] "light" + + $status + [1] "primary" + + $elevation + [1] 4 + + $collapsed + [1] FALSE + + $minified + [1] FALSE + + $expand_on_hover + [1] FALSE + + $fixed + [1] TRUE + + $custom_area + NULL + + [[10]] + NULL + + +# add_ui_left_sidebar example left sidebar + + [[1]] + [[1]][[1]] +
+ + [[1]][[2]] + NULL + + + $skin + [1] "light" + + $status + [1] "primary" + + $elevation + [1] 4 + + $collapsed + [1] FALSE + + $minified + [1] FALSE + + $expand_on_hover + [1] FALSE + + $fixed + [1] TRUE + + $custom_area + NULL + + +# add_ui_footer empty footer + +
+
+
+
+ +# add_ui_footer example footer + + + +# add_ui_body empty body + + Code + shiny::isolate(periscope2:::.g_opts$body_elements) + Output + [[1]] +
+ + [[2]] + + + [[3]] + NULL + + +# add_ui_body example body + + [[1]] +
+ + [[2]] + + + [[3]] + [[3]][[1]] +
+

periscope2: Enterprise Streamlined 'Shiny' Application Framework

+

+

+ periscope2 is a scalable and UI-standardized 'shiny' framework including a variety of developer convenience + functions with the goal of both streamlining robust application development and assisting in creating a consistent + user experience regardless of application or developer. +

+

+
+

+

+
Features
+
    +
  • Predefined but flexible template for new Shiny applications with a default dashboard layout
  • +
  • Separation by file of functionality that exists in one of the three shiny scopes: global, server-global, and server-local.
  • +
  • Off-the-shelf and ready to be used modules ('Announcements', 'Table Downloader', 'Plot Downloader', 'File Downloader', 'Application Logger' and 'Reset Application'
  • +
  • Different methods to notify user and add useful information about application UI and server operations
  • +
+
+

+ More +
+ + + +--- + + [[1]] +
+ + [[2]] + + + [[3]] +
more elements
+ + [[4]] + [[4]][[1]] +
+

periscope2: Enterprise Streamlined 'Shiny' Application Framework

+

+

+ periscope2 is a scalable and UI-standardized 'shiny' framework including a variety of developer convenience + functions with the goal of both streamlining robust application development and assisting in creating a consistent + user experience regardless of application or developer. +

+

+
+

+

+
Features
+
    +
  • Predefined but flexible template for new Shiny applications with a default dashboard layout
  • +
  • Separation by file of functionality that exists in one of the three shiny scopes: global, server-global, and server-local.
  • +
  • Off-the-shelf and ready to be used modules ('Announcements', 'Table Downloader', 'Plot Downloader', 'File Downloader', 'Application Logger' and 'Reset Application'
  • +
  • Different methods to notify user and add useful information about application UI and server operations
  • +
+
+

+ More +
+ + + +# add_ui_body append + + [[1]] +
+ + [[2]] + + + [[3]] +
more elements
+ + [[4]] +
append div
+ + [[5]] + [[5]][[1]] +
+

periscope2: Enterprise Streamlined 'Shiny' Application Framework

+

+

+ periscope2 is a scalable and UI-standardized 'shiny' framework including a variety of developer convenience + functions with the goal of both streamlining robust application development and assisting in creating a consistent + user experience regardless of application or developer. +

+

+
+

+

+
Features
+
    +
  • Predefined but flexible template for new Shiny applications with a default dashboard layout
  • +
  • Separation by file of functionality that exists in one of the three shiny scopes: global, server-global, and server-local.
  • +
  • Off-the-shelf and ready to be used modules ('Announcements', 'Table Downloader', 'Plot Downloader', 'File Downloader', 'Application Logger' and 'Reset Application'
  • +
  • Different methods to notify user and add useful information about application UI and server operations
  • +
+
+

+ More +
+ + + +# load_theme_settings - null settings + + Code + load_theme_settings() + Output + NULL + +# ui_tooltip + + + mylabel + + + +--- + + + mylabel2 + + + +--- + + 'arg' should be one of "top", "bottom", "left", "right" + +# theme - invalid color + + Code + theme_warnings + Output + [1] "primary has invalid color value. Setting default color." + [2] "-300 must be positive value. Setting default value." + +# dashboard - create default dashboard + + Code + periscope2:::create_application_dashboard() + Output + [[1]] +
+
+
+
+
+ + [[2]] +
+
+
+
+
+ + [[3]] +
+
+ +
+ + +
+
+
+ +
more elements
+
append div
+
+

periscope2: Enterprise Streamlined 'Shiny' Application Framework

+

+

+ periscope2 is a scalable and UI-standardized 'shiny' framework including a variety of developer convenience + functions with the goal of both streamlining robust application development and assisting in creating a consistent + user experience regardless of application or developer. +

+

+
+

+

+
Features
+
    +
  • Predefined but flexible template for new Shiny applications with a default dashboard layout
  • +
  • Separation by file of functionality that exists in one of the three shiny scopes: global, server-global, and server-local.
  • +
  • Off-the-shelf and ready to be used modules ('Announcements', 'Table Downloader', 'Plot Downloader', 'File Downloader', 'Application Logger' and 'Reset Application'
  • +
  • Different methods to notify user and add useful information about application UI and server operations
  • +
+
+

+ More +
+
+
+ + +
+ +
+
+ + +# add_ui_header - html title + + Code + shiny::isolate(periscope2:::.g_opts$app_info) + Output + Demonstrate periscope features and generated application layout + +--- + + Code + header[[1]] + Output + + +# add_ui_header - url title + + Code + shiny::isolate(periscope2:::.g_opts$app_info) + Output + [1] "https://cran.r-project.org/web/packages/periscope2/index.html" + +--- + + Code + header[[1]] + Output + + +# create alert - id + + + +# set_app_parameters update values + + Code + deprecated_flds_warn + Output + [1] "The `announcements_file` argument of `set_app_parameters()` is deprecated as of periscope2 0.2.3.\ni Please use `periscope2::load_announcements` instead" + [2] "The `title` argument of `set_app_parameters()` is deprecated as of periscope2 0.2.3.\ni Please use `periscope2::add_ui_header(title)` instead" + +--- + + Code + shiny::isolate(periscope2:::.g_opts$app_info) + Output + Demonstrate periscope features and generated application layout + +--- + + Code + shiny::isolate(periscope2:::.g_opts$loading_indicator) + Output + $html +
Loading ...
+ + diff --git a/tests/testthat/test_ui_functions.R b/tests/testthat/test_ui_functions.R index 46ff317b..45810c3f 100644 --- a/tests/testthat/test_ui_functions.R +++ b/tests/testthat/test_ui_functions.R @@ -60,575 +60,575 @@ test_that("add_ui_header - ui element", { ) # busy indicator - title - UI elements - # periscope2::add_ui_header(ui_elements = menu) - # header <- shiny::isolate(periscope2:::.g_opts$header) - # print(header[[1]]$children[[2]]) - # expect_equal(length(header), 2) - # expect_equal(length(header[[1]]), 3) - # expect_equal(length(header[[1]]$children), 3) - # expect_true(grepl('periscope-busy-ind.*Set using add_ui_header().*Tab1', header[[1]]$children[[2]])) - # - # # busy indicator - UI elements - title - # periscope2::add_ui_header(ui_elements = menu, - # ui_position = "center", - # title_position = "right") - # header <- shiny::isolate(periscope2:::.g_opts$header) - # print(header[[1]]$children[[2]]) - #expect_true(grepl('periscope-busy-ind.*Tab1.*Set using add_ui_header', header[[1]]$children[[2]])) -# -# # UI elements - busy indicator - title -# periscope2::add_ui_header(ui_elements = menu, -# ui_position = "left", -# title_position = "right") -# header <- shiny::isolate(periscope2:::.g_opts$header) -# expect_true(grepl('Tab1.*periscope-busy-ind.*Set using add_ui_header', header[[1]]$children[[2]])) -# -# # UI elements - title - busy indicator -# periscope2::add_ui_header(ui_elements = menu, -# ui_position = "left", -# title_position = "center") -# header <- shiny::isolate(periscope2:::.g_opts$header) -# expect_true(grepl('Tab1.*Set using add_ui_header.*periscope-busy-ind', header[[1]]$children[[2]])) -# -# # title - UI elements - busy indicator -# periscope2::add_ui_header(ui_elements = menu, -# ui_position = "center", -# title_position = "left") -# header <- shiny::isolate(periscope2:::.g_opts$header) -# expect_true(grepl('Set using add_ui_header.*Tab1.*periscope-busy-ind', header[[1]]$children[[2]])) -# -# # title - busy indicator - UI elements -# periscope2::add_ui_header(ui_elements = menu, -# ui_position = "right", -# title_position = "left") -# header <- shiny::isolate(periscope2:::.g_opts$header) -# expect_true(grepl('Set using add_ui_header.*periscope-busy-ind.*Tab1', header[[1]]$children[[2]])) -# -# # busy indicator - title - UI elements (center as well) -# expect_warning(periscope2::add_ui_header(ui_elements = menu, -# ui_position = "center"), -# regexp = "title_position cannot be equal to ui_position") -# -# header <- shiny::isolate(periscope2:::.g_opts$header) -# expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) -# -# # busy indicator - title - UI elements positions is NULL -# expect_warning(periscope2::add_ui_header(ui_elements = menu, -# ui_position = NULL), -# regexp = "ui_position must be on of 'left', 'center'or 'right' values. Setting default value 'right'") -# -# header <- shiny::isolate(periscope2:::.g_opts$header) -# expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) -# -# # busy indicator - title - UI elements positions is wrong -# expect_warning(periscope2::add_ui_header(ui_elements = menu, -# ui_position = "abc"), -# regexp = "ui_position must be on of 'left', 'center'or 'right' values. Setting default value 'right'") -# -# header <- shiny::isolate(periscope2:::.g_opts$header) -# expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) -# -# # busy indicator - title positions is wrong- UI elements -# expect_warning(periscope2::add_ui_header(ui_elements = menu, -# title_position = "abc"), -# regexp = "title_position must be on of 'left', 'center'or 'right' values. Setting default value 'center'") -# -# header <- shiny::isolate(periscope2:::.g_opts$header) -# expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) -# -# # busy indicator - title positions is NULL- UI elements -# expect_warning(periscope2::add_ui_header(ui_elements = menu, -# title_position = NULL), -# regexp = "title_position must be on of 'left', 'center'or 'right' values. Setting default value 'center'") -# -# header <- shiny::isolate(periscope2:::.g_opts$header) -# expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) -# -# # busy indicator - title positions is NULL- UI elements position is center -# warn_msgs <- capture_warnings(periscope2::add_ui_header(ui_elements = menu, -# ui_position = "center", -# title_position = NULL)) -# expect_equal("title_position must be on of 'left', 'center'or 'right' values. Setting default value 'center'", -# warn_msgs[1]) -# expect_equal("title_position cannot be equal to ui_position. Setting default values", -# warn_msgs[2]) -# header <- shiny::isolate(periscope2:::.g_opts$header) -# expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) -# -# # busy indicator - title positions is right- UI elements position is NULL -# warn_msgs <- capture_warnings(periscope2::add_ui_header(ui_elements = menu, -# ui_position = NULL, -# title = "Header Title", -# title_position = "right")) -# expect_equal(shiny::isolate(periscope2:::.g_opts$app_title), "Header Title") -# expect_equal("ui_position must be on of 'left', 'center'or 'right' values. Setting default value 'right'", -# warn_msgs[1]) -# expect_equal("title_position cannot be equal to ui_position. Setting default values", -# warn_msgs[2]) -# header <- shiny::isolate(periscope2:::.g_opts$header) -# expect_true(grepl('periscope-busy-ind.*Header Title.*Tab1', header[[1]]$children[[2]])) -}) - - -# test_that("add_ui_header - no ui element", { -# skin <- "light" -# status <- "white" -# border <- TRUE -# compact <- FALSE -# left_sidebar_icon <- shiny::icon("bars") -# right_sidebar_icon <- shiny::icon("th") -# fixed <- FALSE -# left_menu <- NULL -# right_menu <- NULL -# -# periscope2::add_ui_header(left_menu = left_menu, -# right_menu = right_menu, -# skin = skin, -# status = status, -# border = border, -# compact = compact, -# left_sidebar_icon = left_sidebar_icon, -# right_sidebar_icon = right_sidebar_icon, -# fixed = fixed, -# title = "good title") -# -# -# header <- shiny::isolate(periscope2:::.g_opts$header) -# expect_equal(length(header), 2) -# expect_true(grepl("good title", header[[1]], fixed = TRUE)) -# expect_null(header[[2]]) -# }) -# -# -# test_that("add_ui_left_sidebar no left sidebar", { -# expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$left_sidebar)) -# }) -# -# -# test_that("add_ui_left_sidebar empty left sidebar", { -# skin <- "light" -# status <- "primary" -# elevation <- 4 -# collapsed <- FALSE -# minified <- FALSE -# expand_on_hover <- FALSE -# fixed <- TRUE -# sidebar_elements <- NULL -# sidebar_menu <- NULL -# custom_area <- NULL -# add_ui_left_sidebar(sidebar_elements = sidebar_elements, -# skin = skin, -# status = status, -# elevation = elevation, -# collapsed = collapsed, -# minified = minified, -# expand_on_hover = expand_on_hover, -# fixed = fixed, -# sidebar_menu = sidebar_menu, -# custom_area = custom_area) -# expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$left_sidebar)) -# }) -# -# -# test_that("add_ui_left_sidebar example left sidebar", { -# skin <- "light" -# status <- "primary" -# elevation <- 4 -# collapsed <- FALSE -# minified <- FALSE -# expand_on_hover <- FALSE -# fixed <- TRUE -# sidebar_elements <- NULL -# sidebar_menu <- sidebarMenu( -# id = "features_id", -# sidebarHeader("Periscope2 Features"), -# menuItem( -# text = "Application Setup", -# tabName = "application_setup", -# icon = icon("building") -# ), -# menuItem( -# text = "Periscope2 Modules", -# tabName = "periscope_modules", -# icon = icon("cubes") -# ), -# menuItem( -# text = "User Notifications", -# tabName = "user_notifications", -# icon = icon("comments") -# ) -# ) -# custom_area <- NULL -# add_ui_left_sidebar(sidebar_elements = sidebar_elements, -# skin = skin, -# status = status, -# elevation = elevation, -# collapsed = collapsed, -# minified = minified, -# expand_on_hover = expand_on_hover, -# fixed = fixed, -# sidebar_menu = sidebar_menu, -# custom_area = custom_area) -# left_sidebar <- shiny::isolate(periscope2:::.g_opts$left_sidebar) -# expect_equal(length(left_sidebar), 10) -# expect_snapshot_output(left_sidebar[1:9]) -# expect_true(grepl('Application Setup', left_sidebar[[10]], fixed = TRUE)) -# expect_true(grepl('Periscope2 Modules', left_sidebar[[10]], fixed = TRUE)) -# expect_true(grepl('User Notifications', left_sidebar[[10]], fixed = TRUE)) -# }) -# -# -# test_that("add_ui_right_sidebar no right sidebar", { -# expect_null(shiny::isolate(periscope2:::.g_opts$right_sidebar)) -# }) -# -# -# test_that("add_ui_right_sidebar empty right sidebar", { -# collapsed <- TRUE -# overlay <- TRUE -# skin <- "light" -# pinned <- FALSE -# sidebar_elements <- NULL -# sidebar_menu <- NULL -# -# add_ui_right_sidebar(sidebar_elements = sidebar_elements, -# collapsed = collapsed, -# overlay = overlay, -# skin = skin, -# pinned = pinned, -# sidebar_menu = sidebar_menu) -# right_sidebar <- shiny::isolate(periscope2:::.g_opts$right_sidebar) -# expect_true(grepl('id="controlbarId"', right_sidebar, fixed = TRUE)) -# expect_true(grepl('id="sidebarRightAlert"', right_sidebar, fixed = TRUE)) -# }) -# -# -# test_that("add_ui_right_sidebar example right sidebar", { -# collapsed <- TRUE -# overlay <- TRUE -# skin <- "light" -# pinned <- FALSE -# sidebar_elements <- list(div(checkboxInput("hideFileOrganization", "Show Files Organization"), style = "margin-left:20px")) -# sidebar_menu <- NULL -# -# add_ui_right_sidebar(sidebar_elements = sidebar_elements, -# collapsed = collapsed, -# overlay = overlay, -# skin = skin, -# pinned = pinned, -# sidebar_menu = sidebar_menu) -# right_sidebar <- shiny::isolate(periscope2:::.g_opts$right_sidebar) -# expect_true(grepl('id="controlbarId"', right_sidebar, fixed = TRUE)) -# expect_true(grepl('id="sidebarRightAlert"', right_sidebar, fixed = TRUE)) -# expect_true(grepl('id="hideFileOrganization"', right_sidebar, fixed = TRUE)) -# expect_true(grepl('Show Files Organization', right_sidebar, fixed = TRUE)) -# }) -# -# -# test_that("add_ui_footer no footer", { -# expect_null(shiny::isolate(periscope2:::.g_opts$footer)) -# }) -# -# -# test_that("add_ui_footer empty footer", { -# right <- NULL -# fixed <- FALSE -# left <- NULL -# -# add_ui_footer(left, right, fixed) -# expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$footer)) -# }) -# -# -# test_that("add_ui_footer example footer", { -# right <- "2022" -# fixed <- FALSE -# left <- a( -# href = "https://periscopeapps.org/", -# target = "_blank", -# "periscope2") -# -# add_ui_footer(left, right, fixed) -# expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$footer)) -# }) -# -# -# test_that("add_ui_body empty body", { -# expect_equal(shiny::isolate(periscope2:::.g_opts$body_elements), c()) -# add_ui_body() -# expect_snapshot(shiny::isolate(periscope2:::.g_opts$body_elements)) -# }) -# -# -# test_that("add_ui_body example body", { -# about_box <- jumbotron( -# title = "periscope2: Enterprise Streamlined 'Shiny' Application Framework", -# lead = p("periscope2 is a scalable and UI-standardized 'shiny' framework including a variety of developer convenience", -# "functions with the goal of both streamlining robust application development and assisting in creating a consistent", -# " user experience regardless of application or developer."), -# tags$dl(tags$dt("Features"), -# tags$ul(tags$li("Predefined but flexible template for new Shiny applications with a default dashboard layout"), -# tags$li("Separation by file of functionality that exists in one of the three shiny scopes: global, server-global, and server-local."), -# tags$li("Off-the-shelf and ready to be used modules ('Announcements', 'Table Downloader', 'Plot Downloader', 'File Downloader', 'Application Logger' and 'Reset Application'"), -# tags$li("Different methods to notify user and add useful information about application UI and server operations"))), -# status = "info", -# href = "https://periscopeapps.org/" -# ) -# -# add_ui_body(list(about_box)) -# expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$body_elements)) -# -# add_ui_body(list(div("more elements")), append = TRUE) -# expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$body_elements)) -# dashboard_ui <- periscope2:::create_application_dashboard() -# expect_true(grepl('id="announceAlert"' , dashboard_ui[[1]], fixed = TRUE)) -# expect_true(grepl('id="headerAlert"' , dashboard_ui[[2]], fixed = TRUE)) -# expect_true(grepl('Periscope2 Features' , dashboard_ui[[3]], fixed = TRUE)) -# expect_true(grepl('id="sidebarRightAlert"' , dashboard_ui[[3]], fixed = TRUE)) -# expect_true(grepl('id="footerAlert"' , dashboard_ui[[3]], fixed = TRUE)) -# }) -# -# test_that("add_ui_body append", { -# add_ui_body(list(div("append div")), append = TRUE) -# expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$body_elements)) -# dashboard_ui <- periscope2:::create_application_dashboard() -# expect_true(grepl('id="announceAlert"' , dashboard_ui[[1]], fixed = TRUE)) -# expect_true(grepl('id="headerAlert"' , dashboard_ui[[2]], fixed = TRUE)) -# expect_true(grepl('Periscope2 Features' , dashboard_ui[[3]], fixed = TRUE)) -# expect_true(grepl('id="sidebarRightAlert"' , dashboard_ui[[3]], fixed = TRUE)) -# expect_true(grepl('id="footerAlert"' , dashboard_ui[[3]], fixed = TRUE)) -# }) -# -# -# test_that("load_announcements function params", { -# expect_equal(load_announcements(announcements_file_path = system.file("fw_templ", "announce.yaml", package = "periscope2")), 30000) -# }) -# -# -# test_that("load_announcements empty file", { -# # test empty announcement -# appTemp_dir <- tempdir() -# appTemp <- tempfile(pattern = "TestThatApp", tmpdir = appTemp_dir) -# announcements_file <- paste0(gsub('\\\\|/', '', (gsub(appTemp_dir, "", appTemp, fixed = TRUE))), ".yaml") -# yaml::write_yaml("", announcements_file) -# -# expect_null(load_announcements(announcements_file_path = announcements_file)) -# unlink(announcements_file, TRUE) -# }) -# -# test_that("load_announcements - parsing error", { -# # test empty announcement -# appTemp_dir <- tempdir() -# appTemp <- tempfile(pattern = "TestThatApp", tmpdir = appTemp_dir) -# announcements_file <- paste0(gsub('\\\\|/', '', (gsub(appTemp_dir, "", appTemp, fixed = TRUE))), ".yaml") -# cat(":", file = (con <- file(announcements_file, "w", encoding = "UTF-8"))) -# close(con) -# -# expect_warning(load_announcements(announcements_file_path = announcements_file), -# regexp = "[(Could not parse TestThatApp)]") -# unlink(announcements_file, TRUE) -# }) -# -# test_that("load_announcements function parameters", { -# expect_null(create_announcements(start_date = "2222-11-26", -# end_date = "2222-12-26")) -# expect_null(create_announcements(start_date = "2022-11-26", -# end_date = "2222-12-26", -# style = "not-style")) -# expect_null(create_announcements(start_date = "11-26-2222", -# end_date = "12-26-2222", -# start_date_format = "%m-%d-%Y", -# end_date_format = "%m-%d-%Y")) -# expect_null(create_announcements(start_date = "11-26-2222", -# end_date = "12-26-2222", -# end_date_format = "%m-%d-%Y")) -# expect_null(create_announcements(start_date = "11-26-2222", -# end_date = "12-26-2222", -# start_date_format = "%m-%d-%Y")) -# expect_null(create_announcements(start_date = "11-26-2222", -# start_date_format = "%m-%d-%Y")) -# expect_null(create_announcements(style = "info")) -# expect_null(create_announcements(style = "info", -# text = "text", -# auto_close = "abc")) -# }) -# -# test_that("load_theme_settings - null settings", { -# expect_snapshot(load_theme_settings()) -# }) -# -# -# test_that("ui_tooltip", { -# expect_snapshot_output(ui_tooltip(id = "id", label = "mylabel", text = "mytext")) -# expect_snapshot_output(ui_tooltip(id = "id2", label = "mylabel2", text = "mytext2", placement = "left")) -# expect_snapshot_error(ui_tooltip(id = "id2", label = "mylabel2", text = "mytext2", placement = "nowhere")) -# }) -# -# test_that("ui_tooltip no text", { -# expect_warning(ui_tooltip(id = "id", label = "mylabel", text = ""), "ui_tooltip\\() called without tooltip text.") -# }) -# -# -# test_that("theme - valid theme", { -# theme_settings <- yaml::read_yaml(system.file("fw_templ", "p_example", "periscope_style.yaml", package = "periscope2")) -# dir.create("www") -# yaml::write_yaml(theme_settings, "www/periscope_style.yaml") -# periscope_theme <- create_theme() -# expect_true(!is.null(periscope_theme)) -# expect_true(nchar(periscope_theme) > 0) -# unlink("www/periscope_style.yaml") -# unlink("www", recursive = TRUE) -# }) -# -# -# test_that("theme - parsing error", { -# dir.create("www") -# theme_file <- "www/periscope_style.yaml" -# cat(":", file = (con <- file(theme_file, "w", encoding = "UTF-8"))) -# close(con) -# periscope_theme <- suppressWarnings(periscope2:::create_theme()) -# expect_true(!is.null(periscope_theme)) -# expect_true(nchar(periscope_theme) > 0) -# unlink("www/periscope_style.yaml") -# unlink("www", recursive = TRUE) -# }) -# -# -# test_that("theme - invalid color", { -# local_edition(3) -# theme_settings <- yaml::read_yaml(system.file("fw_templ", "p_example", "periscope_style.yaml", package = "periscope2")) -# dir.create("www") -# theme_settings[["primary"]] <- "not color" -# theme_settings[["sidebar_width"]] <- "300" -# theme_settings[["control_sidebar_width"]] <- "-300" -# -# yaml::write_yaml(theme_settings, "www/periscope_style.yaml") -# theme_warnings <- capture_warnings(periscope2:::create_theme()) -# expect_snapshot(theme_warnings) -# unlink("www/periscope_style.yaml") -# unlink("www", recursive = TRUE) -# }) -# -# -# test_that("dashboard - create default dashboard", { -# expect_snapshot(periscope2:::create_application_dashboard()) -# }) -# -# -# test_that("add_ui_header - html title", { -# title <- "periscope Example Application" -# app_info <- HTML("Demonstrate periscope features and generated application layout") -# log_level <- "INFO" -# app_version <- "2.3.1" -# loading_indicator <- list(html = tagList(div("Loading ..."))) -# -# periscope2::set_app_parameters(app_info = app_info, -# log_level = log_level, -# app_version = app_version, -# loading_indicator = loading_indicator) -# # normal header -# skin <- "light" -# status <- "white" -# border <- TRUE -# compact <- FALSE -# left_sidebar_icon <- shiny::icon("bars") -# right_sidebar_icon <- shiny::icon("th") -# fixed <- FALSE -# left_menu <- NULL -# right_menu <- NULL -# -# periscope2::add_ui_header(title = title, -# left_menu = left_menu, -# right_menu = right_menu, -# skin = skin, -# status = status, -# border = border, -# compact = compact, -# left_sidebar_icon = left_sidebar_icon, -# right_sidebar_icon = right_sidebar_icon, -# fixed = fixed) -# -# -# expect_snapshot(shiny::isolate(periscope2:::.g_opts$app_info)) -# header <- shiny::isolate(periscope2:::.g_opts$header) -# expect_snapshot(header[[1]]) -# }) -# -# -# test_that("add_ui_header - url title", { -# announcements_file <- system.file("fw_templ", "announce.yaml", package = "periscope2") -# title <- "periscope Example Application" -# app_info <- "https://cran.r-project.org/web/packages/periscope2/index.html" -# log_level <- "DEBUG" -# app_version <- "2.3.1" -# loading_indicator <- list(html = tagList(div("Loading ..."))) -# -# set_app_parameters(app_info = app_info, -# log_level = log_level, -# app_version = app_version, -# loading_indicator = loading_indicator) -# # normal header -# skin <- "light" -# status <- "white" -# border <- TRUE -# compact <- FALSE -# left_sidebar_icon <- shiny::icon("bars") -# right_sidebar_icon <- shiny::icon("th") -# fixed <- FALSE -# left_menu <- NULL -# right_menu <- NULL -# -# periscope2::add_ui_header(title = title, -# left_menu = left_menu, -# right_menu = right_menu, -# skin = skin, -# status = status, -# border = border, -# compact = compact, -# left_sidebar_icon = left_sidebar_icon, -# right_sidebar_icon = right_sidebar_icon, -# fixed = fixed) -# -# expect_snapshot(shiny::isolate(periscope2:::.g_opts$app_info)) -# header <- shiny::isolate(periscope2:::.g_opts$header) -# expect_snapshot(header[[1]]) -# }) -# -# -# test_that("create alert - id and target error", { -# expect_error(createPSAlert(id = "test_id", selector = "test_selector", options = NULL), -# regexp = "Please choose either target or selector!") -# }) -# -# -# test_that("create alert - id", { -# expect_snapshot_output(createPSAlert(id = "test_id", session = MockShinySession$new(), options = NULL)) -# }) -# -# -# test_that("set_app_parameters update values", { -# announcements_file <- system.file("fw_templ", "announce.yaml", package = "periscope2") -# title <- "periscope Example Application" -# app_info <- HTML("Demonstrate periscope features and generated application layout") -# log_level <- "INFO" -# app_version <- "2.3.1" -# loading_indicator <- list(html = tagList(div("Loading ..."))) -# -# deprecated_flds_warn <- capture_warnings(set_app_parameters(title = title, -# app_info = app_info, -# log_level = log_level, -# app_version = app_version, -# loading_indicator = loading_indicator, -# announcements_file = announcements_file)) -# expect_snapshot(deprecated_flds_warn) -# expect_equal(shiny::isolate(periscope2:::.g_opts$app_title), title) -# expect_snapshot(shiny::isolate(periscope2:::.g_opts$app_info)) -# expect_equal(shiny::isolate(periscope2:::.g_opts$loglevel), log_level) -# expect_equal(shiny::isolate(periscope2:::.g_opts$app_version), app_version) -# expect_snapshot(shiny::isolate(periscope2:::.g_opts$loading_indicator)) -# expect_equal(shiny::isolate(periscope2:::.g_opts$announcements_file), announcements_file) -# expect_equal(load_announcements(), 30000) -# expect_equal(periscope2:::fw_get_loglevel(), log_level) -# expect_equal(periscope2:::fw_get_title(), title) -# expect_equal(periscope2:::fw_get_version(), app_version) -# }) + periscope2::add_ui_header(ui_elements = menu) + header <- shiny::isolate(periscope2:::.g_opts$header) + print(header[[1]]$children[[2]]) + expect_equal(length(header), 2) + expect_equal(length(header[[1]]), 3) + expect_equal(length(header[[1]]$children), 3) + expect_true(grepl('periscope-busy-ind.*Set using add_ui_header().*Tab1', header[[1]]$children[[2]])) + +# busy indicator - UI elements - title +periscope2::add_ui_header(ui_elements = menu, + ui_position = "center", + title_position = "right") +header <- shiny::isolate(periscope2:::.g_opts$header) +print(header[[1]]$children[[2]]) +expect_true(grepl('periscope-busy-ind.*Tab1.*Set using add_ui_header', header[[1]]$children[[2]])) + + # UI elements - busy indicator - title + periscope2::add_ui_header(ui_elements = menu, + ui_position = "left", + title_position = "right") + header <- shiny::isolate(periscope2:::.g_opts$header) + expect_true(grepl('Tab1.*periscope-busy-ind.*Set using add_ui_header', header[[1]]$children[[2]])) + + # UI elements - title - busy indicator + periscope2::add_ui_header(ui_elements = menu, + ui_position = "left", + title_position = "center") + header <- shiny::isolate(periscope2:::.g_opts$header) + expect_true(grepl('Tab1.*Set using add_ui_header.*periscope-busy-ind', header[[1]]$children[[2]])) + + # title - UI elements - busy indicator + periscope2::add_ui_header(ui_elements = menu, + ui_position = "center", + title_position = "left") + header <- shiny::isolate(periscope2:::.g_opts$header) + expect_true(grepl('Set using add_ui_header.*Tab1.*periscope-busy-ind', header[[1]]$children[[2]])) + + # title - busy indicator - UI elements + periscope2::add_ui_header(ui_elements = menu, + ui_position = "right", + title_position = "left") + header <- shiny::isolate(periscope2:::.g_opts$header) + expect_true(grepl('Set using add_ui_header.*periscope-busy-ind.*Tab1', header[[1]]$children[[2]])) + + # busy indicator - title - UI elements (center as well) + expect_warning(periscope2::add_ui_header(ui_elements = menu, + ui_position = "center"), + regexp = "title_position cannot be equal to ui_position") + + header <- shiny::isolate(periscope2:::.g_opts$header) + expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) + + # busy indicator - title - UI elements positions is NULL + expect_warning(periscope2::add_ui_header(ui_elements = menu, + ui_position = NULL), + regexp = "ui_position must be on of 'left', 'center'or 'right' values. Setting default value 'right'") + + header <- shiny::isolate(periscope2:::.g_opts$header) + expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) + + # busy indicator - title - UI elements positions is wrong + expect_warning(periscope2::add_ui_header(ui_elements = menu, + ui_position = "abc"), + regexp = "ui_position must be on of 'left', 'center'or 'right' values. Setting default value 'right'") + + header <- shiny::isolate(periscope2:::.g_opts$header) + expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) + + # busy indicator - title positions is wrong- UI elements + expect_warning(periscope2::add_ui_header(ui_elements = menu, + title_position = "abc"), + regexp = "title_position must be on of 'left', 'center'or 'right' values. Setting default value 'center'") + + header <- shiny::isolate(periscope2:::.g_opts$header) + expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) + + # busy indicator - title positions is NULL- UI elements + expect_warning(periscope2::add_ui_header(ui_elements = menu, + title_position = NULL), + regexp = "title_position must be on of 'left', 'center'or 'right' values. Setting default value 'center'") + + header <- shiny::isolate(periscope2:::.g_opts$header) + expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) + + # busy indicator - title positions is NULL- UI elements position is center + warn_msgs <- capture_warnings(periscope2::add_ui_header(ui_elements = menu, + ui_position = "center", + title_position = NULL)) + expect_equal("title_position must be on of 'left', 'center'or 'right' values. Setting default value 'center'", + warn_msgs[1]) + expect_equal("title_position cannot be equal to ui_position. Setting default values", + warn_msgs[2]) + header <- shiny::isolate(periscope2:::.g_opts$header) + expect_true(grepl('periscope-busy-ind.*Set using add_ui_header.*Tab1', header[[1]]$children[[2]])) + + # busy indicator - title positions is right- UI elements position is NULL + warn_msgs <- capture_warnings(periscope2::add_ui_header(ui_elements = menu, + ui_position = NULL, + title = "Header Title", + title_position = "right")) + expect_equal(shiny::isolate(periscope2:::.g_opts$app_title), "Header Title") + expect_equal("ui_position must be on of 'left', 'center'or 'right' values. Setting default value 'right'", + warn_msgs[1]) + expect_equal("title_position cannot be equal to ui_position. Setting default values", + warn_msgs[2]) + header <- shiny::isolate(periscope2:::.g_opts$header) + expect_true(grepl('periscope-busy-ind.*Header Title.*Tab1', header[[1]]$children[[2]])) +}) + + +test_that("add_ui_header - no ui element", { + skin <- "light" + status <- "white" + border <- TRUE + compact <- FALSE + left_sidebar_icon <- shiny::icon("bars") + right_sidebar_icon <- shiny::icon("th") + fixed <- FALSE + left_menu <- NULL + right_menu <- NULL + + periscope2::add_ui_header(left_menu = left_menu, + right_menu = right_menu, + skin = skin, + status = status, + border = border, + compact = compact, + left_sidebar_icon = left_sidebar_icon, + right_sidebar_icon = right_sidebar_icon, + fixed = fixed, + title = "good title") + + + header <- shiny::isolate(periscope2:::.g_opts$header) + expect_equal(length(header), 2) + expect_true(grepl("good title", header[[1]], fixed = TRUE)) + expect_null(header[[2]]) +}) + + +test_that("add_ui_left_sidebar no left sidebar", { + expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$left_sidebar)) +}) + + +test_that("add_ui_left_sidebar empty left sidebar", { + skin <- "light" + status <- "primary" + elevation <- 4 + collapsed <- FALSE + minified <- FALSE + expand_on_hover <- FALSE + fixed <- TRUE + sidebar_elements <- NULL + sidebar_menu <- NULL + custom_area <- NULL + add_ui_left_sidebar(sidebar_elements = sidebar_elements, + skin = skin, + status = status, + elevation = elevation, + collapsed = collapsed, + minified = minified, + expand_on_hover = expand_on_hover, + fixed = fixed, + sidebar_menu = sidebar_menu, + custom_area = custom_area) + expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$left_sidebar)) +}) + + +test_that("add_ui_left_sidebar example left sidebar", { + skin <- "light" + status <- "primary" + elevation <- 4 + collapsed <- FALSE + minified <- FALSE + expand_on_hover <- FALSE + fixed <- TRUE + sidebar_elements <- NULL + sidebar_menu <- sidebarMenu( + id = "features_id", + sidebarHeader("Periscope2 Features"), + menuItem( + text = "Application Setup", + tabName = "application_setup", + icon = icon("building") + ), + menuItem( + text = "Periscope2 Modules", + tabName = "periscope_modules", + icon = icon("cubes") + ), + menuItem( + text = "User Notifications", + tabName = "user_notifications", + icon = icon("comments") + ) + ) + custom_area <- NULL + add_ui_left_sidebar(sidebar_elements = sidebar_elements, + skin = skin, + status = status, + elevation = elevation, + collapsed = collapsed, + minified = minified, + expand_on_hover = expand_on_hover, + fixed = fixed, + sidebar_menu = sidebar_menu, + custom_area = custom_area) + left_sidebar <- shiny::isolate(periscope2:::.g_opts$left_sidebar) + expect_equal(length(left_sidebar), 10) + expect_snapshot_output(left_sidebar[1:9]) + expect_true(grepl('Application Setup', left_sidebar[[10]], fixed = TRUE)) + expect_true(grepl('Periscope2 Modules', left_sidebar[[10]], fixed = TRUE)) + expect_true(grepl('User Notifications', left_sidebar[[10]], fixed = TRUE)) +}) + + +test_that("add_ui_right_sidebar no right sidebar", { + expect_null(shiny::isolate(periscope2:::.g_opts$right_sidebar)) +}) + + +test_that("add_ui_right_sidebar empty right sidebar", { + collapsed <- TRUE + overlay <- TRUE + skin <- "light" + pinned <- FALSE + sidebar_elements <- NULL + sidebar_menu <- NULL + + add_ui_right_sidebar(sidebar_elements = sidebar_elements, + collapsed = collapsed, + overlay = overlay, + skin = skin, + pinned = pinned, + sidebar_menu = sidebar_menu) + right_sidebar <- shiny::isolate(periscope2:::.g_opts$right_sidebar) + expect_true(grepl('id="controlbarId"', right_sidebar, fixed = TRUE)) + expect_true(grepl('id="sidebarRightAlert"', right_sidebar, fixed = TRUE)) +}) + + +test_that("add_ui_right_sidebar example right sidebar", { + collapsed <- TRUE + overlay <- TRUE + skin <- "light" + pinned <- FALSE + sidebar_elements <- list(div(checkboxInput("hideFileOrganization", "Show Files Organization"), style = "margin-left:20px")) + sidebar_menu <- NULL + + add_ui_right_sidebar(sidebar_elements = sidebar_elements, + collapsed = collapsed, + overlay = overlay, + skin = skin, + pinned = pinned, + sidebar_menu = sidebar_menu) + right_sidebar <- shiny::isolate(periscope2:::.g_opts$right_sidebar) + expect_true(grepl('id="controlbarId"', right_sidebar, fixed = TRUE)) + expect_true(grepl('id="sidebarRightAlert"', right_sidebar, fixed = TRUE)) + expect_true(grepl('id="hideFileOrganization"', right_sidebar, fixed = TRUE)) + expect_true(grepl('Show Files Organization', right_sidebar, fixed = TRUE)) +}) + + +test_that("add_ui_footer no footer", { + expect_null(shiny::isolate(periscope2:::.g_opts$footer)) +}) + + +test_that("add_ui_footer empty footer", { + right <- NULL + fixed <- FALSE + left <- NULL + + add_ui_footer(left, right, fixed) + expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$footer)) +}) + + +test_that("add_ui_footer example footer", { + right <- "2022" + fixed <- FALSE + left <- a( + href = "https://periscopeapps.org/", + target = "_blank", + "periscope2") + + add_ui_footer(left, right, fixed) + expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$footer)) +}) + + +test_that("add_ui_body empty body", { + expect_equal(shiny::isolate(periscope2:::.g_opts$body_elements), c()) + add_ui_body() + expect_snapshot(shiny::isolate(periscope2:::.g_opts$body_elements)) +}) + + +test_that("add_ui_body example body", { + about_box <- jumbotron( + title = "periscope2: Enterprise Streamlined 'Shiny' Application Framework", + lead = p("periscope2 is a scalable and UI-standardized 'shiny' framework including a variety of developer convenience", + "functions with the goal of both streamlining robust application development and assisting in creating a consistent", + " user experience regardless of application or developer."), + tags$dl(tags$dt("Features"), + tags$ul(tags$li("Predefined but flexible template for new Shiny applications with a default dashboard layout"), + tags$li("Separation by file of functionality that exists in one of the three shiny scopes: global, server-global, and server-local."), + tags$li("Off-the-shelf and ready to be used modules ('Announcements', 'Table Downloader', 'Plot Downloader', 'File Downloader', 'Application Logger' and 'Reset Application'"), + tags$li("Different methods to notify user and add useful information about application UI and server operations"))), + status = "info", + href = "https://periscopeapps.org/" + ) + + add_ui_body(list(about_box)) + expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$body_elements)) + + add_ui_body(list(div("more elements")), append = TRUE) + expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$body_elements)) + dashboard_ui <- periscope2:::create_application_dashboard() + expect_true(grepl('id="announceAlert"' , dashboard_ui[[1]], fixed = TRUE)) + expect_true(grepl('id="headerAlert"' , dashboard_ui[[2]], fixed = TRUE)) + expect_true(grepl('Periscope2 Features' , dashboard_ui[[3]], fixed = TRUE)) + expect_true(grepl('id="sidebarRightAlert"' , dashboard_ui[[3]], fixed = TRUE)) + expect_true(grepl('id="footerAlert"' , dashboard_ui[[3]], fixed = TRUE)) +}) + +test_that("add_ui_body append", { + add_ui_body(list(div("append div")), append = TRUE) + expect_snapshot_output(shiny::isolate(periscope2:::.g_opts$body_elements)) + dashboard_ui <- periscope2:::create_application_dashboard() + expect_true(grepl('id="announceAlert"' , dashboard_ui[[1]], fixed = TRUE)) + expect_true(grepl('id="headerAlert"' , dashboard_ui[[2]], fixed = TRUE)) + expect_true(grepl('Periscope2 Features' , dashboard_ui[[3]], fixed = TRUE)) + expect_true(grepl('id="sidebarRightAlert"' , dashboard_ui[[3]], fixed = TRUE)) + expect_true(grepl('id="footerAlert"' , dashboard_ui[[3]], fixed = TRUE)) +}) + + +test_that("load_announcements function params", { + expect_equal(load_announcements(announcements_file_path = system.file("fw_templ", "announce.yaml", package = "periscope2")), 30000) +}) + + +test_that("load_announcements empty file", { + # test empty announcement + appTemp_dir <- tempdir() + appTemp <- tempfile(pattern = "TestThatApp", tmpdir = appTemp_dir) + announcements_file <- paste0(gsub('\\\\|/', '', (gsub(appTemp_dir, "", appTemp, fixed = TRUE))), ".yaml") + yaml::write_yaml("", announcements_file) + + expect_null(load_announcements(announcements_file_path = announcements_file)) + unlink(announcements_file, TRUE) +}) + +test_that("load_announcements - parsing error", { + # test empty announcement + appTemp_dir <- tempdir() + appTemp <- tempfile(pattern = "TestThatApp", tmpdir = appTemp_dir) + announcements_file <- paste0(gsub('\\\\|/', '', (gsub(appTemp_dir, "", appTemp, fixed = TRUE))), ".yaml") + cat(":", file = (con <- file(announcements_file, "w", encoding = "UTF-8"))) + close(con) + + expect_warning(load_announcements(announcements_file_path = announcements_file), + regexp = "[(Could not parse TestThatApp)]") + unlink(announcements_file, TRUE) +}) + +test_that("load_announcements function parameters", { + expect_null(create_announcements(start_date = "2222-11-26", + end_date = "2222-12-26")) + expect_null(create_announcements(start_date = "2022-11-26", + end_date = "2222-12-26", + style = "not-style")) + expect_null(create_announcements(start_date = "11-26-2222", + end_date = "12-26-2222", + start_date_format = "%m-%d-%Y", + end_date_format = "%m-%d-%Y")) + expect_null(create_announcements(start_date = "11-26-2222", + end_date = "12-26-2222", + end_date_format = "%m-%d-%Y")) + expect_null(create_announcements(start_date = "11-26-2222", + end_date = "12-26-2222", + start_date_format = "%m-%d-%Y")) + expect_null(create_announcements(start_date = "11-26-2222", + start_date_format = "%m-%d-%Y")) + expect_null(create_announcements(style = "info")) + expect_null(create_announcements(style = "info", + text = "text", + auto_close = "abc")) +}) + +test_that("load_theme_settings - null settings", { + expect_snapshot(load_theme_settings()) +}) + + +test_that("ui_tooltip", { + expect_snapshot_output(ui_tooltip(id = "id", label = "mylabel", text = "mytext")) + expect_snapshot_output(ui_tooltip(id = "id2", label = "mylabel2", text = "mytext2", placement = "left")) + expect_snapshot_error(ui_tooltip(id = "id2", label = "mylabel2", text = "mytext2", placement = "nowhere")) +}) + +test_that("ui_tooltip no text", { + expect_warning(ui_tooltip(id = "id", label = "mylabel", text = ""), "ui_tooltip\\() called without tooltip text.") +}) + + +test_that("theme - valid theme", { + theme_settings <- yaml::read_yaml(system.file("fw_templ", "p_example", "periscope_style.yaml", package = "periscope2")) + dir.create("www") + yaml::write_yaml(theme_settings, "www/periscope_style.yaml") + periscope_theme <- create_theme() + expect_true(!is.null(periscope_theme)) + expect_true(nchar(periscope_theme) > 0) + unlink("www/periscope_style.yaml") + unlink("www", recursive = TRUE) +}) + + +test_that("theme - parsing error", { + dir.create("www") + theme_file <- "www/periscope_style.yaml" + cat(":", file = (con <- file(theme_file, "w", encoding = "UTF-8"))) + close(con) + periscope_theme <- suppressWarnings(periscope2:::create_theme()) + expect_true(!is.null(periscope_theme)) + expect_true(nchar(periscope_theme) > 0) + unlink("www/periscope_style.yaml") + unlink("www", recursive = TRUE) +}) + + +test_that("theme - invalid color", { + local_edition(3) + theme_settings <- yaml::read_yaml(system.file("fw_templ", "p_example", "periscope_style.yaml", package = "periscope2")) + dir.create("www") + theme_settings[["primary"]] <- "not color" + theme_settings[["sidebar_width"]] <- "300" + theme_settings[["control_sidebar_width"]] <- "-300" + + yaml::write_yaml(theme_settings, "www/periscope_style.yaml") + theme_warnings <- capture_warnings(periscope2:::create_theme()) + expect_snapshot(theme_warnings) + unlink("www/periscope_style.yaml") + unlink("www", recursive = TRUE) +}) + + +test_that("dashboard - create default dashboard", { + expect_snapshot(periscope2:::create_application_dashboard()) +}) + + +test_that("add_ui_header - html title", { + title <- "periscope Example Application" + app_info <- HTML("Demonstrate periscope features and generated application layout") + log_level <- "INFO" + app_version <- "2.3.1" + loading_indicator <- list(html = tagList(div("Loading ..."))) + + periscope2::set_app_parameters(app_info = app_info, + log_level = log_level, + app_version = app_version, + loading_indicator = loading_indicator) + # normal header + skin <- "light" + status <- "white" + border <- TRUE + compact <- FALSE + left_sidebar_icon <- shiny::icon("bars") + right_sidebar_icon <- shiny::icon("th") + fixed <- FALSE + left_menu <- NULL + right_menu <- NULL + + periscope2::add_ui_header(title = title, + left_menu = left_menu, + right_menu = right_menu, + skin = skin, + status = status, + border = border, + compact = compact, + left_sidebar_icon = left_sidebar_icon, + right_sidebar_icon = right_sidebar_icon, + fixed = fixed) + + + expect_snapshot(shiny::isolate(periscope2:::.g_opts$app_info)) + header <- shiny::isolate(periscope2:::.g_opts$header) + expect_snapshot(header[[1]]) +}) + + +test_that("add_ui_header - url title", { + announcements_file <- system.file("fw_templ", "announce.yaml", package = "periscope2") + title <- "periscope Example Application" + app_info <- "https://cran.r-project.org/web/packages/periscope2/index.html" + log_level <- "DEBUG" + app_version <- "2.3.1" + loading_indicator <- list(html = tagList(div("Loading ..."))) + + set_app_parameters(app_info = app_info, + log_level = log_level, + app_version = app_version, + loading_indicator = loading_indicator) + # normal header + skin <- "light" + status <- "white" + border <- TRUE + compact <- FALSE + left_sidebar_icon <- shiny::icon("bars") + right_sidebar_icon <- shiny::icon("th") + fixed <- FALSE + left_menu <- NULL + right_menu <- NULL + + periscope2::add_ui_header(title = title, + left_menu = left_menu, + right_menu = right_menu, + skin = skin, + status = status, + border = border, + compact = compact, + left_sidebar_icon = left_sidebar_icon, + right_sidebar_icon = right_sidebar_icon, + fixed = fixed) + + expect_snapshot(shiny::isolate(periscope2:::.g_opts$app_info)) + header <- shiny::isolate(periscope2:::.g_opts$header) + expect_snapshot(header[[1]]) +}) + + +test_that("create alert - id and target error", { + expect_error(createPSAlert(id = "test_id", selector = "test_selector", options = NULL), + regexp = "Please choose either target or selector!") +}) + + +test_that("create alert - id", { + expect_snapshot_output(createPSAlert(id = "test_id", session = MockShinySession$new(), options = NULL)) +}) + + +test_that("set_app_parameters update values", { + announcements_file <- system.file("fw_templ", "announce.yaml", package = "periscope2") + title <- "periscope Example Application" + app_info <- HTML("Demonstrate periscope features and generated application layout") + log_level <- "INFO" + app_version <- "2.3.1" + loading_indicator <- list(html = tagList(div("Loading ..."))) + + deprecated_flds_warn <- capture_warnings(set_app_parameters(title = title, + app_info = app_info, + log_level = log_level, + app_version = app_version, + loading_indicator = loading_indicator, + announcements_file = announcements_file)) + expect_snapshot(deprecated_flds_warn) + expect_equal(shiny::isolate(periscope2:::.g_opts$app_title), title) + expect_snapshot(shiny::isolate(periscope2:::.g_opts$app_info)) + expect_equal(shiny::isolate(periscope2:::.g_opts$loglevel), log_level) + expect_equal(shiny::isolate(periscope2:::.g_opts$app_version), app_version) + expect_snapshot(shiny::isolate(periscope2:::.g_opts$loading_indicator)) + expect_equal(shiny::isolate(periscope2:::.g_opts$announcements_file), announcements_file) + expect_equal(load_announcements(), 30000) + expect_equal(periscope2:::fw_get_loglevel(), log_level) + expect_equal(periscope2:::fw_get_title(), title) + expect_equal(periscope2:::fw_get_version(), app_version) +}) From 0baa8399ed46dcdf85b06db37e2a8beb25230c82 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Wed, 27 Aug 2025 07:54:33 -0700 Subject: [PATCH 096/139] - Increased ui helpers test coverage --- tests/testthat/test_ui_functions.R | 76 +++++++++++++++++++----------- 1 file changed, 49 insertions(+), 27 deletions(-) diff --git a/tests/testthat/test_ui_functions.R b/tests/testthat/test_ui_functions.R index 45810c3f..34d53151 100644 --- a/tests/testthat/test_ui_functions.R +++ b/tests/testthat/test_ui_functions.R @@ -62,19 +62,17 @@ test_that("add_ui_header - ui element", { # busy indicator - title - UI elements periscope2::add_ui_header(ui_elements = menu) header <- shiny::isolate(periscope2:::.g_opts$header) - print(header[[1]]$children[[2]]) expect_equal(length(header), 2) expect_equal(length(header[[1]]), 3) expect_equal(length(header[[1]]$children), 3) expect_true(grepl('periscope-busy-ind.*Set using add_ui_header().*Tab1', header[[1]]$children[[2]])) -# busy indicator - UI elements - title -periscope2::add_ui_header(ui_elements = menu, - ui_position = "center", - title_position = "right") -header <- shiny::isolate(periscope2:::.g_opts$header) -print(header[[1]]$children[[2]]) -expect_true(grepl('periscope-busy-ind.*Tab1.*Set using add_ui_header', header[[1]]$children[[2]])) + # busy indicator - UI elements - title + periscope2::add_ui_header(ui_elements = menu, + ui_position = "center", + title_position = "right") + header <- shiny::isolate(periscope2:::.g_opts$header) + expect_true(grepl('periscope-busy-ind.*Tab1.*Set using add_ui_header', header[[1]]$children[[2]])) # UI elements - busy indicator - title periscope2::add_ui_header(ui_elements = menu, @@ -412,7 +410,8 @@ test_that("load_announcements empty file", { announcements_file <- paste0(gsub('\\\\|/', '', (gsub(appTemp_dir, "", appTemp, fixed = TRUE))), ".yaml") yaml::write_yaml("", announcements_file) - expect_null(load_announcements(announcements_file_path = announcements_file)) + output_msg <- capture_output(expect_null(load_announcements(announcements_file_path = announcements_file))) + expect_true(grepl('Announcements will be ignored', output_msg, fixed = TRUE)) unlink(announcements_file, TRUE) }) @@ -424,33 +423,56 @@ test_that("load_announcements - parsing error", { cat(":", file = (con <- file(announcements_file, "w", encoding = "UTF-8"))) close(con) - expect_warning(load_announcements(announcements_file_path = announcements_file), - regexp = "[(Could not parse TestThatApp)]") + output_msg <- capture_output(expect_warning(load_announcements(announcements_file_path = announcements_file), + regexp = "[(Could not parse TestThatApp)]")) + expect_true(grepl('Parser error', output_msg, fixed = TRUE)) unlink(announcements_file, TRUE) }) test_that("load_announcements function parameters", { expect_null(create_announcements(start_date = "2222-11-26", end_date = "2222-12-26")) - expect_null(create_announcements(start_date = "2022-11-26", - end_date = "2222-12-26", - style = "not-style")) - expect_null(create_announcements(start_date = "11-26-2222", + output_msg <- capture_output(expect_null( + create_announcements(start_date = "2022-11-26", + end_date = "2222-12-26", + style = "not-style"))) + expect_true(grepl("Announcement 'style' must be one of info, danger, success, warning, primary", + output_msg, fixed = TRUE)) + expect_null( + create_announcements(start_date = "11-26-2222", + end_date = "12-26-2222", + start_date_format = "%m-%d-%Y", + end_date_format = "%m-%d-%Y")) + + output_msg <- capture_output(expect_null( + create_announcements(start_date = "11-26-2222", + end_date = "12-26-2222", + end_date_format = "%m-%d-%Y"))) + expect_true(grepl("All formats failed to parse. No formats found", + output_msg, fixed = TRUE)) + + output_msg <- capture_output(expect_null( + create_announcements(start_date = "11-26-2222", end_date = "12-26-2222", - start_date_format = "%m-%d-%Y", - end_date_format = "%m-%d-%Y")) - expect_null(create_announcements(start_date = "11-26-2222", - end_date = "12-26-2222", - end_date_format = "%m-%d-%Y")) - expect_null(create_announcements(start_date = "11-26-2222", - end_date = "12-26-2222", - start_date_format = "%m-%d-%Y")) + start_date_format = "%m-%d-%Y"))) + expect_true(grepl("All formats failed to parse. No formats found", + output_msg, fixed = TRUE)) + expect_null(create_announcements(start_date = "11-26-2222", start_date_format = "%m-%d-%Y")) - expect_null(create_announcements(style = "info")) - expect_null(create_announcements(style = "info", - text = "text", - auto_close = "abc")) + expect_true(grepl("All formats failed to parse. No formats found", + output_msg, fixed = TRUE)) + + output_msg <- capture_output(expect_null(create_announcements(style = "info"))) + expect_true(grepl("Announcement 'text' value is empty. It must contain non empty text value", + output_msg, fixed = TRUE)) + + output_msg <- capture_output(expect_null( + create_announcements(style = "info", + text = "text", + auto_close = "abc"))) + expect_true(grepl("Announcement 'auto_close' value ' abc ' is invalid", + output_msg, fixed = TRUE)) }) test_that("load_theme_settings - null settings", { From 1754cd2f24aaa5d23599f96667e7db56a8933c71 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Wed, 27 Aug 2025 08:29:03 -0700 Subject: [PATCH 097/139] - Increased logger test coverage --- tests/testthat/test_logger.R | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/testthat/test_logger.R b/tests/testthat/test_logger.R index b6d9a3c9..c974e778 100644 --- a/tests/testthat/test_logger.R +++ b/tests/testthat/test_logger.R @@ -254,10 +254,10 @@ test_that("File logging level DEBUG", { set_app_parameters(log_level = "DEBUG") periscope2:::addHandler(writeToFile, file = test_file_log) - logdebug("debug message") - loginfo("info message") - logwarn("warn message") - logerror("error message") + expect_output(logdebug("debug message"), "debug message") + expect_output(loginfo("info message"), "info message") + expect_output(logwarn("warn message"), "warn message") + expect_output(logerror("error message"), "error message") log_content <- readLines(test_file_log) expect_true(any(grepl("debug message", log_content))) @@ -272,9 +272,9 @@ test_that("File logging level INFO", { periscope2:::addHandler(writeToFile, file = test_file_log) logdebug("debug message") - loginfo("info message") - logwarn("warn message") - logerror("error message") + expect_output(loginfo("info message"), "info message") + expect_output(logwarn("warn message"), "warn message") + expect_output(logerror("error message"), "error message") log_content <- readLines(test_file_log) expect_false(any(grepl("debug message", log_content))) @@ -290,8 +290,8 @@ test_that("File logging level WARNING", { logdebug("debug message") loginfo("info message") - logwarn("warn message") - logerror("error message") + expect_output(logwarn("warn message"), "warn message") + expect_output(logerror("error message"), "error message") log_content <- readLines(test_file_log) expect_false(any(grepl("debug message", log_content))) @@ -308,7 +308,7 @@ test_that("File logging level ERROR", { logdebug("debug message") loginfo("info message") logwarn("warn message") - logerror("error message") + expect_output(logerror("error message"), "error message") log_content <- readLines(test_file_log) expect_false(any(grepl("debug message", log_content))) From 055ae3500e832acbeccc6f452c68be7e067095eb Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Wed, 27 Aug 2025 21:35:03 -0700 Subject: [PATCH 098/139] - Updated NEWS and RD files --- NEWS.md | 2 +- man/set_app_parameters.Rd | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index 1dd472f3..0ebe3107 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,7 @@ ## Enhancements - Adding openxlsx2 support while keeping openxlsx to support legacy apps and backwards compatibility. writexl package is used as last resort. - Updated *?logViewerOutput* module to display log data in downloadableReactTable. +- Added loglevel filtering according to level chosen- Added loglevel filtering according to level chosen ----- @@ -16,7 +17,6 @@ - Updated `set_app_parameters` method documentation to displayed html tags correctly - Updated package documentation to fix `docType` deprecation warning - Updated `logger` module internal documentation -- Added loglevel filtering according to level chosen ----- diff --git a/man/set_app_parameters.Rd b/man/set_app_parameters.Rd index 64594ceb..80f99ca6 100644 --- a/man/set_app_parameters.Rd +++ b/man/set_app_parameters.Rd @@ -7,7 +7,7 @@ set_app_parameters( title = NULL, app_info = NULL, - log_level = c("DEBUG", "INFO", "WARNING", "ERROR"), + log_level = "DEBUG", app_version = "1.0.0", loading_indicator = NULL, announcements_file = NULL @@ -29,7 +29,7 @@ application title.} \item{Supplying \strong{NULL} will disable the title link functionality.} }} -\item{log_level}{Designating the log level to use for the user log as 'DEBUG','INFO', 'WARN' or 'ERROR' (default = 'DEBUG')} +\item{log_level}{Designating the log level to use for the user log as 'DEBUG','INFO', 'WARNING' or 'ERROR' (default = 'DEBUG')} \item{app_version}{Character string designating the application version (default = '1.0.0')} From 6d6421cb42ffda17bf74275cbb5fa605f424976d Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Wed, 27 Aug 2025 21:46:02 -0700 Subject: [PATCH 099/139] - Updated NEWS file --- NEWS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 0ebe3107..57dbf586 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,9 +4,10 @@ - Added new module *?downloadableReactTable* based on reactable package and downloadFile module ## Enhancements -- Adding openxlsx2 support while keeping openxlsx to support legacy apps and backwards compatibility. writexl package is used as last resort. +- Added openxlsx2 support while keeping openxlsx to support legacy apps and backwards compatibility. writexl package is used as last resort. - Updated *?logViewerOutput* module to display log data in downloadableReactTable. - Added loglevel filtering according to level chosen- Added loglevel filtering according to level chosen +- Added new feature to downloadFile module to control whether row names are to be written for tabular data. ----- From a729bf053d334456f27ec93f29ebc8bc55cddb41 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Wed, 27 Aug 2025 22:13:24 -0700 Subject: [PATCH 100/139] - Update NEWS file --- NEWS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 57dbf586..8bf11599 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,9 +5,9 @@ ## Enhancements - Added openxlsx2 support while keeping openxlsx to support legacy apps and backwards compatibility. writexl package is used as last resort. -- Updated *?logViewerOutput* module to display log data in downloadableReactTable. +- Updated *?logViewerOutput* module to display log data using *?downloadableReactTable*. - Added loglevel filtering according to level chosen- Added loglevel filtering according to level chosen -- Added new feature to downloadFile module to control whether row names are to be written for tabular data. +- Added new feature to *?downloadFile* module to control whether row names are to be written for tabular data. ----- From c26ca253fe34c0d652d62074339f0a0fce8f0d00 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Tue, 2 Sep 2025 22:12:03 -0700 Subject: [PATCH 101/139] - Updated NEWS file and package version with the final version number --- DESCRIPTION | 2 +- NEWS.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 6d371506..9a11a5d9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: periscope2 Type: Package Title: Enterprise Streamlined 'shiny' Application Framework Using 'bs4Dash' -Version: 0.3.0.9013 +Version: 0.3.0 Authors@R: c( person("Mohammed", "Ali", role = c("aut", "cre"), email = "mohammed@aggregate-genius.com"), person("Constance", "Brett", role = c("ctb")), diff --git a/NEWS.md b/NEWS.md index 8bf11599..b5196680 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,4 @@ -# periscope2 0.3.0.9004 +# periscope2 0.3.0 ## New Features - Added new module *?downloadableReactTable* based on reactable package and downloadFile module From 2d2741f51e80237c023a71c09d155a5b5293cc6c Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Tue, 2 Sep 2025 22:13:23 -0700 Subject: [PATCH 102/139] - Updated package documentation and site --- docs/404.html | 5 +- docs/articles/announcement-module.html | 9 +- docs/articles/announcement_addin.html | 9 +- docs/articles/applicationReset-module.html | 9 +- docs/articles/downloadFile-module.html | 14 +- docs/articles/downloadablePlot-module.html | 9 +- .../downloadableReactTable-module.html | 389 ++++++++++++++++++ docs/articles/downloadableTable-module.html | 9 +- .../figures/downloadableReactTable-1.png | Bin 0 -> 25216 bytes docs/articles/index.html | 7 +- docs/articles/logViewer-module.html | 19 +- docs/articles/migrate_to_v0_2_0.html | 5 +- docs/articles/new-application.html | 9 +- docs/articles/themeBuilder_addin.html | 9 +- docs/authors.html | 9 +- docs/index.html | 13 +- docs/news/index.html | 21 +- docs/pkgdown.yml | 3 +- docs/reference/add_ui_body.html | 5 +- docs/reference/add_ui_footer.html | 5 +- docs/reference/add_ui_header.html | 5 +- docs/reference/add_ui_left_sidebar.html | 5 +- docs/reference/add_ui_right_sidebar.html | 5 +- .../announcementConfigurationsAddin.html | 5 +- docs/reference/appReset.html | 5 +- docs/reference/appResetButton.html | 5 +- docs/reference/createPSAlert.html | 5 +- docs/reference/create_application.html | 13 +- docs/reference/create_left_sidebar.html | 5 +- docs/reference/create_right_sidebar.html | 5 +- docs/reference/downloadFile.html | 15 +- docs/reference/downloadFileButton.html | 7 +- .../downloadFile_AvailableTypes.html | 5 +- .../reference/downloadFile_ValidateTypes.html | 5 +- docs/reference/downloadablePlot.html | 11 +- docs/reference/downloadablePlotUI.html | 11 +- docs/reference/downloadableReactTable.html | 276 +++++++++++++ docs/reference/downloadableReactTableUI.html | 238 +++++++++++ docs/reference/downloadableTable.html | 5 +- docs/reference/downloadableTableUI.html | 5 +- docs/reference/get_url_parameters.html | 5 +- docs/reference/index.html | 13 +- docs/reference/load_announcements.html | 5 +- docs/reference/logViewerOutput.html | 40 +- docs/reference/logging-entrypoints.html | 5 +- docs/reference/periscope2.html | 5 +- docs/reference/set_app_parameters.html | 7 +- docs/reference/themeConfigurationsAddin.html | 5 +- docs/reference/ui_tooltip.html | 5 +- docs/sitemap.xml | 3 + 50 files changed, 1208 insertions(+), 79 deletions(-) create mode 100644 docs/articles/downloadableReactTable-module.html create mode 100644 docs/articles/figures/downloadableReactTable-1.png create mode 100644 docs/reference/downloadableReactTable.html create mode 100644 docs/reference/downloadableReactTableUI.html diff --git a/docs/404.html b/docs/404.html index 9c31e350..da33cbf0 100644 --- a/docs/404.html +++ b/docs/404.html @@ -32,7 +32,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -60,6 +60,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/articles/announcement-module.html b/docs/articles/announcement-module.html index d0d8ac3f..f1900b86 100644 --- a/docs/articles/announcement-module.html +++ b/docs/articles/announcement-module.html @@ -32,7 +32,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -60,6 +60,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • @@ -108,7 +111,7 @@

    Using the Announcement Shiny Module

    Mohammed Ali

    -

    2025-04-14

    +

    2025-09-02

    Source: vignettes/announcement-module.Rmd @@ -249,6 +252,8 @@

    Sample ApplicationTheme Configuration Builder +
  • downloadableReactTable +Module
  • diff --git a/docs/articles/announcement_addin.html b/docs/articles/announcement_addin.html index c99f7d6b..bfdf5296 100644 --- a/docs/articles/announcement_addin.html +++ b/docs/articles/announcement_addin.html @@ -32,7 +32,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -60,6 +60,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • @@ -109,7 +112,7 @@

    Announcement Configuration YAML File

    Mohammed Ali

    -

    2025-04-14

    +

    2025-09-02

    Source: vignettes/announcement_addin.Rmd @@ -362,6 +365,8 @@

    Downloaded File
  • Theme Configuration Builder
  • +
  • downloadableReactTable +Module
  • diff --git a/docs/articles/applicationReset-module.html b/docs/articles/applicationReset-module.html index 479d7bc4..e66bb8a0 100644 --- a/docs/articles/applicationReset-module.html +++ b/docs/articles/applicationReset-module.html @@ -32,7 +32,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -60,6 +60,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • @@ -108,7 +111,7 @@

    Using the appReset Shiny Module

    Mohammed Ali

    -

    2025-04-14

    +

    2025-09-02

    Source: vignettes/applicationReset-module.Rmd @@ -250,6 +253,8 @@

    Sample ApplicationTheme Configuration Builder +
  • downloadableReactTable +Module
  • diff --git a/docs/articles/downloadFile-module.html b/docs/articles/downloadFile-module.html index 758b265e..00640e6a 100644 --- a/docs/articles/downloadFile-module.html +++ b/docs/articles/downloadFile-module.html @@ -32,7 +32,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -60,6 +60,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • @@ -108,7 +111,7 @@

    Using the downloadFile Shiny Module

    Dr. Connie Brett

    -

    2025-04-14

    +

    2025-09-02

    Source: vignettes/downloadFile-module.Rmd @@ -225,6 +228,11 @@

    downloadFileAdditional ResourcesTheme Configuration Builder +
  • downloadableReactTable +Module
  • diff --git a/docs/articles/downloadablePlot-module.html b/docs/articles/downloadablePlot-module.html index c5528aba..986b4432 100644 --- a/docs/articles/downloadablePlot-module.html +++ b/docs/articles/downloadablePlot-module.html @@ -32,7 +32,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -60,6 +60,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • @@ -108,7 +111,7 @@

    Using the downloadablePlot Shiny Module

    Dr. Connie Brett

    -

    2025-04-14

    +

    2025-09-02

    Source: vignettes/downloadablePlot-module.Rmd @@ -310,6 +313,8 @@

    Additional ResourcesTheme Configuration Builder +
  • downloadableReactTable +Module
  • diff --git a/docs/articles/downloadableReactTable-module.html b/docs/articles/downloadableReactTable-module.html new file mode 100644 index 00000000..98f07036 --- /dev/null +++ b/docs/articles/downloadableReactTable-module.html @@ -0,0 +1,389 @@ + + + + + + + +Using downloadableReactTable Shiny Module • periscope2 + + + + + + + + + + + +
    +
    + + + + +
    +
    + + + + +
    +

    Overview +

    +
    +

    Purpose +

    +

    The document explains how to use +downloadableReactTable shiny module in periscope2 +applications.

    +
    +
    +

    Features +

    +
      +
    • Ability to display and download datasets.
    • +
    • Table rows selection can be multiple, single or none (the default) +
        +
      • When selection mode is enabled an additional column will be added to +the table
      • +
      • The selection controls will be radio buttons for single row +selection and checkboxes for “multiple” rows selection mode
      • +
      +
    • +
    • Returns a reactive expression containing named list with two +elements: selected_rows and +table_state +
    • +
    • Supports full-table searching including regular expressions
    • +
    • Columns are sort-able in both directions
    • +
    • Configurable table “window” (viewing area) height with infinite +vertical scrolling (no paging by default)
    • +
    • Supports rownames
    • +
    • Requires minimal code (see the Usage section for details)
    • +
    • Uses downloadFile Shiny Module functionality to +ensure consistent download functionality for table data.
    • +
    • +downloadFile button will be hidden if +downloadableReactTable parameter +download_data_fxns or downloadableReactTableUI +parameter downloadtypes is empty
    • +
    +
    +
    +
    +

    Usage +

    +
    +

    Shiny Module Overview +

    +

    Shiny modules consist of a pair of functions that modularize, or +package, a small piece of reusable functionality. The UI function is +called directly by the user to place the UI in the correct location (as +with other shiny UI objects). The module server function that is called +only once to set it up using the module name as a function inside the +server function (i.e. user-local session scope. The first function +argument is a string that represents the module id (the same id used in +module UI function). Additional arguments can be supplied by the user +based on the specific shiny module that is called. There can be +additional helper functions that are a part of a shiny module.

    +

    The downloadableReactTable Shiny Module is a part of +the periscope2 package and consists of the following +functions:

    +
      +
    • +downloadableReactTableUI - the UI function to place +the table in the application
    • +
    • +downloadableReactTable - the server function to be +called inside server_local.R.
    • +
    +
    +
    +

    downloadableReactTableUI +

    +

    The downloadableReactTableUI function is called from +the ui.R (or equivalent) file in the location where the table should be +placed. This is similar to other UI element placement in shiny.

    +

    The downloadableReactTableUI looks like:

    +
    + +
    +

    The downloadableReactTableUI function takes the unique object ID for +the UI object.

    +

    The next two arguments (downloadtypes and hovertext) are passed to +the downloadFileButton and set the file types the button will allow the +user to request and the downloadFileButton’s tooltip text.

    +
    +# Inside ui_body.R or ui_sidebar.R
    +
    + downloadableReactTableUI(
    +             id            = "object_id1",
    +             downloadtypes = c("csv", "tsv"),
    +             hovertext     = "Download the data here!")
    +
    +
    +

    downloadableReactTable +

    +

    The downloadableReactTable function is called +directly. The call consists of the following:

    +
      +
    • the unique object ID that was provided to downloadableTableUI when +creating the UI object
    • +
    • the logging logger to be used
    • +
    • +file_name_root is an optional character, function +or reactive expression providing downloadable file name
    • +
    • the root (prefix) of the downloaded file name to be used in the +browser as a character string or a reactive expression that returns a +character string
    • +
    • +download_data_fxns named list of functions or +reactive expressions that provide the data to the downloadFileButton +(see below). +
        +
      • It is important that the types of files to be downloaded are matched +to the correct data function in the list.
        +
      • +
      • The function/reactive expression names are unquoted - they will be +called at the time the user initiates a download (see requirements +below).
      • +
      • The download functions or reactive expressions can all be the same +or different from each other and/or the one provided to +table_data. This allows finer control over what the +user can view vs. download if desired. For example you can allow a user +to view a smaller subset of data but download an expanded dataset, or +perhaps download a redacted version of data, etc.
      • +
      +
    • +
    • This module also supports most of reactable table options for +further customization. See the example below.
    • +
    +

    Data Function or Reactive Expression +Requirements

    +
      +
    • If a function is provided it must be parameter-less (require NO +parameters). No parameters will be provided when a function is called to +retrieve the plot or data. Reactive expressions cannot take parameters +by definition.
    • +
    • The function or reactive expression must return an appropriate data +format for the file type.
    • +
    • For instance: csv/tsv/xlsx types require data that is convertible to +a tabular type, to various download types see the downloadFile module +help or vignette.
    • +
    • For the visible table data the return value must be able to be +converted to tabular format
    • +
    • Since the function or reactive expression is called at the time the +user requests the data it is recommended that reactive expressions are +used to provide dynamic values from the application to create the +table.
    • +
    +

    Reactive Return Value

    +
      +
    • The server function returns a reactive expression containing named +list with two elements: +
        +
      • +selected_rows: data.frame of current selected +rows.
      • +
      +
      * Note that this is the data, not references, rownumbers, etc from the table -- it is the actual, visible, table row data.  This allows the developer to use this more easily to update another table, chart, etc. as desired.
      +
        +
      • +table_state: a list of the current table state. The +list keys are (“page”, “pageSize”, “pages”, “sorted” and +“selected”)
      • +
      +
    • +
    • It is acceptable to ignore the return value as well if this +functionality is not needed. Simply do not assign the result to a +variable.
    • +
    +

    Customization Options

    +

    downloadableReactTable module can be customized using +reactable function arguments(see ?reactable::reactable). +These options can be sent as a named options via the server function, +see example below.

    +
    +# Inside server_local.R
    + library(shiny)
    + library(periscope2)
    + library(reactable)
    +
    +table_state <- downloadableReactTable(
    +             id                 = "object_id1",
    +             table_data         = reactiveVal(iris),
    +             download_data_fxns = list(csv = reactiveVal(iris), tsv = reactiveVal(iris)),
    +             selection_mode     = "multiple",
    +             pre_selected_rows  = function() {c(1, 3, 5)},
    +             table_options      = list(columns = list(
    +                 Sepal.Length = colDef(name = "Sepal Length"),
    +                 Sepal.Width  = colDef(filterable = TRUE),
    +                 Petal.Length = colDef(show = FALSE),
    +                 Petal.Width  = colDef(defaultSortOrder = "desc")),
    +                 showSortable = TRUE,
    +                 theme = reactableTheme(
    +                     borderColor = "#dfe2e5",
    +                     stripedColor = "#f6f8fa",
    +                     highlightColor = "#f0f5f9",
    +                     cellPadding = "8px 12px")))
    +
    +        observeEvent(table_state(), { print(table_state()) })
    +
    +# NOTE: table_state is the reactive return value, captured for later use
    +
    +
    +

    Sample Application +

    +

    For a complete running shiny example application using the +downloadableReactTable module you can create and run a +periscope2 sample application using:

    +
    +library(periscope2)
    +
    +app_dir = tempdir()
    +create_new_application(name = 'mysampleapp', location = app_dir, sample_app = TRUE)
    +runApp(paste(app_dir, 'mysampleapp', sep = .Platform$file.sep))
    +
    +
    + +
    + + + +
    + + + +
    + +
    +

    +

    Site built with pkgdown 2.1.1.

    +
    + +
    +
    + + + + + + + + diff --git a/docs/articles/downloadableTable-module.html b/docs/articles/downloadableTable-module.html index 357ad2d5..8aa96057 100644 --- a/docs/articles/downloadableTable-module.html +++ b/docs/articles/downloadableTable-module.html @@ -32,7 +32,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -60,6 +60,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • @@ -108,7 +111,7 @@

    Using the downloadableTable Shiny Module

    Dr. Connie Brett

    -

    2025-04-14

    +

    2025-09-02

    Source: vignettes/downloadableTable-module.Rmd @@ -369,6 +372,8 @@

    Additional ResourcesTheme Configuration Builder +
  • downloadableReactTable +Module
  • diff --git a/docs/articles/figures/downloadableReactTable-1.png b/docs/articles/figures/downloadableReactTable-1.png new file mode 100644 index 0000000000000000000000000000000000000000..3d2266dc3bcc2216b2e5724f0ce5e13b37c2b882 GIT binary patch literal 25216 zcmb?@1yo#3w`D^J?gY000fM_j1HpqOxJ%>i?n!V95Zrf4Kl%4s=qpQm>80M;5 z#Yh+4uWy}_f`8U+`Z4)afrbbEn?P)W*WGY_euIDxEL^nSfa~+qq@J(0-Yq*{$V`{d zg;t!pw;pcKcd($Zx^5Ew*r>TfvB$eh2NxGEZYM(tadGiZr1za6(3i4O$jh(tb1|tX zDJd0XC?uf=(_%kFK#%cK2NFP!IebKBgC1M|@dosAA|2g7I5F{jXC#vcj{#Jr)z{ZY zAuOEvk=aq+(ZQi@U?4mQjTpq995Dp#)G?CR`5kG+T=3oH!N$JlTCMD_4-r$ATeD!J zn>+5#iGjj)T)y!2brAB|dEd}bmP9xnGCTCVnco9}Nhl1sy?uRcGc^=wjZF+Xp&Zch z{R*hzX>LY?UX+bH96E-$-zt=SBO^T%0|OeamKl0_n>#zi124mQM~@2{&J>1RP_asu zl$0n4)kukp=av-e_CZG}zK{A3#;#Hza7D&z@89|}q1$l_m(=9aNwMPXmYEUCjl|ht zJKe<9u`)NRqNLAtTq+J_GCXTVZ+ILo(RwHFvX;{=lW&A(fGxn z7PEIFje6&zk}vbHIqy=3>16neC=KXCwA(f+jP|Zv(Rj{il&Z6tKg0EPYV!FfRQ)t= zW;*D2l)ywtY>AWmWFY;f=!Ck|Tl*y>qe7?!qyzuZcD{k$Tct3ob1$i!_w#S?<%i<= z33Ohst0M4=Gvt1l!Uie$+|i>b4Lf%HHYYn=nw({Xc6wH*DjVXE_G%(W)yL>uH9Xoc zI!o;M_X!sHa|XBp+W901dgh;j-7zBqA=3!(;4T~V!(Ds*%OO1lT+r{gz3*FJzQ`-! zgX|I6ky)X=x~Nwz9v<)BV{n`M|5p5V#WUHA^MF^4SUIuit7%Ms(^Fa$-Y$ z+3~2JMiRt)@tQ^TO41#ARZ867378v4M`(uzQws;PjV9->qc81BjS)s$8v-0YLmfTl zV?n}%%!{f1HI1@#EoD+k98T|Vv-9(UP0E>f z`1ttHPRwV^C}`sP{?6x2$iNb~X8gsun3;HZ62E+b%_j%r)nP*yfV-#X`oV#f_1)CV zI=2+DwPoHJPR}vy2&%1n3|g^fQ0fQ^3xh7WEWIXARWjp$jk_0@;r;toYi@>C>GX~5 zZOM)xG`jciS-DjOpw@_?|D>ZM2ibS;b2Zg%ElMj(< z5`Vry_bF+4x@vp(1IJ~LYtol@f}K#ruWdU=EgCoc!Y4c22FFM=%3)FHm~$l`A_09u*55$%UX2!+<6d-zs^ z!nkiI30Z9c*^Yv*%tJ%rT&iYk-JR+DileX*LQM~c6Ddn$+J{7h<945cXRt}j>%k+! zi&Zgkopp7rf?ktn_>%T4s=cHk;AE*jx%v!BkM4_mN>3e(2QN1h7J1nK2b2{ZhiB2# zbe`#1e6*w?NF2!=J9Ni(P9bhj6;W}3Hz!+W3#;At9uIqaa(>R3Krdm?-m@>WZgcAy zhnISzgi_N&^2LRU@5(PS!Y|Y;uc=cz^N1;RRy(czPoyx8D@5!9 z{VZ3nNz%FA>%C#Zn`S}yRT+q|B*$6873V3J5qj89H-uhZ_u55MU)$MKnd?BT-37!7 z+T5b0);`l$$rnATNEgtb)hEYv*mdjN?L)*M4siqo<8T#rUO`uTa6RbCzQvR?G%rq? zGX4{~`OBLO7nw^guMV$`EQ6*84tn3iN0HFv%*JK9&6ntu3&r-Xnkmc&f&85w8lPpS znY_{yO^4Q>uV85v=A|sHiF9YkM#iC@<^6`p_$VJ^L(nnd)wWEX+Ho`Y32DZYI#`$?72g8YxOZR%!kAr7La{)K^I5xfN)whO9VExb%fR#t7cN+C6sd+K*}{Ae2xXYJqCflYHmn^)$I&Qt=^{d zdwhY&FD`G9==(ONHR0WV=-TP!-RAA8_HXB?&KypE_@b(>XM0zioJ!fST4%;*=VPL8 z0S;{H^Wt()w|reg$YDCjiYFny*{{KfH)wiaHHv!znIt@qVbN$l!9zG{nQVsaPFjQ$6BK%KzA+@u^CK_M#RbMw&!EzMPwiJsNM3m!otqBsabT-bt%;~N9<9^`e74i+Iq1w?=p2xc{F5L zcYb2DK69kTWV~^vxbnK1<$Lmnb+^$fKY}6I8A?Zw#CzgeBECA0BsrSJ#YKDQw#t0~ zgc?XSYy7(RJutXS2~d)eG=$Gb>s$gDNOqdEGH70{9ODZQgjH7@L}M;L?D}Cn(-ARe zZ2ji;lKzp${<0d{|If0hfc@3>d7u*6J{lmRSEFPW-R= zm^7}vi_6yGTt#=ebh`c3;T*D^r23MjZ#X4o)%W~-TD))xQOW(Z>}*O#Jd})#jP5+k z{8Ca(tOClqy1F-S-h9%reyF#MnID*%!c|mM^!D+Ily8~eYIYTQ2?ydOGKnZ`_-fEk z;%WZ>$@EVt7#9~;buK6GpQMI=2VeL5;=-BRc2U~XvmU(1v-9uUYRDv7Vm+tlOo{G0 z8kz(kWTiz|x89ju+9c2SY3P{ z&Pt7lVSQsR`8C`(cGrtf{0K=AOMgW8!y$yNX}Thbeb%=SUM->qmG*553c)f|d*U{2 zi;56VSIh#+?$9RL1V##4+I*d2N@%DzUaWQ4vP_v}pj7HQ<5r+pPr#B(&ulF>L1Ii5 zPB*N%Fm7#Bu{NTX5nQJDR!y4s$(LwjOQli=^HtA9zK;QBQ_y9BxWA0q=?Uh%D?!_O zhsl(lo3Pw<>)ZL2h0NoqC5b;{QKSO&xCQ`6M@ML`9SqjVf!ZtAbwF{`E>11xHJrHM zU?CVhNP@56pPKx=%ryy6Uf#>jyz{&F<8lGA0-COIT>=$Iv&BLP5@}^+-R{}{rD_pj z#X8EA7g)o#L@*?kI$hx_2?y-%MxHTp1N-0>1UmD}h+Ecl$P!HZF*hKA#{Fi_jr*9G zi_}Mgz!sw6XP9D*d?FF-a6Ou=L(hQs{a zFvde2_DEXL!1RQ#H6yuvbZh=DV1O*O)hTqZdW>2b^Hq+KyMxYqpsGMSOI5_F^ElK- zy#owxpVWGLdVJbx7KImd=bt0E&(_M*K_zrDsV09j+-pMhT!0IaGhLVobd_W$9n zCglxRG{9tflrxauiE9CNj}OW+sRv9Fbj!(Z?(+G0qLH?>GGFqg#7ZuEFvPo;mQ|E_ z>|;uH&%#DP%Li{cHMya$Corzv;%r=Oq*fL7$0Spk!+FlGzL^NB_ZgHfBGF_*yECOL zCgk0KbL~$dSTXZMg~BYu2J&W04@jtpXgMRf)Q5ww?z-#^);@XdWHnxPkfA?d zioG*EPHPT}@1x7L)f!1)0TFJ!{@y>Idnn7O9*|jKlb@b~wly|ap>eWeJkH&PIp;yb zfI~Y$cu2+S#d{(KyP-{}4j_37@nEo4|BC7jOrFfW%srM~%Q>x9R`U>EnAbwjXbtBu z(o9bKFph$o8HQcJ^x|35=X*Tin=IJwhB~}V@&L6cnqd>;N$e;h0fDnZ#XEtq2vm$5 zn-jQ%IXjh&MEQSyyn>7qqd%l>Gaz8YEz|KsIg-&@rh5O zKybzlfU`qeF7Q)$q*HEVSi~s*<5}qH)YR!930XvRYJ86NMiQ^Nb6sJKFH@#3+q-7r zNP&i=wv^#&>amNuXLbtt&OYd7C(UGLcwXrsr|Z<}$`#$0c`PK>uTU}RbZA9gx@l!t zO;g56Z1BFcB*gGRSE#Nzz(`*TeL%JM!*rcN_ptn1E*D%y_*W7ZMbgd>Y6d8EiS?zr z=OXu8^V%8};M^Qst3H1PJ??4d9FNPEzTD!l>h$b-6=|G;*Dn@T8wm4@KuDmjVDx<7 zrrVAbjY#Y64eJ3AlHw4=+$BoKLy^TG291gpP+mD}omvEWQ zc45EQSQ4$(T;z__(#fUcc7%}w2Av_;aK-Jr$6syMPpLNCy_Fab=#j;ByIBip{R0(g zBrxaQ0Bb_+h?xoBa8WV^Nk!eGVIpo1oHN2TEfz2`S@{tN)!!+3L2D!FVmy0@l{cr+ z`*a`c#P?<tESh^z{yM=bzxKzvO7EgRM2O?^!3T#wjxnkudw?*sel*lKZ@42}bZOx^V$F9Q!M$ zf)m1sIBI9?<9+=cYpI#T&EAN}2a2-ebJg?#CNc=Ev}rQoIeWhf<8tvPe4qv#F58}C z4(GnT*-#$iLEqHw*Ox$6 zH%K@i2lPuDdVmH&ZM>eeU94|c>FxE(-NTfsOyNUp&~Bnkhr1b#a(H9@AhD@at~lPV z85RZ>g}c;CYpc0RAb>D)wo5?+dDIbhR@Q}^%?1mmdd=YXl++GZKV}r zo0wSzbA>ng!J2qR18rKhZU+(sOL8FuiBZ1dLrh@$sWJf}ncDQU5y`m{6TsRFm=Msb`oTtpUEOW8toEaOBg-0+fGhe?nR`DtU$+cq*6l@)uP zzDkDm%@vKpyB*tgdv3-f`I^_i@rVue086RfdsnDJ)2-IJr5xLAVIJBmI=WG7L}$NW znLUUL2ZYXOIQZzK={6hI#}Wb{FMGlq>1^K6P{SsJ9@+e9o^V|5-zPU;LJyS zzw(tV0iR&PlEn8a%9JPNxc45q%}F-eI5xs?(MAuE=xDYya=2*<`uGB=L${_2I?37A z+`{BN3V`9*a{4G6gyD4@0&!Vk9F2+F&C+P_abAJZVbmzyY8@JPrl9RX6vi45T*e*DImRm9xX!cyfTt8`k z#big=--=FC&OL0hc_Du9HS=p_qxa+Lse{AeFvnQ)GvaY0HKLUkvQ)UW_Rs=vU{>7v zoKLsh&Xd^Q)L?(}jk)BLz>s07Wj+xio7!~Ou!$R%{RU@j>`Yu*T5GPF$Y=KRm5N6y zJ3Rx)1btYv>$?hSy5vfEHt{=vZJS{I`v9hbzIS$7if3Vc&IrBQee)_IX$`?OK?^)Q z-V_MFvmF6-SWyCo>UBPJxFlUteSNt!EQUoG{(Q}2H@${VhebR zV<9zGAOk;-W|1C6r%uxZrM-K|Rz{yQ<)A_Fx)umT}vU`Sw_qIC-d8eDZ=F2I-$!Wt;>uNz#J#NRz zeM6vD!#DMv()FfO(N8~m&m!ZWZr&+$47TpXNy{qGSV;aZt`wScO|#Q9Czn74J(M5i z+J2kSN<;OHA4a`5L2U~AMYE>IhmAi zhK~w$8ki&RbU*eoH?MmkUncjo`@H%)m;JF>Q5t!NSD`5u5{Yhe^P=yRBo`)#eYR%h zN_l62%37Vd?zL0S<1BBzi+~hgtF4!>xv=BArK6bXO-vhkUp4L#q8@)TipnE_nJ1aS z54N_NnFED`&Ahv#amPS^?YB&?NQuSwYX?>39y)YfGf=jZXR`NDS)K79kv3K+>=Vpn z#)2R9AmQ}*fcZwjEq+H`(9~1J;PhyfUFBk_*F&O4iZ0d z@&$BKA!h(X+5Liw_L4F27a`Bir|cNQ5vmqqk<8{-NwtGlhBQJI*>&#gi(hKN!NLHT zp?-44d*8~}`uq_FD(0{4RH*M1*Seml5j^Mahx<&%F7^ccfh}Hxz_ZL@nR^Q7zU$hb zKvW3q_fDspZ&;oqZxW99t+dPDr{@IQ2-}7e8frhE5LCk$8_yD1f}wt^`j#Vrtx4*9 zvE0~EEIWyi*hIE~@@(M*`KL_ICz>Ymo*VSbqb`7_ykBNb5W-lw3m`8%EgHkp)Xzcj zV>Md5esQ)+#({u;=sC_D>1d`ID9>R>)q8fAOej&{k-}~c?u)=TR_o9FNgq7UqOTNr za8Fnf#@Jay04zq~h^t%5#~0&)f+se3;sdP!xl8&}vYqLKQ%I>}llP9lHiJ zj>n!rB66Lz2R#2{DyfC1;j{JRb4$y;kjTqm_iaXjPtE7QtmO?XxLZHz>0))0(y2;!}C^h5+ z=3-AMAk`FFoNWQ0wlMK{jJ~^pwCNOO6dyDlJS3RaeDR=yq=+p*drdI z-QtnkNo}_gey3nIn@cCyT$zm||6;5HwKkk2zWysDhwJPyXq3uVbSHQT^zK@3Mr>Mc zdcHdn-7W|_zhj4h_vA?Uzjz1}2o)T*4 zQ$oD5llg6sjqZXuXxg~l4$2`84In~SS>CK`agNJ6%|`H&>y*-U8Rpw0;itK)ry8$i z#Jh;p_DPE|RFvq|I(~bKkGR1};k(#F{av4(uV4-eoTD|Gkd)Q?K~0k1V`tF*p;cnXKj7$*Y3!*vu7)ATWIzqfG=X0}4sJJD z*(ln^q-;o%O{>oY6{`_!{ZC|Rw^j(2f>5qj zM<6zdNHD-eqAkLYHbKe`(OuZ52YSpTfLBQDQX2VFSK((`d=RJpA|Yv z@w%Y&)KpnfQJAi-u5OwbhVA|RFeob8+}zxqSD3w_{3kX_u`WS(?{sf4QA1rO1j;6F zE!H`4TF;jC`@s~X{x@zb{zhNQpP7e; zhltx+T2wRuycf22^W#t6hH{CCs)x7U!Jl`XZ}9PzTYPN;m%7%lKka^8S-dSg%Cc>y~F-94hv|Ri_d?6H{bkqo7e8N#Rs~v(J6R7E%7+69I2< zaZfj*g^31m$E9CX13DZ~ES=s5cf3G3KjPQFe)a!LSn!J;i4DqCrU`jL`Se@W;S8bh z!h=#_aTo88E~BS(Gv$Byfu%00XT+YKo*=NsMeuvoyx+eO!@IuZ=jZ<~bEO02|NWLV z8j1cZLat+Nja>)&@5s=4dVb(9dgu&W&Bc>tN=Qg-Wwf02Gi+t(_v0uEem<^CP1Ua3 zX_@|a#;v72<`+H6U%iBP8egZ526U#e9AN(%@jq5j6euNXXfxs&$-E%&X>-=D`?5t0 zxG$26Tvx|!s1D#ikZlww;dr^YKv$CV(Z=y{R6~PTT?&W%VX;aG9U~)W!b=}`_Wy@I z{x75-5Cv@LI)(S}@?Bf~GVxGZ^*y_Npm|CSi3bCpXk}eAHFWvr+mC03-0(3sTHQ#T zmGikfNFVof>4&R~Nbl+$S|d%U%7G`QW(VK(fg&}I7}=7s73xqKJDWkUFD;EiEd;4n z>6Ykhghd(m77ky$o$cVHoV>Q)I ztqjy-dX-TC#qhr4+)tN-dXTxv3x6*elCF4ngjRG*#;_ycr4Yh>08Y8DsCfW!GqlJ z`HJeSqRE6b>>B&Np1gR51$5NLo7W7HIyo~Y$&s_|7uRZ;PU)2&6SpIjKA1JgdvcE{ zqjFhR)b_hK+R&N}-d8K8pkZ=gdRTMF9xT+n`~_AO&)w&=bLGMDl)|yK2Bh4h8h6hs z8v`%c$c2B%Y}`o8=6m8tsQAlJL3xW^$lWsM?H?CGF;AcU5s<#26DfKuwRT} zM<(S{Nb%RpRY?$8H48CbTZo_SyN2G{A0fP@dCmuHX=_517B<$f-W@cUQ$>vSH{1u3 z6YDs}+w&Rt8XRpTay_x?o$x|?7Q@$q4t1GBIwNQG#Ha75) zJtQ$$Hs0OA3-4YgS%=Wc1q*rbSVk z(O2OXI55WOSh4ky`-EJ+;NT7yVAa8q^T>%_p3r+#I`uSs6*zVub@4UQS~=`y*^?Mu z{Mt%5wCBno0`TGCqTK{4dJEJV*=knTA1ov09xRc$e-@`W=B!(gJ6A(rD5Uzs)R|kN zjplYi^=3857uAR>RG-+34>7s39lrfv>fVe!7_J9#dQTsgM{UEDt$E!bTJ%^%nDI;~xB2YT>eJ#EXs_-M>p1eEW_@V! zQOl~H2`zuY@4@`hmBfo7MTAi)riNo~bn9(l1BvnFK;WA?(WiXP!<*Xg5E_%yA^nLt z)?GwlUrkioW6t_lZ=;tKGmQPhPDQNb4y1W{1;x%HrDp9nu(%hU(jJ4+miGNEk=a(t zEw?P%1*)h8?rF;vtEg}CCK82z{qXy~iLt|RyM~2&GeMktD%5}h;52O&y9Q|yN!<91 zU|)N{<7de9(^%Yzb*9}2P4rZ=RciJvSH#rTDYAKvveJ~o`U*qP*0ybBbSD+ z7WCf=9@y6N1%1EJfVT~hz#sv6ne*7s4KRdmiS-8uqqAMM$)O9mh73vp-?DpADn3BO zH4$Pyp61?bC@!8O^IrUZ^xj&VAkp+d{~erI%J512Ngbf3;Ok9P=&%Y=ygYUMs$Z_N zcOqdGMsrxxp>@%&teaoAXa)g8gOdOexorr#KD|b5!qP=-}*U#6y$rGs25~@}3ia=xTq2 zO(ma`n@m2Vpk{ZcTtholBtqS3#w)wk_-N4}{VU?4s|8oXEyJ`Wt3XE1sRSipT`2$d z*hnmg>RuD^8WZPu&y3F5DZfwb$+=}}7JOBhfEibB%ayQT_Py4X`aqV9A!FMuAv|5_ zE>!h=B@n5!!hXEC(#o9OwT$gGHrAygcI@(w;e;b7(dH{pBzVQ^LQaGX1hEN06Gyr|e1U8u0Ige|ofBW#mqdg^Y;r zMGa$fVQuUD z&@hEKem}O%`09tw{aaa?A-bc`OOhLRKAq)RD^@JipF>{ux$hOV2lvT=YS~Sei(OzH4G`w*H^`JC@EQWqw6$h|VrXFs^KNr8Pd3U&3vijAC zwNdvSl)^C^rSpMjAp=Jrm@yfzvFT@jVW|J9y}9n4+5oddcUM<5ZET6+DYOYOpw?-wE-wWi`<-PeUwgKC`0}Y%57%dZ{19>3 zV}?m{oIpifdr?O-9hIU zjK>qND#8p0MPlOxOsWjMQM8i(Jois{4s>grt zrgjnzX~#BWuvUC8$VraViCb?etRfbV&BYfxUeoh>VNA~3D;j#es}o6QPCJ!rQ*q0{ z501XWS!J^t2*?~VbGQ0W4L(#B`6!Xa_S4JF9D#!s-QC!U8Z9AT$)DqubJn_k|2`2E zeq!j+C~^5i*J*s(RSs8e0??SPp^9MH0^#_kBkO^4Y>hSCp*51mU|v_%Uy^)K zeUte4HdT`ILfi#AH(oBX@wwrbLf+e(7BaXd2-I|ljbB$*dv z5P8Ajf@WXA%jFzvCjy7M$vcS%-+|9b*fZ@wl^RPlwyk9I8Gb~^^ZrOjjS6K=K?^_| z=?BDP@}crq=#l=H!Ej{cxVBapHVr`>j*^s8EagR949UOZq;TfBpi-aqT@DK*f%E?P z8X;l-(18iQdQk5|{X#w7J3pScfAXJ^J-066$&W4T|A5U}b#akP6bcL)jHk9E(& zaUQaTB51q+5VFIPtXP+e+5sFI8<}!Qx}h;xK_1`5g{^EhjZf4+3}nO@GLf$ zS$T~X8R;%G`6Hx?X|JK0Zsygh>^1Jo$HTD=7V0LJrjq(>c(I5;A+Pbanc(uoSm$MT zSBeOq@Z4e~K}!YW{fbi)DlUWsR>vFXtKgRIyJDH5Xf#@%?oX1;%(DFVcv5VRdi*gg zrIdVh0r>SaU~3->4g-^OCozIs3vE4VIf|o$YeIcH3aWf+!uC&H)lCnqPG?f*V>4dT z3wOYrhqJP2j;r!H{FZN}0t%}+kup%WsJHc^PA)>8$)@HhZMZsVOt6+KO?~nj5j-9sJM754D!4AguRe_A zVPvBB;X0zze4Z4gQPDqr{Dolx)*_l1l8Q*_NL+T~W}NsB%&zmH?Mw)YtI?#4hl}yH zR)P;$=V}^+_l{6B6%Gq?G@*BQjHnr}06F=bkbMRtacAL7u@o*!_~GpkV7KS{&kxIs zS2PGCuw8uC+H64<9_Fy6Dg|OJX~sJTfe#>SK#5ll#S>6sb72LYeh z@B!d^bLOK&8)|*mN|uzzG(>6c$Y?9vFY)!C0&tVsl{OP%|KYMYTuH5N<Z#Y^D|raRJ_OJS}r;m^|&lk4kTzSbj%9GQmW)*ow9 zDB9GFsKTz>)C_oNJUf@6|DKqMaQU_NGy(B00VsJccld^KAm|%&fBp{VbbiUslz>5( zJ_05QTlx^L?PUM(PB1o6)wei99E~z-p*}XT$aLmD%O)Lh?3lvsebEj{8^nX#cMzcm zvPXp|vw7EhzIPDc#y7gHw>!g5zIP7TcRV|j7Kl(6XH{Af+~b}@Db3dBeeyGNC`MTQ za!;D!el)nV1M#KsS|U_96pr?4fOqyR20OfV(W(kcQsR+3w|?f_?kuXe-hGcFzJtk| zlJU9OTkvbnx6xbvR^gDh<9wct-uO~5NAqiOHeEXjPpBk^H;DO7N4XnY$zMW9$UFKs z;b@YvlM+9*VJb}1Z!-l*ZQz|5i)(w+H`pTN(ZJk%A0A^s_M&vu?4J0bXXtz#^tBG( zylA!D zPCNV?^!sH{(Fk;t%7gGGka2VWV0GI0(dVp?*0sW;=33d7xwau?5EgAXJaa{n+^DkM z)oDb;6}1KDt{TCc>6>a~hY2-r8XzdFLWdDbb^f83IUMeuNdc7(*#o8=Oc-!66#j&q~>Qf(t zHP4f_I!Yl3_I@_msrdw#r(Ed%(~Tg@2LY3aKLwZ6jLqrWk+BDqTQgMUROW$&dFAeG z?Sr?n5;q!R|3Qi=t1s#OJn@VN9hBDbcEd}L%dtsxcH8Zv6291p#_EouR2q(As~vQ1 zGug&mm)R)b(X~(BYfmEepCKJ={)J{obF;=ifHR_*!Ko-`*tyD*WRscWCg?_0k?6U8 zrl_a`@;cXlh8y>Vu>N{dI>0aZamPuxEpL$9a1_bY`n9!zcr`dajy#j59ejy2v=KCr8)RFkHI=p=iFp8wb%e4|kYOa=Z;1kI+4?x7~F&L(BJ#g<--}W~3?$`#7{T`2VT;}tySE&Ap$^QNfsH{4k_cSf#fL8`*r_b>)@T=?Cc1tTF_=&gxa3*(=$Rk(MSd1TKV23cx;&sYTIBbY2#v#*jG6Ob#s8(D zY@jrK-u|etJhTa#j<0*a=&7NIbrm+H>l+nx|GgPR0905H2M-S=J1nP* zlEEr}pX?CNRq2T$wOFhLgLR^!qcLn2s<{%Z(*E}IqyD)501*Wx$M^BBo2CpQDFd1( z<;fEpDM!AD5TOqmvTpc*K9cY`e)GA%a@g#}Qb^~Q5D@_krSjyb_Xg0GzJ>E}aNTsl8D?Db zlSv-SU7B^z`t?9E(236?s_Jk=lk;3Yw%?^lCX=JdjHMm=1dcT(R=%qAae)qUc%+N} z2ZclJfiO~rS?{9kv8~vvD(`dU*GJPh-seNeYKOCf%N)3s;k?jWb&d?e&&h5-C(*H= zf2kb=JK^4tQmv&I=L$@YPdCeaXb*lz?(m*$sb715qje=qE$0Ivkq^Eyd@vEQc;{Z4 z!O@@h@GaX?6*E+tKP=3dmt_wRd@G@aK^4MA(p@uo+dv#$IAw5%_IS!PgDGZ>7krFe z6%9RgO{EJYq2Z;@ho|YmsyPR?KOISJnt91CKok7=y9I=$7kAc<4<=B~H<;H{k`Yo< zx(yXtnWC-TxVFCC&mZ^QYfu6&y1%0M6`!&#?>@N_T0gfi`<7DSw?{=o@~A+Hz0oWC zbw5Sc%fppf;F=NM!;?|9qJ~I5BuCSEd|K~jONC||b$tVA;r2#xfp5|dlL6?Jgn?px zG~@%%Zgfb;PJWjZP2T_Ryjt{Keq-xAvXr;DH5Elm)qlOSdFOYNM_yOtWx^xRu}u1I zpo`vA>8G>Qaa)A(X4>CYtWQ!v2X;gBzAcW1 zp9l-ewnw8^Xh!+;e#1JK8M;kNx~D{}%Mw_-Zjvj1&A7t61IM`0+p$Doh_wO zPRe+3kA2W}NNyDv>3x~$7rvw@>QZ+!&+&=0fdX&;4{Eu^Kld&bS$_u<&s}$iir1AG zMEaC<@mRxt9uoYh7Hn5x%gF>3HJvkv>@}IBve)9c^Bqf}XwY)HFQ=(I)|#F-wuCnQ z3+w-*Zn1Q&C?9f@%_XL!J$7B(%?X}q3gV&>JZ<;XRlcMMGWcNkZ4z>1_axA=UO{Y^ ztvExBc=y1d1j8#me?3}kB~^Ruc41c^BSG#1DT{*4m7l#$_goG`nkC2YDoaK{^xLC~ z<@W8gYGO|kq7SQ$^>#RB)D&jk0V5=iZI(jfew?_qD>e=o`4P132C{Bnsuua3a+fCn zWeyWfwE0rwMH3$55{_;~v7hc}@f=y~ie=sze&NX+N|W)HY2`2f@%uCrM{q;=%DW-* z8xmn8CaAg-X+(GWM#`reT6SbLFA4o#9$cdc}D(DeRbOM#GL&+ z3NPCv;C`uZgk(3C^b;ofM5JdT_mGazqaX=Ft_L!ll!v?Jq2b*hL0!{UIuuA~-clat zUvArFwQCh04QUABtxfARieyWTu)>b9k_?^&_`mf#_2#;;v#^ z;B6EdDyMkBcA+~VD==>!&!nu~eq?3wY#Pb>{XT`CnbMJtZtk71eOfMYbQ#?`YCg=v z;>l}%wA0pfN`+xydr1Bi9+2 zq0ebf?ZDULdqRcdM1zyno1-i6Mp1)BG}kCe4bib0HzKTkjVELua+I~yYQj?bt{Z&q z=%l4Hpg^%kF0nX!WZm-ky1n8!Z($)`L$6cxsTwwg5sC*G+coly>|NF!BFF)mMc4w# zVaV}MQC9ai;O(~JypS7@QL`ah`rb>!4rd!WH@AWk{!;fraLY$u)u{kHjbyC0e=&Wr zd<8gifnJz_>%(Oy4ntBHxo<0`srYf}S4?|6OCt6%el^;JeAsHfcep1)2Q!=OMbGlp z+y@rSp7V2#^^%ZtP`wYfGEwtDho5%8*})9v{p3I{y3d!fmPdS#uwIFy|1BvIDtOw0 zBSPEmO)<0YPTfP%Jn}kjgCVyNilG+oO;e@RIyi`%DjIi3&HG&jt1w05U7nmd1W?*v zI<;KT{Zx}k1Nvrw7~I|LJE}5J@+2=;k94A*?a5|JfX}?I0&DK@G~Sy{H@@{Tn_u-~jR`+I6Ca^531EMK z-wTqZ;b3QfTT3OnH83@h()RSlSB^j}b7{qR0!Sa#$yWp7h86`@y%gx_7V;eW!AdVQ zMaQ-goro3xUHf*#k;ze)mkii_lwlMoKg+wmJ{MUAZ~uP8ovEhxmk-aw+rqCLd>LC` zW{#?rMOrjqbnLBOS2}{5*tElu(x#K9DJ0U8>_g)Hfbu}w8{u8Wx3}ILyKjlR)82aA ze2yY_g9+%bO%OiVdCv%pF1DwfCv&X20kWisq z)$N0OZEn-??^sNu$5Z0iXrHdDChF&Q8h#fWvAY-@N=knIi^`#J8DU49taOt8PyB5> zw*#+GiQT`7Es1wlVkCJz(sWBW(;ns>e-o}vXtgNiIG84i!mNp;t#50HKtE9EMZ4-@ zdL(=ti8RJ16bP4&lnHHzO1uR18&fgn1}1-HxHs?0s340dXG<1S(DQ_umR9CnS{0vq z357KsO_%MO-lrw5s?U;aw@qFPT3m(E8hWKvtdC*=-f}29>^6WFy|{9fbBSBL6ML4E zcY2f3JNleYnpphxz^08a3w_9%y}j`iqCwtrPvrP0;Fj{dux#lKRk zD9QJTnm;v@dNUC3T~WFUw|@8MCyVm)w!d zD?FLspAU_vi@T}CH&Z}ETjIc*ySw$zV^Xyhb!oh>pu`8S+#Zu^lGm3-P;_{8_z!7R zx~2^)58Wn2YmW@&OgtS-{zgx(R(yt@nskO0`xq1E`4!P;p(_S+W>IKK)BQ4WYIQ5J<=GC$V3l^-?+iC9rpAEVV*|1>du6^9B*J4Sca>@!+RHV#DXZK9 zrAqwou3U|lhC6EZs23zY)_T+u$a1f2#eEj_hv{8$&0807{CmCQ?E6pQI){pN@N>s$ z#8_-!G$wF5BhWZqYb7~p*4;>N4KDGl`bZL8|azqb!1pwDr=5NQN+Pq~CYPkWv)02j`LtI2_pbyM;edlS(o-2bJt zvkq!=U(hg64zx&tQi@ygLh&NSin|nd*P_Kew0NOVN^vWGS~NJt-3jh)A-I!Z*^i#x zyL40hiAX*FfY1W08x06d z1n*s~D^ma~*#hx%@+`m{SX}|$`G2o8{om_If(^wt@G^>uyCl`OWW^4aw0%L}Ok|@(BZT3BA9JX1iSlwTmWop8(SKn_+fVljzqn)@vzb*qkWRq0l}h z;SS>5n7GQKixyc;jR<***(w;@-5eY*d1wnUu9s-dtb;u-YFPJjUUX3Pm-`!d$$p?v zYNYcaSKb|1AT#*&?LTD{Sw8&P)%5 zQUJ;6T%pvY^8i3c6Sm%5FJ1^kVv+q^i9r1ci%ZWR%=ST0gSet$fmw_8dWz|&>wS;D z2YA0DbiQK0V~#rgMvK{<84{g3c%)_#EVmesKs-!^><`1g@u#$SJfw9?;|Y{S5Fe#e zuG@*$3N{fkaoI_VrMAo@7G5MaKDIo8^;pyelrjY(cZO~(HlLLMu!F+tmSb7>=lj2K z2No9!(9);sNe{92>+G;YgzyAj2>2pM00khq5>qc=Ce{a!*u`}19iOmXYo?mm(!$A; z{0HD!XKr(4v|Eg$O>MQl_w>lG!>c%eW9 z(0c2{yvSkxsp1@5)nYd+r((G#r&gsI5}h6n%hy4o~!ZIE7UMzZ0Y z60%+82u&D7*1 zp@+LZc$a5;vT0*fo(Z~t&t|N8p4ajfOwT2ry$rbTMb$7ai@l; z*vfjb>JbhJRae1LJv}4C(Vv2GKOo)lTEEozWT#ba20scP1USCxOG`Ym%{^D5AUE*+ zF2<+ZyFmipW}_ftPMNuPo=METnLX>q_SA@^jjBX)g#r@ zg$N2GX(ORjdJ(YWOZg?<3^_syBkoku$_f(lETuWGNVbQnTOa;ppm;eSEmxwEoFZW@ zB6fq)yAfr9`RMVEtRz++LNI=Cyyd{%6>%P=sZ!gyyZvf18g!3v8j@bQ12hNo-)YXp z(GjYXqeLvUk;^S{kGhq0EU6Tn>0-hok!2e+TN2@4#JUvM><)rVzt`}PsRk~%8}v-C zDmJUjzvu0zC*kWUwQLb7$jAa69zw>ln&{ML%21-B8AEi`RRZ^6IrFrDDl;zytT<3$ z5F>f*Ot3Op8lQedc<9cEZV*3w1ZlA-^59l*I;{oB?#Q+WMW6IgK8vJ!NuM4Ug?>Ay%S;E=2nx*4P zrjc70;Q@8j3PImiiGdaTIlm8mg)eh+WJi`(cX;ik`uJ|`1ijHfd!}a4QulCNl6-=* zHxv-5Du(YVq#AFZoSKi_^Pt?<$y_Nnu=5S~Zfp7@d1)l|^J?+;WT(w{-}sCeXH{{D zzPLSW|HRL2>E!lt8HWx|q?+1t%xFiXywa48J;k9e7;Y@kH$07v=$bK2LiWAY_7d#f zVV9K)B;Q^M6H=?&r|-*k?Hj`MLS7w-Gyc%1fD7L+r?soPk+~dhNZYSt?FIoQ(KnR@ z!aPgujiisPa-VwsjRC3J?C@UGc@n|)pti3^elo>_v_U1E zpG_al3KYR*^>l%@i%`JZj=`BWA3ry>`qHH2gEdHa+)?PDh2r{T*OWqaf`-n{$XUyu z3$Nh$secfJga%w!qJl6)xXH%9j)pApgm%fpNXj-})GF^-{My^RY+IO_G`!$_8T#$H z$wi4P{4%2^B!gn5M>1w0a)QdvzMFG@p^_el6&X$iTA>+d%v^(Stf%`v{}9O3bGpsg zbJ~#L20o86R5=J6w@fcK%~ZhFSwJll(&x6+vBzLLt<9kFWzAa9Sn;sI5177M+x3iz zr-q`roX}y{qtH2uhp*fGh~n6tq&5#4CM@K7XM%%Vuw~JK3_yaf#2363(E}8zbA1>+ zvEoos+RpZEtQQk41sXGkLl3}dg8O_bSy^nuo%UrXYA)KFebmK@_-5 zIm%nx^n2=iV$*(iNYG)oCpt}R&3%vU!=3X|wB-f9k&N|gShC&na7(c@)^k^X9M*Wk zWJ?88}>>xH7_RV3sRvMVTdABW zpp-Pin(GMe0-=JpZ=dy1lj`X6YXZ%(@(`|o9`IQ?I6E3L(kCp*b~+{S)H!->lwE!> zEmO@?E9O;NKscq0gRcxHCtV7^cK+Bd>@TmOSc zC8*CTA{5wqxWvb&oJz6a!KSJBgP z@)J{N|IoYog)3=xNW5od>d5VsZyhSUZal!4t~8fwf62bv{r_gUeRRaw;~# zx`m@(9nmdGj#3yw+M;I>*fixC*p~Wz9;XG@!l1cD{hI2(N@&-bHCFVeU76U{9s&=| zZJUbYoWRLR6{|rCBTqb__+dNm!F(D>)n?!v zlgsdiAX%kk?Zc8;$OYva+ihEI_w=qog2%bMEvb>cDag%9&mvMnDhXRD^lT&F6Vp9U zqYV1$-a_GO#!L#=2$Si=E#y1ntL>(?KmzxXc!xdm!4`l}A#$kMqcU^8_YmJSL%mP>&Y-z1RC+fO z*KTPz)TDWU&STAv+ZyV^P3eD#{avS+)VbCz-Jd5ddY*h`xxcGs&YB81wNYMKT;he9 z&R9~aS(%{N4ON<-)VD{L#++Q^dO&<30LsBB3?Pa9j$M_Wns?j{wG{0`?2a0MN`LbU2y0f1G%K}_I8hj>k)I@G zw2ppB3ApmC5~>ZMVzgQ^(!4>H*%8peBjZyKiray1nNhR+sB*Ie=>zc$&Yl%r^pI1h z8E?3-|2L+YL0`~b%u?bw{N2d6Dva-JeMifR=Z`jMdn759=hO#ux}x6p1Xdn$xB@k` z%Zg{bd#(sCnhce%*eQF3J<%S32yss4$u;Vyhn;%vtd?8ou^?<{JJlkd{~)?@{N9X_ zqb&F>HpP+s3*wK~jwsi&s4gI&0K5nK+wEuo`vY?jqFlf%AYk(1XI7?@^y^}k}K=D6gX@w^iMG`MphT5HRElQ3s7-PTWBJEC5i zgUb$GK}&{5Z$AJ#Lir3g!EmD>9dch!?9PdD+}gz%jPZoM-MIW{k}H-D?IzU;k8T-2 z!34nQ)|UERLjMC3F%#kbNuXYD)wQu3B`Zz6TFeBuKJOP~lP^ZM1yalg$$q>o$zzXs?mv;X$M1Qo#U0U5 zCKS(4zN^EyySqv6pMZ+MoNZo}G|wUt*M%^fw}?3I>6eTpEvfeMWC4?-u7W915&liD zN(Mx7Sp4h++UB(0;sg%GT9?UIFjp>&p==Z=s(3DLt3O4D7P)um zT8mtISvS>1B*@EmE<0O|W@XZ^hJYdy5$5$-(V5<^P;0&i_8H|#c2VrgG+fD%UungD)8ybAJ8 z456lkg8iijzngVBxG_^+e|Z#4MR%v$2YJgjgPC<3K=J?s*P=a~)G8n<@cG}7B=K+} zqp&po>`{5>dLSw3xN(lsj;~k^gZRkxS=}^eDW;=efxVGMbVg+ zqaiSEz@C^~Zn^s902Mab9NDj{k$_nboVm#`ag(awy2+cB^kO3^;NMYt(uu+?JYndAJjk9A z*b%CoqW{X-mIkpNroU5=(OPhNZjO>ym=}lpr6sP;it`L{DL}v#JL^E#f2qT_BQwd|B%IIx0H6XJ0U5OqHVh__zex+R_Af8@tdr|NDzMv zT+oZ4(@xY6DoG+79?~iGHSso~RW-}}~P{n0U=?_JdbKuL@D5(UTsQ0k`nYlljLxob3bwtMr^eEP$%ol(M@Bq~v ziCu>|b3IGFM7ePM*fsPzmEcP1!Jw1$hJn#>%vt`mpA~8&`;Scg^{Ke+%k^YW4UG4( zICOi!XXIwJR#!m=|AhYu`LzfEq)Gg2=mY#3^NA>IdGO#=gzJ=Fo5YuW#kzgZxf?xs z@bmGdbM$Ocaedzd4lw5qe?H=LgrbqUAEi9q9ENA3&A;-;)6D((RdVi$|=g?jkrvyJu?0dtM% ziVIT;RnS7}l;WCz<(_W#KM`o697O^bfs>mJqVn1S@!u(GbryxfmnPm3B;0>>Cp+6x zN#fGh#6WR%N?_2!jyc?Msg-;;(`5$(U%>l6Tm!P2Qn_BmwU|{ ztl0^BBIJ>2*H3dXaV<_>tn>sEXO$)M)?zy`9-5FYjii)MoCor@t zzv=}XB&`bAhim|GBGQp%~v%(^{#Qm=fwdjH=5LlxUwzGrH#n3o+)X*0t zcXder@v>23fv?qf?jqKNU;x(txl^+tYv$Al!*Dr_CWvkzh0~Tp}$41{vsep zh%5<_Im5J^y}#YImn9NB$q;W=Zq9N$%S=2V4O+!{LPe^8gqfI8ouEf)7t@=K{XtGd zfKz6z9OhQb;UBMsNILIbi45T{7qjC`zt(TvvUtq0sZ(mQ^Gia--)i%$hKFL=Kk>Pk zSE|_Pw=Z9r^|L|}$k#esvqNuN%Fv7M# z6(_R|51m#&_suM`3tV|@sRX9B;K9Fw2A6EJCdlPnIP+{MSeFG)Nnq%N3l;wLn*9x( z{((GhNw=Q~Xg?%#)Vw9&>fM~sv$a0q<H42Wq}CzHtzRgH!`chW#N#v1dc1=%Vt9 zSn_*1rA|md$^?Z0@qe-@_yc?;NE)qOhR8!*yeG&pp>J8ZPvow5SJRyLbntKAX|v6e z=Dh@DNRV%Qd$t?gFHh37d}9`Rt}AbHN4+!$EoVldB%Lb5T^Two5K)F-K_d|hSiIJ@ z6Gq9R{wc{%uUTFU)OI%C&=wF_kiNSPedI8W(R>?L<%&8YQ5x{H$JbI2e5f^f_(EMV zU2mW>^pYS+k9)G_OOrkImb5+nrKjDyZA0);CeQXa(O}}(nd!LdFQGmDkyww*zMe<9 z_@M-}G{+b~U*(LaKjU@8)rFWLzW>*3mEl`Izm&XaxQCs}`3kKVI=~oTdy*|{i1K0I zkY-$=G>v}|iWonb8Y`J3)p}xHEEXtv0xp|rp2kXk2znk8O-r(jbMM_8Vow;!WjKoRjynAQitJz zecU5wD?Gs=;fIa$X{`-Sc4OIjl8bMxOoU9Ub-73(UQhMF>90RmXV%`nOTz+0A|dL* zz$K#Pt!3X-hXIDU_qyT8F4a@bN7skJ^~aN0b1Zt{I*6hl?6zJrVXvS}Y)(sXlXDsA zosSZ(s+;QDJBk|1MK|0i*VEV|-&vZ84e`|e*iCv($JNOh@LfuW1I-{4I#Kw?H{{PI zU-g+Z@xFh!F=)*LnEu@- zT17zf4wz*JUQ}-XUtG8NdpD_`Lcmptzkv`4afP`;7x$aB8dsn=DN~)q;*v7pUn5ZD MrB$R#B}_m58|m{hd;kCd literal 0 HcmV?d00001 diff --git a/docs/articles/index.html b/docs/articles/index.html index 3d79b0f4..472b0975 100644 --- a/docs/articles/index.html +++ b/docs/articles/index.html @@ -17,7 +17,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -43,6 +43,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • @@ -95,6 +98,8 @@

    All vignettes

    Using the downloadablePlot Shiny Module
    +
    Using downloadableReactTable Shiny Module
    +
    Using the downloadableTable Shiny Module
    Using the downloadFile Shiny Module
    diff --git a/docs/articles/logViewer-module.html b/docs/articles/logViewer-module.html index 0d1e4b00..ed2098e5 100644 --- a/docs/articles/logViewer-module.html +++ b/docs/articles/logViewer-module.html @@ -32,7 +32,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -60,6 +60,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • @@ -108,7 +111,7 @@

    Using the logViewer Shiny Module

    Mohammed Ali

    -

    2025-04-14

    +

    2025-09-02

    Source: vignettes/logViewer-module.Rmd @@ -140,6 +143,16 @@

    Features
  • Many actions are automatically logged by the framework and it is easy for developers to add additional items as they see fit.
  • +
  • Filtering logs by setting log_level argument as desired in +set_app_parameters +
      +
    • “DEBUG” will log logdebug, loginfo, logwarn, and logerror +messages
    • +
    • “INFO” will log loginfo, logwarn, and logerror messages
    • +
    • “WARNING” will log logwarn and logerror messages
    • +
    • “ERROR” will log logerror messages
    • +
    +
  • It is important to note that the log rolls over for each session and is reset if using the appReset module.
  • @@ -217,6 +230,8 @@

    Additional ResourcesTheme Configuration Builder +
  • downloadableReactTable +Module
  • diff --git a/docs/articles/migrate_to_v0_2_0.html b/docs/articles/migrate_to_v0_2_0.html index 0d3072a6..7b0a124a 100644 --- a/docs/articles/migrate_to_v0_2_0.html +++ b/docs/articles/migrate_to_v0_2_0.html @@ -32,7 +32,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -60,6 +60,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/articles/new-application.html b/docs/articles/new-application.html index fb6fb404..e27464ab 100644 --- a/docs/articles/new-application.html +++ b/docs/articles/new-application.html @@ -32,7 +32,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -60,6 +60,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • @@ -108,7 +111,7 @@

    Creating a new framework-based application

    Dr. Connie Brett

    -

    2025-04-14

    +

    2025-09-02

    Source: vignettes/new-application.Rmd @@ -789,6 +792,8 @@
    www/img Builder
  • Theme Configuration Builder
  • +
  • downloadableReactTable +Module
  • diff --git a/docs/articles/themeBuilder_addin.html b/docs/articles/themeBuilder_addin.html index 439fab93..f0294835 100644 --- a/docs/articles/themeBuilder_addin.html +++ b/docs/articles/themeBuilder_addin.html @@ -32,7 +32,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -60,6 +60,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • @@ -108,7 +111,7 @@

    Theme Configuration Builder

    Mohammed Ali

    -

    2025-04-14

    +

    2025-09-02

    Source: vignettes/themeBuilder_addin.Rmd @@ -280,6 +283,8 @@

    Downloaded Configuration File Usage
  • logViewer Module
  • applicationReset Module
  • +
  • downloadableReactTable +Module
  • diff --git a/docs/authors.html b/docs/authors.html index 7a9911b8..47b4b538 100644 --- a/docs/authors.html +++ b/docs/authors.html @@ -17,7 +17,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -43,6 +43,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • @@ -108,13 +111,13 @@

    Citation

    Ali M (2025). periscope2: Enterprise Streamlined 'shiny' Application Framework Using 'bs4Dash'. -R package version 0.2.4, http://periscopeapps.org:3838, https://github.com/Aggregate-Genius/periscope2. +R package version 0.3.0, http://periscopeapps.org:3838, https://github.com/Aggregate-Genius/periscope2.

    @Manual{,
       title = {periscope2: Enterprise Streamlined 'shiny' Application Framework Using 'bs4Dash'},
       author = {Mohammed Ali},
       year = {2025},
    -  note = {R package version 0.2.4, http://periscopeapps.org:3838},
    +  note = {R package version 0.3.0, http://periscopeapps.org:3838},
       url = {https://github.com/Aggregate-Genius/periscope2},
     }
    diff --git a/docs/index.html b/docs/index.html index c2113cc5..7f4d5b0a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -33,7 +33,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -61,6 +61,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • @@ -115,7 +118,7 @@

    OverviewPredefined but flexible templates for new Shiny applications with a default bs4Dash layout
  • Separation by file of functionality that exists in one of the three shiny scopes: global, server-global, and server-local
  • Generated applications are organized in an easy to follow and maintain folder structure based on files functionality
  • -
  • Off-the-shelf and ready to be used modules (‘Table Downloader’, ‘Plot Downloader’, ‘File Downloader’ and ‘Reset Application’
  • +
  • Off-the-shelf and ready to be used modules (‘Table/React Table Downloader’, ‘Plot Downloader’, ‘File Downloader’ and ‘Reset Application’
  • Different methods and tools to alert users and add useful information about application UI and server operations
  • Application logger with different levels and a UI tool to display and review recorded application logs
  • Application look and feel can be customized easily via ‘www/periscope_style.yaml’ or more advanced via ‘www/css/custom.css’
  • @@ -231,7 +234,11 @@

    Periscope2 Modules periscope2 - 0.2.4 + 0.3.0 @@ -43,6 +43,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • @@ -85,7 +88,21 @@

    Changelog

    - + +
    +

    New Features

    +
    • Added new module ?downloadableReactTable based on reactable package and downloadFile module
    • +
    +
    +

    Enhancements

    +
    • Added openxlsx2 support while keeping openxlsx to support legacy apps and backwards compatibility. writexl package is used as last resort.
    • +
    • Updated ?logViewerOutput module to display log data using ?downloadableReactTable.
    • +
    • Added loglevel filtering according to level chosen- Added loglevel filtering according to level chosen
    • +
    • Added new feature to ?downloadFile module to control whether row names are to be written for tabular data.
    • +

    +
    +
    +

    Enhancements

    • Updated set_app_parameters method documentation to displayed html tags correctly
    • diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index ae405c81..a7c6f493 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -6,10 +6,11 @@ articles: announcement-module: announcement-module.html applicationReset-module: applicationReset-module.html downloadablePlot-module: downloadablePlot-module.html + downloadableReactTable-module: downloadableReactTable-module.html downloadableTable-module: downloadableTable-module.html downloadFile-module: downloadFile-module.html logViewer-module: logViewer-module.html migrate_to_v0_2_0: migrate_to_v0_2_0.html new-application: new-application.html themeBuilder_addin: themeBuilder_addin.html -last_built: 2025-04-14T11:09Z +last_built: 2025-09-03T05:05Z diff --git a/docs/reference/add_ui_body.html b/docs/reference/add_ui_body.html index c221244a..519d77f1 100644 --- a/docs/reference/add_ui_body.html +++ b/docs/reference/add_ui_body.html @@ -18,7 +18,7 @@ periscope2 - 0.2.4 + 0.3.0
    @@ -44,6 +44,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/reference/add_ui_footer.html b/docs/reference/add_ui_footer.html index eaf2d230..8a6e188d 100644 --- a/docs/reference/add_ui_footer.html +++ b/docs/reference/add_ui_footer.html @@ -18,7 +18,7 @@ periscope2 - 0.2.4 + 0.3.0
    @@ -44,6 +44,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/reference/add_ui_header.html b/docs/reference/add_ui_header.html index 499e31eb..4b622156 100644 --- a/docs/reference/add_ui_header.html +++ b/docs/reference/add_ui_header.html @@ -18,7 +18,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -44,6 +44,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/reference/add_ui_left_sidebar.html b/docs/reference/add_ui_left_sidebar.html index d99e64fd..15acdb31 100644 --- a/docs/reference/add_ui_left_sidebar.html +++ b/docs/reference/add_ui_left_sidebar.html @@ -18,7 +18,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -44,6 +44,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/reference/add_ui_right_sidebar.html b/docs/reference/add_ui_right_sidebar.html index b5da2b94..6b3fa7d3 100644 --- a/docs/reference/add_ui_right_sidebar.html +++ b/docs/reference/add_ui_right_sidebar.html @@ -18,7 +18,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -44,6 +44,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/reference/announcementConfigurationsAddin.html b/docs/reference/announcementConfigurationsAddin.html index 693f0578..22b26d60 100644 --- a/docs/reference/announcementConfigurationsAddin.html +++ b/docs/reference/announcementConfigurationsAddin.html @@ -18,7 +18,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -44,6 +44,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/reference/appReset.html b/docs/reference/appReset.html index 738d23ef..51fabd09 100644 --- a/docs/reference/appReset.html +++ b/docs/reference/appReset.html @@ -19,7 +19,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -45,6 +45,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/reference/appResetButton.html b/docs/reference/appResetButton.html index 64e9a7c9..f865f676 100644 --- a/docs/reference/appResetButton.html +++ b/docs/reference/appResetButton.html @@ -20,7 +20,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -46,6 +46,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/reference/createPSAlert.html b/docs/reference/createPSAlert.html index ad4c560a..49afe05e 100644 --- a/docs/reference/createPSAlert.html +++ b/docs/reference/createPSAlert.html @@ -17,7 +17,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -43,6 +43,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/reference/create_application.html b/docs/reference/create_application.html index b072fd4d..24b8e69e 100644 --- a/docs/reference/create_application.html +++ b/docs/reference/create_application.html @@ -19,7 +19,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -45,6 +45,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • @@ -218,25 +221,25 @@

    Examples

    # sample app named 'mytestapp' created in a temp dir
     location <- tempdir()
     create_application(name = 'mytestapp', location = location, sample_app = TRUE)
    -#> periscope2 application mytestapp was created successfully at /tmp/RtmpBjjCUk
    +#> periscope2 application mytestapp was created successfully at /tmp/Rtmpk1yVfS
     unlink(paste0(location,'/mytestapp'), TRUE)
     
     # sample app named 'mytestapp' with a right sidebar using a custom icon created in a temp dir
     location <- tempdir()
     create_application(name = 'mytestapp', location = location, sample_app = TRUE, right_sidebar = TRUE)
    -#> periscope2 application mytestapp was created successfully at /tmp/RtmpBjjCUk
    +#> periscope2 application mytestapp was created successfully at /tmp/Rtmpk1yVfS
     unlink(paste0(location,'/mytestapp'), TRUE)
     
     # blank app named 'myblankapp' created in a temp dir
     location <- tempdir()
     create_application(name = 'myblankapp', location = location)
    -#> periscope2 application myblankapp was created successfully at /tmp/RtmpBjjCUk
    +#> periscope2 application myblankapp was created successfully at /tmp/Rtmpk1yVfS
     unlink(paste0(location,'/myblankapp'), TRUE)
     
     # blank app named 'myblankapp' without a left sidebar created in a temp dir
     location <- tempdir()
     create_application(name = 'myblankapp', location = location, left_sidebar = FALSE)
    -#> periscope2 application myblankapp was created successfully at /tmp/RtmpBjjCUk
    +#> periscope2 application myblankapp was created successfully at /tmp/Rtmpk1yVfS
     unlink(paste0(location,'/myblankapp'), TRUE)
     
     
    diff --git a/docs/reference/create_left_sidebar.html b/docs/reference/create_left_sidebar.html index 590b78fc..dd2aaa19 100644 --- a/docs/reference/create_left_sidebar.html +++ b/docs/reference/create_left_sidebar.html @@ -17,7 +17,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -43,6 +43,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/reference/create_right_sidebar.html b/docs/reference/create_right_sidebar.html index c5cbf3dc..be481434 100644 --- a/docs/reference/create_right_sidebar.html +++ b/docs/reference/create_right_sidebar.html @@ -17,7 +17,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -43,6 +43,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/reference/downloadFile.html b/docs/reference/downloadFile.html index a949eef1..9331c5ae 100644 --- a/docs/reference/downloadFile.html +++ b/docs/reference/downloadFile.html @@ -19,7 +19,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -45,6 +45,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • @@ -99,7 +102,8 @@

    downloadFile module server function

    logger = NULL, filenameroot = "download", datafxns = NULL, - aspectratio = 1 + aspectratio = 1, + row_names = TRUE ) @@ -132,6 +136,11 @@

    Arguments

    1 = square, 1.3 = 4:3, 0.5 = 1:2). Where not applicable for a download type it is ignored (e.g. data downloads).

    + +
    row_names
    +

    logical value indicating whether row names are to be written +for tabular data. Where not applicable for a download type it is ignored.

    +

    Value

    @@ -177,7 +186,7 @@

    Examples

    logger = "", filenameroot = "mydownload1", datafxns = list(csv = reactiveVal(iris)), - aspectratio = 1) + row_names = FALSE) # multiple download types downloadFile(id = "object_id2", logger = "", diff --git a/docs/reference/downloadFileButton.html b/docs/reference/downloadFileButton.html index 61b1d085..5d992a64 100644 --- a/docs/reference/downloadFileButton.html +++ b/docs/reference/downloadFileButton.html @@ -20,7 +20,7 @@ periscope2 - 0.2.4 + 0.3.0
    @@ -46,6 +46,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • @@ -174,7 +177,7 @@

    Examples

    logger = "", filenameroot = "mydownload1", datafxns = list(csv = reactiveVal(iris)), - aspectratio = 1) + row_names = FALSE) # multiple download types downloadFile(id = "object_id2", logger = "", diff --git a/docs/reference/downloadFile_AvailableTypes.html b/docs/reference/downloadFile_AvailableTypes.html index 7a6476e6..573a1a83 100644 --- a/docs/reference/downloadFile_AvailableTypes.html +++ b/docs/reference/downloadFile_AvailableTypes.html @@ -17,7 +17,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -43,6 +43,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/reference/downloadFile_ValidateTypes.html b/docs/reference/downloadFile_ValidateTypes.html index f37569ab..76c1733c 100644 --- a/docs/reference/downloadFile_ValidateTypes.html +++ b/docs/reference/downloadFile_ValidateTypes.html @@ -17,7 +17,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -43,6 +43,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/reference/downloadablePlot.html b/docs/reference/downloadablePlot.html index 6eb200d7..891ef861 100644 --- a/docs/reference/downloadablePlot.html +++ b/docs/reference/downloadablePlot.html @@ -18,7 +18,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -44,6 +44,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • @@ -190,9 +193,9 @@

    Examples

    download_plot <- function() { ggplot(data = mtcars, aes(x = wt, y = mpg)) + geom_point(aes(color = cyl)) + - theme(legend.justification = c(1, 1), - legend.position = c(1, 1), - legend.title = element_blank()) + + theme(legend.justification = c(1, 1), + legend.position.inside = c(1, 1), + legend.title = element_blank()) + ggtitle("GGPlot Example ") + xlab("wt") + ylab("mpg") diff --git a/docs/reference/downloadablePlotUI.html b/docs/reference/downloadablePlotUI.html index 21ae0b5c..854ebf12 100644 --- a/docs/reference/downloadablePlotUI.html +++ b/docs/reference/downloadablePlotUI.html @@ -19,7 +19,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -45,6 +45,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • @@ -226,9 +229,9 @@

    Examples

    download_plot <- function() { ggplot(data = mtcars, aes(x = wt, y = mpg)) + geom_point(aes(color = cyl)) + - theme(legend.justification = c(1, 1), - legend.position = c(1, 1), - legend.title = element_blank()) + + theme(legend.justification = c(1, 1), + legend.position.inside = c(1, 1), + legend.title = element_blank()) + ggtitle("GGPlot Example ") + xlab("wt") + ylab("mpg") diff --git a/docs/reference/downloadableReactTable.html b/docs/reference/downloadableReactTable.html new file mode 100644 index 00000000..0d207525 --- /dev/null +++ b/docs/reference/downloadableReactTable.html @@ -0,0 +1,276 @@ + +downloadableReactTable module server function — downloadableReactTable • periscope2 + + +
    +
    + + + +
    +
    + + +
    +

    Server-side function for the downloadableReactTableUI.

    +
    + +
    +
    downloadableReactTable(
    +  id,
    +  table_data,
    +  selection_mode = NULL,
    +  pre_selected_rows = NULL,
    +  file_name_root = "data_file",
    +  download_data_fxns = NULL,
    +  pagination = FALSE,
    +  table_height = 600,
    +  show_rownames = FALSE,
    +  columns_filter = FALSE,
    +  global_search = TRUE,
    +  row_highlight = TRUE,
    +  row_striping = TRUE,
    +  table_options = list(),
    +  logger = NULL
    +)
    +
    + +
    +

    Arguments

    + + +
    id
    +

    the ID of the Module's UI element

    + + +
    table_data
    +

    reactive expression (or parameter-less function) that acts as table data source

    + + +
    selection_mode
    +

    to enable row selection, set selection_mode value to either "single" for single row +selection or "multiple" for multiple rows selection, case insensitive. Any other value will +disable row selection. Row selection will be enabled by radio buttons in "single" selection +and checkboxes in "multiple" selection (default = NULL)

    + + +
    pre_selected_rows
    +

    reactive expression (or parameter-less function) provides the rows indices of the rows to +be selected when the table is rendered. If selection_mode is disabled, this parameter will +have no effect. If selection_mode is "single" only the first row index will be used (default = NULL)

    + + +
    file_name_root
    +

    the base name used for user-downloaded file. It can be either a character string +a reactive expression or a function returning a character string (default = 'data_file')

    + + +
    download_data_fxns
    +

    a named list of functions providing the data as return values. +The names for the list should be the same names as the ones used in the +downloadableReactTableUI (default = NULL)

    + + +
    pagination
    +

    to enable table pagination (default = FALSE)

    + + +
    table_height
    +

    max table height in pixels. Vertical scroll will be shown after that height value

    + + +
    show_rownames
    +

    enable displaying rownames as a separate column (default = FALSE)

    + + +
    columns_filter
    +

    enable the filtering input on each column in the table (default = FALSE)

    + + + +

    enable table global searching input to search and filter in all columns at once +(default = TRUE)

    + + +
    row_highlight
    +

    enable highlighting rows upon mouse hover (default = TRUE)

    + + +
    row_striping
    +

    add zebra-striped style to table rows (default = TRUE)

    + + +
    table_options
    +

    optional table formatting parameters check ?reactable::reactable for options full list. +Also see example below to see how to pass options (default = list())

    + + +
    logger
    +

    logger to use (default = NULL)

    + +
    +
    +

    Value

    +

    A named list of two elements:

    • selected_rows: data.frame of current selected rows

    • +
    • table_state: a list of the current table state. The list keys are +("page", "pageSize", "pages", "sorted" and "selected"). +Review ?reactable::getReactableState for more info.

    • +
    +
    +

    Shiny Usage

    + + +

    This function is not called directly by consumers - it is accessed in +server.R using the same id provided in downloadableReactTableUI:

    +

    downloadableReactTable(id)

    +
    + + +
    +

    Examples

    +
    if (interactive()) {
    + library(shiny)
    + library(periscope2)
    + library(reactable)
    +
    + shinyApp(
    +     ui = fluidPage(fluidRow(column(
    +         width = 12,
    +         downloadableReactTableUI(
    +             id            = "object_id1",
    +             downloadtypes = c("csv", "tsv"),
    +             hovertext     = "Download the data here!")))),
    +     server = function(input, output) {
    +         table_state <- downloadableReactTable(
    +             id                 = "object_id1",
    +             table_data         = reactiveVal(iris),
    +             download_data_fxns = list(csv = reactiveVal(iris), tsv = reactiveVal(iris)),
    +             selection_mode     = "multiple",
    +             pre_selected_rows  = function() {c(1, 3, 5)},
    +             table_options      = list(columns = list(
    +                 Sepal.Length = colDef(name = "Sepal Length"),
    +                 Sepal.Width  = colDef(filterable = TRUE),
    +                 Petal.Length = colDef(show = FALSE),
    +                 Petal.Width  = colDef(defaultSortOrder = "desc")),
    +                 theme = reactableTheme(
    +                     borderColor = "#dfe2e5",
    +                     stripedColor = "#f6f8fa",
    +                     highlightColor = "#f0f5f9",
    +                     cellPadding = "8px 12px")))
    +
    +        observeEvent(table_state(), { print(table_state()) })
    +    })
    +}
    +
    +
    +
    +
    + +
    + + +
    + +
    +

    Site built with pkgdown 2.1.1.

    +
    + +
    + + + + + + + + diff --git a/docs/reference/downloadableReactTableUI.html b/docs/reference/downloadableReactTableUI.html new file mode 100644 index 00000000..8edd37f2 --- /dev/null +++ b/docs/reference/downloadableReactTableUI.html @@ -0,0 +1,238 @@ + +downloadableReactTable module UI function — downloadableReactTableUI • periscope2 + + +
    +
    + + + +
    +
    + + +
    +

    downloadableReactTable module is extending ?reactable package table functions by creating +a custom high-functionality table paired with downloadFile button. +The table has the following default functionality:search, highlight functionality, infinite scrolling, sorting by columns and +returns a reactive dataset of selected items and table current state.

    +
    + +
    +
    downloadableReactTableUI(id, downloadtypes = NULL, hovertext = NULL)
    +
    + +
    +

    Arguments

    + + +
    id
    +

    character id for the object

    + + +
    downloadtypes
    +

    vector of values for data download types

    + + +
    hovertext
    +

    download button tooltip hover text

    + +
    +
    +

    Value

    +

    list of downloadFileButton UI and reactable table and hidden inputs for contentHeight option

    +
    +
    +

    Details

    +

    downloadFile button will be hidden if downloadableReactTableUI parameter +downloadtypes is empty

    +
    +
    +

    Table Features

    + + +
    • Consistent styling of the table

    • +
    • downloadFile module button functionality built-in to the table (it will be shown only if downloadtypes is defined)

    • +
    • Ability to show different data from the download data

    • +
    • Table is automatically fit to the window size with infinite +y-scrolling

    • +
    • Table search functionality including highlighting built-in

    • +
    • Multi-select built in, including reactive feedback on which table +items are selected

    • +
    +
    +

    Example

    + + +

    downloadableReactTableUI("mytableID", c("csv", "tsv"), +"Click Here")

    +
    +
    +

    Notes

    + + +

    When there are no rows to download in any of the linked downloaddatafxns the +button will be hidden as there is nothing to download.

    +
    +
    +

    Shiny Usage

    + + +

    Call this function at the place in ui.R where the table should be placed.

    +

    Paired with a call to downloadableReactTable(id, ...) +in server.R

    +
    + + +
    +

    Examples

    +
    if (interactive()) {
    + library(shiny)
    + library(periscope2)
    + library(reactable)
    +
    + shinyApp(
    +     ui = fluidPage(fluidRow(column(
    +         width = 12,
    +         downloadableReactTableUI(
    +             id            = "object_id1",
    +             downloadtypes = c("csv", "tsv"),
    +             hovertext     = "Download the data here!")))),
    +     server = function(input, output) {
    +         table_state <- downloadableReactTable(
    +             id                 = "object_id1",
    +             table_data         = reactiveVal(iris),
    +             download_data_fxns = list(csv = reactiveVal(iris), tsv = reactiveVal(iris)),
    +             selection_mode     = "multiple",
    +             pre_selected_rows  = function() {c(1, 3, 5)},
    +             table_options      = list(columns = list(
    +                 Sepal.Length = colDef(name = "Sepal Length"),
    +                 Sepal.Width  = colDef(filterable = TRUE),
    +                 Petal.Length = colDef(show = FALSE),
    +                 Petal.Width  = colDef(defaultSortOrder = "desc")),
    +                 showSortable = TRUE,
    +                 theme = reactableTheme(
    +                     borderColor = "#dfe2e5",
    +                     stripedColor = "#f6f8fa",
    +                     highlightColor = "#f0f5f9",
    +                     cellPadding = "8px 12px")))
    +
    +        observeEvent(table_state(), { print(table_state()) })
    +    })
    +}
    +
    +
    +
    +
    + +
    + + +
    + +
    +

    Site built with pkgdown 2.1.1.

    +
    + +
    + + + + + + + + diff --git a/docs/reference/downloadableTable.html b/docs/reference/downloadableTable.html index daa4272c..50fee61b 100644 --- a/docs/reference/downloadableTable.html +++ b/docs/reference/downloadableTable.html @@ -19,7 +19,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -45,6 +45,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/reference/downloadableTableUI.html b/docs/reference/downloadableTableUI.html index 8867e2e4..969af537 100644 --- a/docs/reference/downloadableTableUI.html +++ b/docs/reference/downloadableTableUI.html @@ -19,7 +19,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -45,6 +45,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/reference/get_url_parameters.html b/docs/reference/get_url_parameters.html index d6b77367..9bde4aa2 100644 --- a/docs/reference/get_url_parameters.html +++ b/docs/reference/get_url_parameters.html @@ -18,7 +18,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -44,6 +44,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/reference/index.html b/docs/reference/index.html index f358f82f..2a7aacab 100644 --- a/docs/reference/index.html +++ b/docs/reference/index.html @@ -17,7 +17,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -43,6 +43,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • @@ -159,6 +162,14 @@

    All functions downloadablePlotUI()

    downloadablePlot module UI function

    + +

    downloadableReactTable()

    + +

    downloadableReactTable module server function

    + +

    downloadableReactTableUI()

    + +

    downloadableReactTable module UI function

    downloadableTable()

    diff --git a/docs/reference/load_announcements.html b/docs/reference/load_announcements.html index b16b3ae7..54bd7551 100644 --- a/docs/reference/load_announcements.html +++ b/docs/reference/load_announcements.html @@ -18,7 +18,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -44,6 +44,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/reference/logViewerOutput.html b/docs/reference/logViewerOutput.html index 5c61fff1..b006d291 100644 --- a/docs/reference/logViewerOutput.html +++ b/docs/reference/logViewerOutput.html @@ -1,6 +1,7 @@ -Display app logs — logViewerOutput • periscope2Display app logs — logViewerOutput • periscope2 @@ -18,7 +19,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -44,6 +45,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • @@ -87,8 +91,9 @@

    Display app logs

    -

    Creates a shiny table with table containing logged user actions. Table contents are auto updated whenever a user action is -logged. The id must match the same id configured in server.R file upon calling fw_server_setup method

    +

    Display app log data in downloadableReactTable table containing logged user actions. Table contents are auto updated +whenever a user action is logged. User can search for logs, sort them by time and download them in CSV or TSV format. +The id must match the same id configured in server.R file upon calling fw_server_setup method

    @@ -105,7 +110,7 @@

    Arguments

    Value

    -

    shiny tableOutput instance

    +

    downloadableReactTableUI instance

    Table columns

    @@ -143,7 +148,28 @@

    Examples

    # Inside ui_body add the log viewer box to your box list
     
     logViewerOutput('logViewerId')
    -#> <div id="logViewerId-logViewerId" class="shiny-html-output"></div>
    +#> [[1]]
    +#> <div class="shiny-panel-conditional" data-display-if="output.displayButton" data-ns-prefix="logViewerId-logViewerId-">
    +#>   <span id="logViewerId-logViewerId-reactTableButtonDiv" class="periscope-downloadable-react-table-button" style="">
    +#>     <span class="btn-group" data-toggle="tooltip" data-placement="top" title="Download application logs">
    +#>       <button aria-expanded="false" aria-haspopup="true" class="btn btn-default action-button dropdown-toggle periscope-download-btn" data-toggle="dropdown" id="logViewerId-logViewerId-reactTableButtonID-downloadFileList" type="button action">
    +#>         <i class="far fa-copy" role="presentation" aria-label="copy icon"></i>
    +#>       </button>
    +#>       <ul class="dropdown-menu" id="logViewerId-logViewerId-reactTableButtonID-testList">
    +#>         <li>
    +#>           <a aria-disabled="true" class="shiny-download-link disabled periscope-download-choice" download href="" id="logViewerId-logViewerId-reactTableButtonID-csv" tabindex="-1" target="_blank">csv</a>
    +#>         </li>
    +#>         <li>
    +#>           <a aria-disabled="true" class="shiny-download-link disabled periscope-download-choice" download href="" id="logViewerId-logViewerId-reactTableButtonID-tsv" tabindex="-1" target="_blank">tsv</a>
    +#>         </li>
    +#>       </ul>
    +#>     </span>
    +#>   </span>
    +#> </div>
    +#> 
    +#> [[2]]
    +#> <div class="reactable html-widget html-widget-output shiny-report-size html-fill-item" data-reactable-output="logViewerId-logViewerId-reactTableOutputID" id="logViewerId-logViewerId-reactTableOutputID" style="width:auto;height:auto;"></div>
    +#> 
     
     
     
    diff --git a/docs/reference/logging-entrypoints.html b/docs/reference/logging-entrypoints.html index a696fe57..111425c9 100644 --- a/docs/reference/logging-entrypoints.html +++ b/docs/reference/logging-entrypoints.html @@ -17,7 +17,7 @@ periscope2 - 0.2.4 + 0.3.0
    @@ -43,6 +43,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/reference/periscope2.html b/docs/reference/periscope2.html index f7d82eee..ba853cc6 100644 --- a/docs/reference/periscope2.html +++ b/docs/reference/periscope2.html @@ -21,7 +21,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -47,6 +47,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/reference/set_app_parameters.html b/docs/reference/set_app_parameters.html index c935d94e..17cac068 100644 --- a/docs/reference/set_app_parameters.html +++ b/docs/reference/set_app_parameters.html @@ -17,7 +17,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -43,6 +43,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • @@ -122,7 +125,7 @@

    Arguments

    log_level
    -

    Designating the log level to use for the user log as 'DEBUG','INFO', 'WARN' or 'ERROR' (default = 'DEBUG')

    +

    Designating the log level to use for the user log as 'DEBUG','INFO', 'WARNING' or 'ERROR' (default = 'DEBUG')

    app_version
    diff --git a/docs/reference/themeConfigurationsAddin.html b/docs/reference/themeConfigurationsAddin.html index cc93464c..431f5dfb 100644 --- a/docs/reference/themeConfigurationsAddin.html +++ b/docs/reference/themeConfigurationsAddin.html @@ -18,7 +18,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -44,6 +44,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/reference/ui_tooltip.html b/docs/reference/ui_tooltip.html index 0a3d3c91..f5f933c1 100644 --- a/docs/reference/ui_tooltip.html +++ b/docs/reference/ui_tooltip.html @@ -18,7 +18,7 @@ periscope2 - 0.2.4 + 0.3.0 @@ -44,6 +44,9 @@
  • Using the downloadablePlot Shiny Module
  • +
  • + Using downloadableReactTable Shiny Module +
  • Using the downloadableTable Shiny Module
  • diff --git a/docs/sitemap.xml b/docs/sitemap.xml index d7dd9a40..19a48ff5 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -5,6 +5,7 @@ /articles/applicationReset-module.html /articles/downloadFile-module.html /articles/downloadablePlot-module.html +/articles/downloadableReactTable-module.html /articles/downloadableTable-module.html /articles/index.html /articles/logViewer-module.html @@ -33,6 +34,8 @@ /reference/downloadFile_ValidateTypes.html /reference/downloadablePlot.html /reference/downloadablePlotUI.html +/reference/downloadableReactTable.html +/reference/downloadableReactTableUI.html /reference/downloadableTable.html /reference/downloadableTableUI.html /reference/get_url_parameters.html From 967b48462e3608aa31b4ebd9e1861d90b63ad188 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Tue, 2 Sep 2025 23:38:32 -0700 Subject: [PATCH 103/139] - Updated cran comments --- cran-comments.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cran-comments.md b/cran-comments.md index 88cceef0..b0e49e7c 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -12,7 +12,7 @@ RStudio 2023.06.1+524 (Windows 11 x64 (build 22621)) CircleCI * R 4.0.5 -* R 4.5.0 +* R 4.5.1 devtools From 120af11c23a6251c95bdd178c6b3d0d692fdbc38 Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Wed, 8 Jul 2026 06:00:18 -0700 Subject: [PATCH 104/139] Updated NEWS and package version --- DESCRIPTION | 4 ++-- NEWS.md | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 9a11a5d9..1086dd70 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: periscope2 Type: Package Title: Enterprise Streamlined 'shiny' Application Framework Using 'bs4Dash' -Version: 0.3.0 +Version: 0.3.1.9000 Authors@R: c( person("Mohammed", "Ali", role = c("aut", "cre"), email = "mohammed@aggregate-genius.com"), person("Constance", "Brett", role = c("ctb")), @@ -34,7 +34,7 @@ Imports: writexl, yaml, lifecycle -RoxygenNote: 7.3.2 +RoxygenNote: 7.3.3 Suggests: assertthat, canvasXpress, diff --git a/NEWS.md b/NEWS.md index b5196680..35dc7369 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,9 @@ +# periscope2 0.3.1 + +## Enhancements + +----- + # periscope2 0.3.0 ## New Features From 0e95800bb2ff1b17588f5bce969d116f17cd276c Mon Sep 17 00:00:00 2001 From: Mohammed Ali Date: Wed, 8 Jul 2026 07:21:51 -0700 Subject: [PATCH 105/139] Updated ui functions unit tests --- tests/testthat/_snaps/ui_functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/_snaps/ui_functions.md b/tests/testthat/_snaps/ui_functions.md index 324f167d..ba5acdf1 100644 --- a/tests/testthat/_snaps/ui_functions.md +++ b/tests/testthat/_snaps/ui_functions.md @@ -419,7 +419,7 @@ Working...
    - +
    Announcements Banner Configuration File @@ -257,33 +215,26 @@

    Sample Application - - - + - - diff --git a/docs/articles/announcement_addin.html b/docs/articles/announcement_addin.html index bfdf5296..ac719d7b 100644 --- a/docs/articles/announcement_addin.html +++ b/docs/articles/announcement_addin.html @@ -1,122 +1,79 @@ - + - + Announcement Configuration YAML File Builder • periscope2 - - - - - - - + + + + + - + + Skip to contents -
    -
    - - - +
    - - diff --git a/docs/articles/applicationReset-module.html b/docs/articles/applicationReset-module.html index e66bb8a0..05c5c168 100644 --- a/docs/articles/applicationReset-module.html +++ b/docs/articles/applicationReset-module.html @@ -1,121 +1,79 @@ - + - + Using the appReset Shiny Module • periscope2 - - - - - - - + + + + + - - - -
    -
    -
    - @@ -169,7 +127,7 @@

    Shiny Module OverviewappResetButton

    - +
    App Reset Toggle Button @@ -183,19 +141,19 @@

    appResetButton
    - +
    Reset Warning
    - +
    Cancel Reset
    - +
    Canceled reset request @@ -258,33 +216,26 @@

    Sample Application - -

    - +
    -
    - diff --git a/docs/articles/downloadFile-module.html b/docs/articles/downloadFile-module.html index 00640e6a..43a414dc 100644 --- a/docs/articles/downloadFile-module.html +++ b/docs/articles/downloadFile-module.html @@ -1,121 +1,79 @@ - + - + Using the downloadFile Shiny Module • periscope2 - - - - - - - + + + + + - + + Skip to contents -
    -
    -
    - - diff --git a/docs/articles/downloadablePlot-module.html b/docs/articles/downloadablePlot-module.html index 986b4432..f2a86c16 100644 --- a/docs/articles/downloadablePlot-module.html +++ b/docs/articles/downloadablePlot-module.html @@ -1,121 +1,79 @@ - + - + Using the downloadablePlot Shiny Module • periscope2 - - - - - - - + + + + + - + + Skip to contents -
    -
    -
    - -
    - diff --git a/docs/articles/downloadableReactTable-module.html b/docs/articles/downloadableReactTable-module.html index 98f07036..d87ec2ec 100644 --- a/docs/articles/downloadableReactTable-module.html +++ b/docs/articles/downloadableReactTable-module.html @@ -1,121 +1,79 @@ - + - + Using downloadableReactTable Shiny Module • periscope2 - - - - - - - + + + + + - + + Skip to contents -
    -
    -
    - -
    - diff --git a/docs/articles/downloadableTable-module.html b/docs/articles/downloadableTable-module.html index 8aa96057..a45d6165 100644 --- a/docs/articles/downloadableTable-module.html +++ b/docs/articles/downloadableTable-module.html @@ -1,121 +1,79 @@ - + - + Using the downloadableTable Shiny Module • periscope2 - - - - - - - + + + + + - + + Skip to contents -
    -
    -
    - - diff --git a/docs/articles/index.html b/docs/articles/index.html index 472b0975..14140f2b 100644 --- a/docs/articles/index.html +++ b/docs/articles/index.html @@ -1,94 +1,53 @@ -Articles • periscope2 - - -
    -
    +
    +
    +
    -
    -

    All vignettes

    -

    +
    Announcement Configuration YAML File Builder
    @@ -113,20 +72,18 @@

    All vignettes

    Theme Configuration Builder
    -
    -
    +
    -
    - +
    diff --git a/docs/articles/logViewer-module.html b/docs/articles/logViewer-module.html index ed2098e5..2561a2da 100644 --- a/docs/articles/logViewer-module.html +++ b/docs/articles/logViewer-module.html @@ -1,121 +1,79 @@ - + - + Using the logViewer Shiny Module • periscope2 - - - - - - - + + + + + - - - -
    -
    -
    - @@ -157,7 +115,7 @@

    Features
    - +


    @@ -234,33 +192,26 @@

    Additional Resources - -

    - +
    -
    - diff --git a/docs/articles/migrate_to_v0_2_0.html b/docs/articles/migrate_to_v0_2_0.html index 7b0a124a..fbc77115 100644 --- a/docs/articles/migrate_to_v0_2_0.html +++ b/docs/articles/migrate_to_v0_2_0.html @@ -1,118 +1,76 @@ - + - + Migrate periscope2 applications to v0.2.0 • periscope2 - - - - - - - + + + + + - - - -
    -
    -
    - - - - +
    -
    - diff --git a/docs/articles/new-application.html b/docs/articles/new-application.html index e27464ab..c5fe164c 100644 --- a/docs/articles/new-application.html +++ b/docs/articles/new-application.html @@ -1,121 +1,79 @@ - + - + Creating a new framework-based application • periscope2 - - - - - - - + + + + + - + + Skip to contents -
    -
    -
    -
    - +
    @@ -760,7 +718,7 @@
    www/periscope_style.yaml

    Part of the file

    - +
    -
    - - - +
    -
    - diff --git a/docs/articles/themeBuilder_addin.html b/docs/articles/themeBuilder_addin.html index f0294835..a28da290 100644 --- a/docs/articles/themeBuilder_addin.html +++ b/docs/articles/themeBuilder_addin.html @@ -1,121 +1,79 @@ - + - + Theme Configuration Builder • periscope2 - - - - - - - + + + + + - + + Skip to contents -
    -
    -
    - @@ -132,7 +90,7 @@

    Add-in Launch -Add-in Launch
    Add-in Launch
    +Add-in Launch
    Add-in Launch

    -
    - - - +
    -
    - diff --git a/docs/authors.html b/docs/authors.html index 47b4b538..3db3ac44 100644 --- a/docs/authors.html +++ b/docs/authors.html @@ -1,92 +1,52 @@ -Authors and Citation • periscope2 - - -
    -
    +
    -
    -
    - +
    +
    +
    +
    + +
    +

    Authors

    • Mohammed Ali. Author, maintainer. @@ -101,42 +61,37 @@

      Authors and Citation

    -
    -
    -

    Citation

    - Source: DESCRIPTION -
    -
    +
    +

    Citation

    +

    Source: DESCRIPTION

    -

    Ali M (2025). +

    Ali M (2026). periscope2: Enterprise Streamlined 'shiny' Application Framework Using 'bs4Dash'. -R package version 0.3.0, http://periscopeapps.org:3838, https://github.com/Aggregate-Genius/periscope2. +R package version 0.4.0.9001, https://github.com/Aggregate-Genius/periscope2.

    -
    @Manual{,
    +      
    @Manual{,
       title = {periscope2: Enterprise Streamlined 'shiny' Application Framework Using 'bs4Dash'},
       author = {Mohammed Ali},
    -  year = {2025},
    -  note = {R package version 0.3.0, http://periscopeapps.org:3838},
    +  year = {2026},
    +  note = {R package version 0.4.0.9001},
       url = {https://github.com/Aggregate-Genius/periscope2},
     }
    +
    -
    - -
    - +
    -
    - +
    diff --git a/docs/index.html b/docs/index.html index 7f4d5b0a..6572eda0 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,114 +1,68 @@ - + - + Enterprise Streamlined shiny Application Framework Using bs4Dash • periscope2 - - - - - - + + + + + + - - + + Skip to contents -
    -
    -
    +
    +
    +
    +

    Overview

    @@ -134,10 +88,10 @@

    Installation
    -devtools::install_cran("periscope2")

    +devtools::install_cran("periscope2")

    and latest development version of periscope2 from GitHub as follows:

    -devtools::install_github('Aggregate-Genius/periscope2')
    +devtools::install_github('Aggregate-Genius/periscope2')



    @@ -306,10 +260,7 @@

    Sample application - inc

    -
    - - -
    - diff --git a/docs/news/index.html b/docs/news/index.html index 7748c11d..ab3c5d28 100644 --- a/docs/news/index.html +++ b/docs/news/index.html @@ -1,94 +1,65 @@ -Changelog • periscope2 - - -
    -
    +
    -
    - +
    +
    +
    - +

    periscope2 0.4.0

    +
    +

    New Features

    +
    • Bumped minimum R version to 4.2 to keep up with modern shiny/promises/httr2/testthat.
    • +
    +
    +

    Bug Fixes

    +
    • Fixed renv installation error
    • +
    • Fixed ?downloadableReactTable module return value reactivity issue
    • +

    +
    +
    +

    periscope2 0.3.0

    CRAN release: 2025-09-04

    New Features

    • Added new module ?downloadableReactTable based on reactable package and downloadFile module
    • @@ -102,7 +73,7 @@

      Enhancements

    - +

    periscope2 0.2.4

    CRAN release: 2025-04-14

    Enhancements

    • Updated set_app_parameters method documentation to displayed html tags correctly
    • @@ -111,7 +82,7 @@

      Enhancements

    - +

    periscope2 0.2.3

    CRAN release: 2024-03-06

    Enhancements

    • Re-factored header layout to be more flexible with menu items
    • @@ -129,7 +100,7 @@

      Bug Fixes - +

      periscope2 0.2.2

      CRAN release: 2024-01-08

      New Features

      • Introduced application theme configuration file generator RStudio add-in
      • @@ -144,7 +115,7 @@

        Enhancements

    - +

    periscope2 0.1.4

    CRAN release: 2023-11-14

    New Features

    • Introduced Announcements configuration file generator RStudio add-in
    • @@ -163,7 +134,7 @@

      Bug Fixes - +

      periscope2 0.1.3

      CRAN release: 2023-08-24

      Enhancements

      • Removed operator “:::” usage from documentation examples
      • @@ -171,14 +142,14 @@

        Enhancements

    - +

    periscope2 0.1.2

    Enhancements

    • Updated shiny modules examples to be executable in an interactive environment

    - +

    periscope2 0.1.1

    Enhancements

    • DESCRIPTION file changes: @@ -195,28 +166,22 @@

      Enhancements

    - +

    periscope2 0.1.0

    • Initial CRAN release
    -
    +
    - +
    - -
    - +
    diff --git a/docs/pkgdown.js b/docs/pkgdown.js index 6f0eee40..0a5573ae 100644 --- a/docs/pkgdown.js +++ b/docs/pkgdown.js @@ -1,108 +1,162 @@ /* http://gregfranko.com/blog/jquery-best-practices/ */ -(function($) { - $(function() { +(function ($) { + $(function () { - $('.navbar-fixed-top').headroom(); + $('nav.navbar').headroom(); - $('body').css('padding-top', $('.navbar').height() + 10); - $(window).resize(function(){ - $('body').css('padding-top', $('.navbar').height() + 10); + Toc.init({ + $nav: $("#toc"), + $scope: $("main h2, main h3, main h4, main h5, main h6") }); - $('[data-toggle="tooltip"]').tooltip(); - - var cur_path = paths(location.pathname); - var links = $("#navbar ul li a"); - var max_length = -1; - var pos = -1; - for (var i = 0; i < links.length; i++) { - if (links[i].getAttribute("href") === "#") - continue; - // Ignore external links - if (links[i].host !== location.host) - continue; - - var nav_path = paths(links[i].pathname); - - var length = prefix_length(nav_path, cur_path); - if (length > max_length) { - max_length = length; - pos = i; - } + if ($('#toc').length) { + $('body').scrollspy({ + target: '#toc', + offset: $("nav.navbar").outerHeight() + 1 + }); } - // Add class to parent
  • , and enclosing
  • if in dropdown - if (pos >= 0) { - var menu_anchor = $(links[pos]); - menu_anchor.parent().addClass("active"); - menu_anchor.closest("li.dropdown").addClass("active"); - } - }); - - function paths(pathname) { - var pieces = pathname.split("/"); - pieces.shift(); // always starts with / + // Activate popovers + $('[data-bs-toggle="popover"]').popover({ + container: 'body', + html: true, + trigger: 'focus', + placement: "top", + sanitize: false, + }); - var end = pieces[pieces.length - 1]; - if (end === "index.html" || end === "") - pieces.pop(); - return(pieces); - } + $('[data-bs-toggle="tooltip"]').tooltip(); - // Returns -1 if not found - function prefix_length(needle, haystack) { - if (needle.length > haystack.length) - return(-1); + /* Clipboard --------------------------*/ - // Special case for length-0 haystack, since for loop won't run - if (haystack.length === 0) { - return(needle.length === 0 ? 0 : -1); + function changeTooltipMessage(element, msg) { + var tooltipOriginalTitle = element.getAttribute('data-bs-original-title'); + element.setAttribute('data-bs-original-title', msg); + $(element).tooltip('show'); + element.setAttribute('data-bs-original-title', tooltipOriginalTitle); } - for (var i = 0; i < haystack.length; i++) { - if (needle[i] != haystack[i]) - return(i); - } + if (ClipboardJS.isSupported()) { + $(document).ready(function () { + var copyButton = ""; - return(haystack.length); - } + $("div.sourceCode").addClass("hasCopyButton"); - /* Clipboard --------------------------*/ + // Insert copy buttons: + $(copyButton).prependTo(".hasCopyButton"); - function changeTooltipMessage(element, msg) { - var tooltipOriginalTitle=element.getAttribute('data-original-title'); - element.setAttribute('data-original-title', msg); - $(element).tooltip('show'); - element.setAttribute('data-original-title', tooltipOriginalTitle); - } + // Initialize tooltips: + $('.btn-copy-ex').tooltip({ container: 'body' }); - if(ClipboardJS.isSupported()) { - $(document).ready(function() { - var copyButton = ""; + // Initialize clipboard: + var clipboard = new ClipboardJS('[data-clipboard-copy]', { + text: function (trigger) { + return trigger.parentNode.textContent.replace(/\n#>[^\n]*/g, ""); + } + }); - $("div.sourceCode").addClass("hasCopyButton"); + clipboard.on('success', function (e) { + changeTooltipMessage(e.trigger, 'Copied!'); + e.clearSelection(); + }); - // Insert copy buttons: - $(copyButton).prependTo(".hasCopyButton"); + clipboard.on('error', function (e) { + changeTooltipMessage(e.trigger, 'Press Ctrl+C or Command+C to copy'); + }); - // Initialize tooltips: - $('.btn-copy-ex').tooltip({container: 'body'}); + }); + } - // Initialize clipboard: - var clipboardBtnCopies = new ClipboardJS('[data-clipboard-copy]', { - text: function(trigger) { - return trigger.parentNode.textContent.replace(/\n#>[^\n]*/g, ""); + /* Search marking --------------------------*/ + var url = new URL(window.location.href); + var toMark = url.searchParams.get("q"); + var mark = new Mark("main#main"); + if (toMark) { + mark.mark(toMark, { + accuracy: { + value: "complementary", + limiters: [",", ".", ":", "/"], } }); + } - clipboardBtnCopies.on('success', function(e) { - changeTooltipMessage(e.trigger, 'Copied!'); - e.clearSelection(); - }); + /* Search --------------------------*/ + /* Adapted from https://github.com/rstudio/bookdown/blob/2d692ba4b61f1e466c92e78fd712b0ab08c11d31/inst/resources/bs4_book/bs4_book.js#L25 */ + // Initialise search index on focus + var fuse; + $("#search-input").focus(async function (e) { + if (fuse) { + return; + } - clipboardBtnCopies.on('error', function() { - changeTooltipMessage(e.trigger,'Press Ctrl+C or Command+C to copy'); - }); + $(e.target).addClass("loading"); + var response = await fetch($("#search-input").data("search-index")); + var data = await response.json(); + + var options = { + keys: ["what", "text", "code"], + ignoreLocation: true, + threshold: 0.1, + includeMatches: true, + includeScore: true, + }; + fuse = new Fuse(data, options); + + $(e.target).removeClass("loading"); }); - } + + // Use algolia autocomplete + var options = { + autoselect: true, + debug: true, + hint: false, + minLength: 2, + }; + var q; + async function searchFuse(query, callback) { + await fuse; + + var items; + if (!fuse) { + items = []; + } else { + q = query; + var results = fuse.search(query, { limit: 20 }); + items = results + .filter((x) => x.score <= 0.75) + .map((x) => x.item); + if (items.length === 0) { + items = [{ dir: "Sorry 😿", previous_headings: "", title: "No results found.", what: "No results found.", path: window.location.href }]; + } + } + callback(items); + } + $("#search-input").autocomplete(options, [ + { + name: "content", + source: searchFuse, + templates: { + suggestion: (s) => { + if (s.title == s.what) { + return `${s.dir} >
    ${s.title}
    `; + } else if (s.previous_headings == "") { + return `${s.dir} >
    ${s.title}
    > ${s.what}`; + } else { + return `${s.dir} >
    ${s.title}
    > ${s.previous_headings} > ${s.what}`; + } + }, + }, + }, + ]).on('autocomplete:selected', function (event, s) { + window.location.href = s.path + "?q=" + q + "#" + s.id; + }); + }); })(window.jQuery || window.$) + +document.addEventListener('keydown', function (event) { + // Check if the pressed key is '/' + if (event.key === '/') { + event.preventDefault(); // Prevent any default action associated with the '/' key + document.getElementById('search-input').focus(); // Set focus to the search input + } +}); diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index a7c6f493..472a931e 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -1,5 +1,5 @@ -pandoc: '3.2' -pkgdown: 2.1.1 +pandoc: 3.6.3 +pkgdown: 2.2.1 pkgdown_sha: ~ articles: announcement_addin: announcement_addin.html @@ -13,4 +13,7 @@ articles: migrate_to_v0_2_0: migrate_to_v0_2_0.html new-application: new-application.html themeBuilder_addin: themeBuilder_addin.html -last_built: 2025-09-03T05:05Z +last_built: 2026-08-05T10:12Z +urls: + reference: https://aggregate-genius.github.io/periscope2/reference + article: https://aggregate-genius.github.io/periscope2/articles diff --git a/docs/reference/add_ui_body.html b/docs/reference/add_ui_body.html index 519d77f1..40e0ed7a 100644 --- a/docs/reference/add_ui_body.html +++ b/docs/reference/add_ui_body.html @@ -1,105 +1,66 @@ -Add UI elements to dashboard body section — add_ui_body • periscope2 - - -
  • +
    + + +
    +
    +
    +
    -
    +

    Builds application body with given configurations and elements. It is called within "ui_body.R". Check example application for detailed example

    -
    +
    +

    Usage

    add_ui_body(body_elements = NULL, append = FALSE)
    -
    -

    Arguments

    +
    +

    Arguments

    body_elements
    @@ -110,18 +71,18 @@

    Arguments

    Add elements to current body elements or remove previous body elements (default = FALSE)

    -
    -

    Value

    +
    +

    Value

    list of both shiny UI elements and html div tags for alert and linking app JS and CSS files

    -
    -

    Shiny Usage

    +
    +

    Shiny Usage

    Call this function from program/ui_body.R to set body parameters

    -
    -

    See also

    + -
    -

    Examples

    +
    +

    Examples

      library(shiny)
       library(bs4Dash)
     #> 
    @@ -154,23 +115,19 @@ 

    Examples

    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/add_ui_footer.html b/docs/reference/add_ui_footer.html index 8a6e188d..eb8ef8f8 100644 --- a/docs/reference/add_ui_footer.html +++ b/docs/reference/add_ui_footer.html @@ -1,105 +1,66 @@ -Add UI elements to dashboard footer section — add_ui_footer • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    Builds application footer with given configurations and elements. It is called within "ui_footer.R". Check example application for detailed example

    -
    +
    +

    Usage

    add_ui_footer(left = NULL, right = NULL, fixed = FALSE)
    -
    -

    Arguments

    +
    +

    Arguments

    left
    @@ -114,18 +75,18 @@

    Arguments

    Always show footer at page bottom regardless page scroll location (default = FALSE).

    -
    -

    Value

    +
    +

    Value

    list of both shiny UI elements and named footer properties

    -
    -

    Shiny Usage

    +
    +

    Shiny Usage

    Call this function from program/ui_footer.R to set footer parameters

    -
    -

    See also

    + -
    -

    Examples

    +
    +

    Examples

      library(shiny)
       library(bs4Dash)
     
    @@ -154,23 +115,19 @@ 

    Examples

    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/add_ui_header.html b/docs/reference/add_ui_header.html index 4b622156..ed8687a0 100644 --- a/docs/reference/add_ui_header.html +++ b/docs/reference/add_ui_header.html @@ -1,100 +1,61 @@ -Add UI elements to dashboard header section — add_ui_header • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    Builds application header with given configurations and elements. It is called within "ui_header.R". These elements will be displayed in the header beside application title and application busy indicator.

    -
    +
    +

    Usage

    add_ui_header(
       ui_elements = NULL,
       ui_position = "right",
    @@ -112,8 +73,8 @@ 

    Add UI elements to dashboard header section

    )
    -
    -

    Arguments

    +
    +

    Arguments

    ui_elements
    @@ -174,12 +135,12 @@

    Arguments

    Sidebar status. Check ?bs4Dash::bs4DashNavbar() for list of valid values

    -
    -

    Value

    +
    +

    Value

    list of both shiny UI elements and named header properties

    -
    -

    Details

    +
    +

    Details

    Application header consists of three elements:

    @@ -203,14 +164,14 @@

    Application header consi
    Check example application for detailed example

    -
    -

    Shiny Usage

    +
    +

    Shiny Usage

    Call this function from program/ui_header.R to set header parameters

    -
    -

    See also

    + -
    -

    Examples

    +
    +

    Examples

      library(shiny)
       library(bs4Dash)
     
    @@ -253,23 +214,19 @@ 

    Examples

    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/add_ui_left_sidebar.html b/docs/reference/add_ui_left_sidebar.html index 15acdb31..43569f08 100644 --- a/docs/reference/add_ui_left_sidebar.html +++ b/docs/reference/add_ui_left_sidebar.html @@ -1,100 +1,61 @@ -Add UI elements to dashboard left sidebar section — add_ui_left_sidebar • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    This function adds left sidebar configurations and UI elements. It is called within "ui_left_sidebar.R". Check example application for detailed example

    -
    +
    +

    Usage

    add_ui_left_sidebar(
       sidebar_elements = NULL,
       sidebar_menu = NULL,
    @@ -109,8 +70,8 @@ 

    Add UI elements to dashboard left sidebar section

    )
    -
    -

    Arguments

    +
    +

    Arguments

    sidebar_elements
    @@ -154,18 +115,18 @@

    Arguments

    Sidebar skin. "dark" or "light" (default = "light")

    -
    -

    Value

    +
    +

    Value

    list of both shiny UI elements and named left sidebar properties

    -
    -

    Shiny Usage

    +
    +

    Shiny Usage

    Call this function from program/ui_left_sidebar.R to set left sidebar parameters

    -
    -

    See also

    + -
    -

    Examples

    +
    +

    Examples

      library(shiny)
       library(bs4Dash)
       # Inside ui_left_sidebar.R
    @@ -191,23 +152,19 @@ 

    Examples

    sidebar_menu = sidebar_menu)
    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/add_ui_right_sidebar.html b/docs/reference/add_ui_right_sidebar.html index 6b3fa7d3..b0961199 100644 --- a/docs/reference/add_ui_right_sidebar.html +++ b/docs/reference/add_ui_right_sidebar.html @@ -1,100 +1,61 @@ -Add UI elements to dashboard right sidebar section — add_ui_right_sidebar • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    Builds application right sidebar with given configurations and elements. It is called within "ui_right_sidebar.R". Check example application for detailed example

    -
    +
    +

    Usage

    add_ui_right_sidebar(
       sidebar_elements = NULL,
       sidebar_menu = NULL,
    @@ -105,8 +66,8 @@ 

    Add UI elements to dashboard right sidebar section

    )
    -
    -

    Arguments

    +
    +

    Arguments

    sidebar_elements
    @@ -133,18 +94,18 @@

    Arguments

    Sidebar skin. "dark" or "light" (default = "light")

    -
    -

    Value

    +
    +

    Value

    list of both shiny UI elements and named right sidebar properties

    -
    -

    Shiny Usage

    +
    +

    Shiny Usage

    Call this function from program/ui_right_sidebar.R to set right sidebar parameters

    -
    -

    See also

    + -
    -

    Examples

    +
    +

    Examples

      library(shiny)
       library(bs4Dash)
     
    @@ -170,23 +131,19 @@ 

    Examples

    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/announcementConfigurationsAddin.html b/docs/reference/announcementConfigurationsAddin.html index 22b26d60..75a01f4c 100644 --- a/docs/reference/announcementConfigurationsAddin.html +++ b/docs/reference/announcementConfigurationsAddin.html @@ -1,141 +1,98 @@ -Build Announcement Module Configuration YAML File — announcementConfigurationsAddin • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    Call this as an addin to build valid yaml file that is needed for running announcements module. The generated file can be used in periscope2 app using load_announcements.

    -
    +
    +

    Usage

    announcementConfigurationsAddin()
    -
    -

    Value

    +
    +

    Value

    launch gadget window

    -
    -

    Details

    +
    +

    Details

    The method can be called directly via R console or via RStudio addins menu

    -
    -

    See also

    + -
    -

    Examples

    +
    +

    Examples

    if (interactive()) {
        periscope2:::announcementConfigurationsAddin()
     }
     
     
    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/appReset.html b/docs/reference/appReset.html index 51fabd09..7d3b4788 100644 --- a/docs/reference/appReset.html +++ b/docs/reference/appReset.html @@ -1,107 +1,69 @@ -appReset module server function — appReset • periscope2appReset module server function — appReset • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    Server-side function for the appResetButton This is a custom high-functionality button for session reload. The server function is used to provide module configurations.

    -
    +
    +

    Usage

    appReset(id, reset_wait = 5000, alert_location = "bodyAlert", logger)
    -
    -

    Arguments

    +
    +

    Arguments

    id
    @@ -120,20 +82,20 @@

    Arguments

    logger to use

    -
    -

    Value

    +
    +

    Value

    nothing, function will display a warning message in the app then reload the whole application

    -
    -

    Shiny Usage

    +
    +

    Shiny Usage

    This function is not called directly by consumers - it is accessed in server_local.R (or similar file) using the same id provided in appResetButton:

    appReset(id = "appResetId", logger = ss_userAction.Log)

    -
    -

    See also

    +
    +

    See also

    -
    -

    Examples

    +
    +

    Examples

    if (interactive()) {
        library(shiny)
        library(periscope2)
    @@ -158,23 +120,19 @@ 

    Examples

    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/appResetButton.html b/docs/reference/appResetButton.html index f865f676..5cf5b086 100644 --- a/docs/reference/appResetButton.html +++ b/docs/reference/appResetButton.html @@ -1,143 +1,106 @@ -appResetButton module UI function — appResetButton • periscope2appResetButton module UI function — appResetButton • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    Creates a toggle button to reset application session. Upon pressing on the button, its state is flipped to cancel application reload with application and console warning messages indicating that the application will be reloaded.

    -
    +
    +

    Usage

    appResetButton(id)
    -
    -

    Arguments

    +
    +

    Arguments

    id

    character id for the object

    -
    -

    Value

    +
    +

    Value

    an html div with prettyToggle button

    -
    -

    Details

    +
    +

    Details

    User can either resume reloading application session or cancel reloading process which will also generate application and console messages to indicate reloading status and result.

    -
    -

    Button Features

    +
    +

    Button Features

    • Initial state label is "Application Reset" with warning status

    • Reloading state label is "Cancel Application Reset" with danger status

    -
    -

    Shiny Usage

    +
    +

    Shiny Usage

    Call this function at any place in UI section.

    It is paired with a call to appReset(id, ...) in server

    -
    -

    See also

    +
    +

    See also

    -
    -

    Examples

    +
    +

    Examples

    if (interactive()) {
        library(shiny)
        library(periscope2)
    @@ -163,23 +126,19 @@ 

    Examples

    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/createPSAlert.html b/docs/reference/createPSAlert.html index 49afe05e..3972a7c5 100644 --- a/docs/reference/createPSAlert.html +++ b/docs/reference/createPSAlert.html @@ -1,98 +1,58 @@ -Display alert panel at specified location — createPSAlert • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    Create an alert panel in server code to be displayed in the specified UI selector location

    -
    +
    +

    Usage

    createPSAlert(
       session = shiny::getDefaultReactiveDomain(),
       id = NULL,
    @@ -101,8 +61,8 @@ 

    Display alert panel at specified location

    )
    -
    -

    Arguments

    +
    +

    Arguments

    session
    @@ -122,26 +82,26 @@

    Arguments

    List of options to pass to the alert

    -
    -

    Value

    +
    +

    Value

    html div and inserts it in the app DOM

    -
    -

    Shiny Usage

    +
    +

    Shiny Usage

    Call this function from program/server_local.R or any other server file to setup the needed alert

    -
    -

    See also

    + -
    -

    Examples

    +
    +

    Examples

      library(shiny)
       library(bs4Dash)
     
    @@ -163,23 +123,19 @@ 

    Examples

    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/create_application.html b/docs/reference/create_application.html index 24b8e69e..f0bd9103 100644 --- a/docs/reference/create_application.html +++ b/docs/reference/create_application.html @@ -1,102 +1,64 @@ -Create a new templated framework application — create_application • periscope2Create a new templated framework application — create_application • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    Creates ready-to-use templated application files using the periscope2 framework. The application can be created either empty (default) or with a sample/documented example application.

    -
    +
    +

    Usage

    create_application(
       name,
       location,
    @@ -106,8 +68,8 @@ 

    Create a new templated framework application

    )
    -
    -

    Arguments

    +
    +

    Arguments

    name
    @@ -130,12 +92,12 @@

    Arguments

    parameter to set the right sidebar. It can be TRUE/FALSE

    -
    -

    Value

    +
    +

    Value

    no return value, creates application folder structure and files

    -
    -

    Name

    +
    +

    Name

    The name directory must not exist in location. If the code @@ -143,8 +105,8 @@

    Name

    a warning and will not create an application template.

    Use only filesystem-compatible characters in the name (ideally w/o spaces)

    -
    -

    Directory Structure

    +
    +

    Directory Structure

    @@ -163,8 +125,8 @@

    Directory Structure

    -
    -

    File Information

    +
    +

    File Information

    @@ -210,57 +172,53 @@

    File Information

    -
    -

    See also

    + -
    -

    Examples

    +
    +

    Examples

    # sample app named 'mytestapp' created in a temp dir
     location <- tempdir()
     create_application(name = 'mytestapp', location = location, sample_app = TRUE)
    -#> periscope2 application mytestapp was created successfully at /tmp/Rtmpk1yVfS
    +#> periscope2 application mytestapp was created successfully at /tmp/RtmpSnL8Yy
     unlink(paste0(location,'/mytestapp'), TRUE)
     
     # sample app named 'mytestapp' with a right sidebar using a custom icon created in a temp dir
     location <- tempdir()
     create_application(name = 'mytestapp', location = location, sample_app = TRUE, right_sidebar = TRUE)
    -#> periscope2 application mytestapp was created successfully at /tmp/Rtmpk1yVfS
    +#> periscope2 application mytestapp was created successfully at /tmp/RtmpSnL8Yy
     unlink(paste0(location,'/mytestapp'), TRUE)
     
     # blank app named 'myblankapp' created in a temp dir
     location <- tempdir()
     create_application(name = 'myblankapp', location = location)
    -#> periscope2 application myblankapp was created successfully at /tmp/Rtmpk1yVfS
    +#> periscope2 application myblankapp was created successfully at /tmp/RtmpSnL8Yy
     unlink(paste0(location,'/myblankapp'), TRUE)
     
     # blank app named 'myblankapp' without a left sidebar created in a temp dir
     location <- tempdir()
     create_application(name = 'myblankapp', location = location, left_sidebar = FALSE)
    -#> periscope2 application myblankapp was created successfully at /tmp/Rtmpk1yVfS
    +#> periscope2 application myblankapp was created successfully at /tmp/RtmpSnL8Yy
     unlink(paste0(location,'/myblankapp'), TRUE)
     
     
    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/create_left_sidebar.html b/docs/reference/create_left_sidebar.html index dd2aaa19..b6cd7cbb 100644 --- a/docs/reference/create_left_sidebar.html +++ b/docs/reference/create_left_sidebar.html @@ -1,115 +1,75 @@ -Add the left sidebar to an existing application — create_left_sidebar • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    User can update an existing application that does not have a left side bar and add a new empty one using this function.

    -
    +
    +

    Usage

    create_left_sidebar(location)
    -
    -

    Arguments

    +
    +

    Arguments

    location

    path of the existing periscope2 application

    -
    -

    Value

    +
    +

    Value

    no return value, creates left sidebar related UI R file and updates related source call in ui.R

    -
    -

    Details

    +
    +

    Details

    If conversion is successful, the following message will be returned "Add left sidebar conversion was successful. File(s) updated: ui.R, ui_left_sidebar.R"

    If the function called on an application with an existing left bar, message @@ -118,28 +78,24 @@

    Details

    If the passed location is invalid, empty, not exist or not a valid periscope2 application, nothing will be added and a related error message will be printed in console

    -
    -

    See also

    + -
    - -
    +
    -
    - +
    diff --git a/docs/reference/create_right_sidebar.html b/docs/reference/create_right_sidebar.html index be481434..0692340b 100644 --- a/docs/reference/create_right_sidebar.html +++ b/docs/reference/create_right_sidebar.html @@ -1,115 +1,75 @@ -Add the right sidebar to an existing application — create_right_sidebar • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    User can update an existing application that does not have a right side bar and add a new empty one using this function.

    -
    +
    +

    Usage

    create_right_sidebar(location)
    -
    -

    Arguments

    +
    +

    Arguments

    location

    path of the existing periscope2 application.

    -
    -

    Value

    +
    +

    Value

    no return value, creates right sidebar related UI R file and updates related source call in ui.R

    -
    -

    Details

    +
    +

    Details

    If conversion is successful, the following message will be returned "Add right sidebar conversion was successful. File(s) updated: ui.R, ui_right_sidebar.R"

    If the function called on an application with an existing right bar, message @@ -118,28 +78,24 @@

    Details

    If the passed location is invalid, empty, not exist or not a valid periscope2 application, nothing will be added and a related error message will be printed in console

    -
    -

    See also

    +
    +

    See also

    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/downloadFile.html b/docs/reference/downloadFile.html index 9331c5ae..215fa11d 100644 --- a/docs/reference/downloadFile.html +++ b/docs/reference/downloadFile.html @@ -1,102 +1,64 @@ -downloadFile module server function — downloadFile • periscope2downloadFile module server function — downloadFile • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    Server-side function for the downloadFileButton. This is a custom high-functionality button for file downloads supporting single or multiple download types. The server function is used to provide the data for download.

    -
    +
    +

    Usage

    downloadFile(
       id,
       logger = NULL,
    @@ -107,8 +69,8 @@ 

    downloadFile module server function

    )
    -
    -

    Arguments

    +
    +

    Arguments

    id
    @@ -142,20 +104,20 @@

    Arguments

    for tabular data. Where not applicable for a download type it is ignored.

    -
    -

    Value

    +
    +

    Value

    no return value, called for downloading selected file type

    -
    -

    Shiny Usage

    +
    +

    Shiny Usage

    This function is not called directly by consumers - it is accessed in server.R using the same id provided in downloadFileButton:

    downloadFile(id, logger, filenameroot, datafxns)

    -
    -

    See also

    + -
    -

    Examples

    +
    +

    Examples

    if (interactive()) {
        library(shiny)
        library(periscope2)
    @@ -198,23 +160,19 @@ 

    Examples

    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/downloadFileButton.html b/docs/reference/downloadFileButton.html index 5d992a64..13e68cd6 100644 --- a/docs/reference/downloadFileButton.html +++ b/docs/reference/downloadFileButton.html @@ -1,109 +1,72 @@ -downloadFileButton module UI function — downloadFileButton • periscope2downloadFileButton module UI function — downloadFileButton • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    Creates a custom high-functionality button for file downloads with two states - single download type or multiple-download types. The button image and pop-up menu (if needed) are set accordingly. A tooltip can also be set for the button.

    -
    +
    +

    Usage

    downloadFileButton(id, downloadtypes = c("csv"), hovertext = NULL)
    -
    -

    Arguments

    +
    +

    Arguments

    id
    @@ -118,35 +81,35 @@

    Arguments

    tooltip hover text

    -
    -

    Value

    +
    +

    Value

    html span with tooltip and either shiny downloadButton in case of single download or shiny actionButton otherwise

    -
    -

    Button Features

    +
    +

    Button Features

    • Consistent styling of the button, including a hover tooltip

    • Single or multiple types of downloads

    • Ability to download different data for each type of download

    -
    -

    Example

    +
    +

    Example

    downloadFileUI("mybuttonID1", c("csv", "tsv"), "Click Here") downloadFileUI("mybuttonID2", "csv", "Click to download")

    -
    -

    Shiny Usage

    +
    +

    Shiny Usage

    Call this function at the place in ui.R where the button should be placed.

    It is paired with a call to downloadFile(id, ...) in server.R

    -
    -

    See also

    + -
    -

    Examples

    +
    +

    Examples

    if (interactive()) {
        library(shiny)
        library(periscope2)
    @@ -189,23 +152,19 @@ 

    Examples

    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/downloadFile_AvailableTypes.html b/docs/reference/downloadFile_AvailableTypes.html index 573a1a83..6440978e 100644 --- a/docs/reference/downloadFile_AvailableTypes.html +++ b/docs/reference/downloadFile_AvailableTypes.html @@ -1,128 +1,84 @@ -downloadFile module list of allowed file types — downloadFile_AvailableTypes • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    Returns a list of all supported types

    -
    +
    +

    Usage

    downloadFile_AvailableTypes()
    -
    -

    Value

    +
    +

    Value

    a vector of all supported types

    -
    -

    See also

    + -
    - -
    +
    -
    - +
    diff --git a/docs/reference/downloadFile_ValidateTypes.html b/docs/reference/downloadFile_ValidateTypes.html index 76c1733c..42619508 100644 --- a/docs/reference/downloadFile_ValidateTypes.html +++ b/docs/reference/downloadFile_ValidateTypes.html @@ -1,115 +1,75 @@ -Check passed file types against downloadFile module allowed file types list — downloadFile_ValidateTypes • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    It is a downloadFile module helper to return periscope2 defined file types list and warns user if an invalid type is included

    -
    +
    +

    Usage

    downloadFile_ValidateTypes(types)
    -
    -

    Arguments

    +
    +

    Arguments

    types

    list of types to test

    -
    -

    Value

    +
    +

    Value

    the list input given in types

    -
    -

    See also

    +
    +

    See also

    -
    -

    Examples

    +
    +

    Examples

      #inside console
       ## Check valid types
       result <- periscope2::downloadFile_AvailableTypes()
    @@ -133,23 +93,19 @@ 

    Examples

    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/downloadablePlot.html b/docs/reference/downloadablePlot.html index 891ef861..b6101369 100644 --- a/docs/reference/downloadablePlot.html +++ b/docs/reference/downloadablePlot.html @@ -1,100 +1,61 @@ -downloadablePlot module server function — downloadablePlot • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    Server-side function for the downloadablePlotUI. This is a custom plot output paired with a linked downloadFile button.

    -
    +
    +

    Usage

    downloadablePlot(
       id,
       logger = NULL,
    @@ -105,8 +66,8 @@ 

    downloadablePlot module server function

    )
    -
    -

    Arguments

    +
    +

    Arguments

    id
    @@ -140,24 +101,24 @@

    Arguments

    display as a return value. This function should require no input parameters.

    -
    -

    Value

    +
    +

    Value

    Reactive expression containing the currently selected plot to be available for display and download

    -
    -

    Details

    +
    +

    Details

    downloadFile button will be hidden if downloadablePlot parameter downloadfxns or downloadablePlotUI parameter downloadtypes is empty

    -
    -

    Notes

    +
    +

    Notes

    When there are no values to download in any of the linked downloadfxns the button will be hidden as there is nothing to download.

    -
    -

    Shiny Usage

    +
    +

    Shiny Usage

    This function is not called directly by consumers - it is accessed in @@ -165,8 +126,8 @@

    Shiny Usage

    downloadablePlot(id, logger, filenameroot, downloadfxns, visibleplot)

    -
    -

    See also

    +
    +

    See also

    -
    -

    Examples

    +
    +

    Examples

    if (interactive()) {
       library(shiny)
       library(ggplot2)
    @@ -211,23 +172,19 @@ 

    Examples

    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/downloadablePlotUI.html b/docs/reference/downloadablePlotUI.html index 854ebf12..1fa66917 100644 --- a/docs/reference/downloadablePlotUI.html +++ b/docs/reference/downloadablePlotUI.html @@ -1,102 +1,64 @@ -downloadablePlot module UI function — downloadablePlotUI • periscope2downloadablePlot module UI function — downloadablePlotUI • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    Creates a custom plot output that is paired with a linked downloadFile button. This module is compatible with ggplot2, grob and lattice produced graphics.

    -
    +
    +

    Usage

    downloadablePlotUI(
       id,
       downloadtypes = c("png"),
    @@ -112,8 +74,8 @@ 

    downloadablePlot module UI function

    )
    -
    -

    Arguments

    +
    +

    Arguments

    id
    @@ -163,24 +125,24 @@

    Arguments

    NULL or an object created by the brushOpts function

    -
    -

    Value

    +
    +

    Value

    list of downloadFileButton UI and plot object

    -
    -

    Details

    +
    +

    Details

    downloadFile button will be hidden if downloadablePlot parameter downloadfxns or downloadablePlotUI parameter downloadtypes is empty

    -
    -

    Example

    +
    +

    Example

    downloadablePlotUI("myplotID", c("png", "csv"), "Download Plot or Data", "300px")

    -
    -

    Notes

    +
    +

    Notes

    When there is nothing to download in any of the linked downloadfxns the @@ -189,16 +151,16 @@

    Notes

    basic plot, etc.) because they cannot be saved into an object and are directly output by the system at the time of creation.

    -
    -

    Shiny Usage

    +
    +

    Shiny Usage

    Call this function at the place in ui.R where the plot should be placed.

    Paired with a call to downloadablePlot(id, ...) in server.R

    -
    -

    See also

    +
    +

    See also

    -
    -

    Examples

    +
    +

    Examples

    if (interactive()) {
       library(shiny)
       library(ggplot2)
    @@ -247,23 +209,19 @@ 

    Examples

    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/downloadableReactTable.html b/docs/reference/downloadableReactTable.html index 0d207525..447b032f 100644 --- a/docs/reference/downloadableReactTable.html +++ b/docs/reference/downloadableReactTable.html @@ -1,98 +1,58 @@ -downloadableReactTable module server function — downloadableReactTable • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    Server-side function for the downloadableReactTableUI.

    -
    +
    +

    Usage

    downloadableReactTable(
       id,
       table_data,
    @@ -112,8 +72,8 @@ 

    downloadableReactTable module server function

    )
    -
    -

    Arguments

    +
    +

    Arguments

    id
    @@ -186,23 +146,23 @@

    Arguments

    logger to use (default = NULL)

    -
    -

    Value

    +
    +

    Value

    A named list of two elements:

    • selected_rows: data.frame of current selected rows

    • table_state: a list of the current table state. The list keys are ("page", "pageSize", "pages", "sorted" and "selected"). Review ?reactable::getReactableState for more info.

    -
    -

    Shiny Usage

    +
    +

    Shiny Usage

    This function is not called directly by consumers - it is accessed in server.R using the same id provided in downloadableReactTableUI:

    downloadableReactTable(id)

    -
    -

    See also

    + -
    -

    Examples

    +
    +

    Examples

    if (interactive()) {
      library(shiny)
      library(periscope2)
    @@ -250,23 +210,19 @@ 

    Examples

    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/downloadableReactTableUI.html b/docs/reference/downloadableReactTableUI.html index 8edd37f2..ea2ac622 100644 --- a/docs/reference/downloadableReactTableUI.html +++ b/docs/reference/downloadableReactTableUI.html @@ -1,109 +1,72 @@ -downloadableReactTable module UI function — downloadableReactTableUI • periscope2downloadableReactTable module UI function — downloadableReactTableUI • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    downloadableReactTable module is extending ?reactable package table functions by creating a custom high-functionality table paired with downloadFile button. The table has the following default functionality:search, highlight functionality, infinite scrolling, sorting by columns and returns a reactive dataset of selected items and table current state.

    -
    +
    +

    Usage

    downloadableReactTableUI(id, downloadtypes = NULL, hovertext = NULL)
    -
    -

    Arguments

    +
    +

    Arguments

    id
    @@ -118,17 +81,17 @@

    Arguments

    download button tooltip hover text

    -
    -

    Value

    +
    +

    Value

    list of downloadFileButton UI and reactable table and hidden inputs for contentHeight option

    -
    -

    Details

    +
    +

    Details

    downloadFile button will be hidden if downloadableReactTableUI parameter downloadtypes is empty

    -
    -

    Table Features

    +
    +

    Table Features

    • Consistent styling of the table

    • @@ -140,30 +103,30 @@

      Table Features

    • Multi-select built in, including reactive feedback on which table items are selected

    -
    -

    Example

    +
    +

    Example

    downloadableReactTableUI("mytableID", c("csv", "tsv"), "Click Here")

    -
    -

    Notes

    +
    +

    Notes

    When there are no rows to download in any of the linked downloaddatafxns the button will be hidden as there is nothing to download.

    -
    -

    Shiny Usage

    +
    +

    Shiny Usage

    Call this function at the place in ui.R where the table should be placed.

    Paired with a call to downloadableReactTable(id, ...) in server.R

    -
    -

    See also

    +
    +

    See also

    -
    -

    Examples

    +
    +

    Examples

    if (interactive()) {
      library(shiny)
      library(periscope2)
    @@ -212,23 +175,19 @@ 

    Examples

    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/downloadableTable.html b/docs/reference/downloadableTable.html index 50fee61b..50d10407 100644 --- a/docs/reference/downloadableTable.html +++ b/docs/reference/downloadableTable.html @@ -1,102 +1,64 @@ -downloadableTable module server function — downloadableTable • periscope2downloadableTable module server function — downloadableTable • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    Server-side function for the downloadableTableUI. This is a custom high-functionality table paired with a linked downloadFile button.

    -
    +
    +

    Usage

    downloadableTable(
       id,
       logger = NULL,
    @@ -108,8 +70,8 @@ 

    downloadableTable module server function

    )
    -
    -

    Arguments

    +
    +

    Arguments

    id
    @@ -147,13 +109,13 @@

    Arguments

    Also see example below to see how to pass options

    -
    -

    Value

    +
    +

    Value

    Reactive expression containing the currently selected rows in the display table

    -
    -

    Details

    +
    +

    Details

    downloadFile button will be hidden if downloadableTable parameter downloaddatafxn or downloadableTableUI parameter downloadtypes is empty

    Generated table can highly customized using function ?DT::datatable same arguments @@ -165,8 +127,8 @@

    Details

    Also, user can apply the same provided ?DT::formatCurrency columns formats on passed dataset using format functions names as keys and their options as a list.

    -
    -

    Notes

    +
    +

    Notes

    • When there are no rows to download in any of the linked downloaddatafxns @@ -175,8 +137,8 @@

      Notes

      See parameters usage section.

    • DT::datatable options editable, width and height are not supported

    -
    -

    Shiny Usage

    +
    +

    Shiny Usage

    This function is not called directly by consumers - it is accessed in @@ -186,8 +148,8 @@

    Shiny Usage

    Note: calling module server returns the reactive expression containing the currently selected rows in the display table.

    -
    -

    See also

    + -
    -

    Examples

    +
    +

    Examples

    if (interactive()) {
      library(shiny)
      library(periscope2)
    @@ -228,23 +190,19 @@ 

    Examples

    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/downloadableTableUI.html b/docs/reference/downloadableTableUI.html index 969af537..f033baf3 100644 --- a/docs/reference/downloadableTableUI.html +++ b/docs/reference/downloadableTableUI.html @@ -1,102 +1,64 @@ -downloadableTable module UI function — downloadableTableUI • periscope2downloadableTable module UI function — downloadableTableUI • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    Creates a custom high-functionality table paired with a linked downloadFile button. The table has search and highlight functionality, infinite scrolling, sorting by columns and returns a reactive dataset of selected items.

    -
    +
    +

    Usage

    downloadableTableUI(
       id,
       downloadtypes = NULL,
    @@ -106,8 +68,8 @@ 

    downloadableTable module UI function

    )
    -
    -

    Arguments

    +
    +

    Arguments

    id
    @@ -131,17 +93,17 @@

    Arguments

    selected at a time (FALSE by default allows multi-select).

    -
    -

    Value

    +
    +

    Value

    list of downloadFileButton UI and DT datatable

    -
    -

    Details

    +
    +

    Details

    downloadFile button will be hidden if downloadableTable parameter downloaddatafxn or downloadableTableUI parameter downloadtypes is empty

    -
    -

    Table Features

    +
    +

    Table Features

    • Consistent styling of the table

    • @@ -153,30 +115,30 @@

      Table Features

    • Multi-select built in, including reactive feedback on which table items are selected

    -
    -

    Example

    +
    +

    Example

    downloadableTableUI("mytableID", c("csv", "tsv"), "Click Here", "300px")

    -
    -

    Notes

    +
    +

    Notes

    When there are no rows to download in any of the linked downloaddatafxns the button will be hidden as there is nothing to download.

    -
    -

    Shiny Usage

    +
    +

    Shiny Usage

    Call this function at the place in ui.R where the table should be placed.

    Paired with a call to downloadableTable(id, ...) in server.R

    -
    -

    See also

    +
    +

    See also

    -
    -

    Examples

    +
    +

    Examples

    if (interactive()) {
      library(shiny)
      library(periscope2)
    @@ -217,23 +179,19 @@ 

    Examples

    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/get_url_parameters.html b/docs/reference/get_url_parameters.html index 9bde4aa2..f4f131e8 100644 --- a/docs/reference/get_url_parameters.html +++ b/docs/reference/get_url_parameters.html @@ -1,124 +1,85 @@ -Parse application passed URL parameters — get_url_parameters • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    This function returns any url parameters passed to the application as a named list. Keep in mind url parameters are always user-session scoped

    -
    +
    +

    Usage

    get_url_parameters(session)
    -
    -

    Arguments

    +
    +

    Arguments

    session

    shiny session object

    -
    -

    Value

    +
    +

    Value

    named list of url parameters and values. List may be empty if no URL parameters were passed when the application instance was launched

    -
    -

    Shiny Usage

    +
    +

    Shiny Usage

    Call this function from program/server_local.R or any other server file

    -
    -

    See also

    + -
    -

    Examples

    +
    +

    Examples

      library(shiny)
       library(periscope2)
     
    @@ -147,23 +108,19 @@ 

    Examples

    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/index.html b/docs/reference/index.html index 2a7aacab..880620b7 100644 --- a/docs/reference/index.html +++ b/docs/reference/index.html @@ -1,233 +1,282 @@ -Package index • periscope2 - - -
    -
    +
    +
    +
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -

    All functions

    -

    -
    -

    add_ui_body()

    -

    Add UI elements to dashboard body section

    -

    add_ui_footer()

    -

    Add UI elements to dashboard footer section

    -

    add_ui_header()

    -

    Add UI elements to dashboard header section

    -

    add_ui_left_sidebar()

    -

    Add UI elements to dashboard left sidebar section

    -

    add_ui_right_sidebar()

    -

    Add UI elements to dashboard right sidebar section

    -

    announcementConfigurationsAddin()

    -

    Build Announcement Module Configuration YAML File

    -

    appReset()

    -

    appReset module server function

    -

    appResetButton()

    -

    appResetButton module UI function

    -

    createPSAlert()

    -

    Display alert panel at specified location

    -

    create_application()

    -

    Create a new templated framework application

    -

    create_left_sidebar()

    -

    Add the left sidebar to an existing application

    -

    create_right_sidebar()

    -

    Add the right sidebar to an existing application

    -

    downloadFile()

    -

    downloadFile module server function

    -

    downloadFileButton()

    -

    downloadFileButton module UI function

    -

    downloadFile_AvailableTypes()

    -

    downloadFile module list of allowed file types

    -

    downloadFile_ValidateTypes()

    -

    Check passed file types against downloadFile module allowed file types list

    -

    downloadablePlot()

    -

    downloadablePlot module server function

    -

    downloadablePlotUI()

    -

    downloadablePlot module UI function

    -

    downloadableReactTable()

    -

    downloadableReactTable module server function

    -

    downloadableReactTableUI()

    -

    downloadableReactTable module UI function

    -

    downloadableTable()

    -

    downloadableTable module server function

    -

    downloadableTableUI()

    -

    downloadableTable module UI function

    -

    get_url_parameters()

    -

    Parse application passed URL parameters

    -

    load_announcements()

    -

    load_announcements

    -

    logViewerOutput()

    -

    Display app logs

    -

    logdebug() loginfo() logwarn() logerror()

    -

    Entry points for logging actions

    -

    periscope2-package periscope2

    -

    Periscope2 Shiny Application Framework

    -

    set_app_parameters()

    -

    Set Application Parameters

    -

    themeConfigurationsAddin()

    -

    Build application theme configuration YAML file

    -

    ui_tooltip()

    -

    Add tooltip icon and text to UI elements labels

    - - -
    +
    +

    All functions

    -
    + + +
    -
    + +
    diff --git a/docs/reference/load_announcements.html b/docs/reference/load_announcements.html index 54bd7551..648a7f2f 100644 --- a/docs/reference/load_announcements.html +++ b/docs/reference/load_announcements.html @@ -1,108 +1,69 @@ -load_announcements — load_announcements • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    Reads and parses application announcements configurations in config/announce.yaml, then display announcements in application header.

    -
    +
    +

    Usage

    load_announcements(
       announcements_file_path = NULL,
       announcement_location_id = "announceAlert"
     )
    -
    -

    Arguments

    +
    +

    Arguments

    announcements_file_path
    @@ -114,44 +75,40 @@

    Arguments

    Announcement target location div id (default = "announceAlert")

    -
    -

    Value

    +
    +

    Value

    number of seconds an announcement should be staying in caller application

    -
    -

    Details

    +
    +

    Details

    If announce.yaml does not exist or contains invalid configurations. Nothing will be displayed. Closing announcements is caller application responsibility

    -
    -

    See also

    + -
    -

    Examples

    +
    +

    Examples

        load_announcements(system.file("fw_templ/announce.yaml", package = "periscope2"))
     #> [1] 30000
     
     
    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/logViewerOutput.html b/docs/reference/logViewerOutput.html index b006d291..c4938107 100644 --- a/docs/reference/logViewerOutput.html +++ b/docs/reference/logViewerOutput.html @@ -1,139 +1,101 @@ -Display app logs — logViewerOutput • periscope2Display app logs — logViewerOutput • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    Display app log data in downloadableReactTable table containing logged user actions. Table contents are auto updated whenever a user action is logged. User can search for logs, sort them by time and download them in CSV or TSV format. The id must match the same id configured in server.R file upon calling fw_server_setup method

    -
    +
    +

    Usage

    logViewerOutput(id = "logViewer")
    -
    -

    Arguments

    +
    +

    Arguments

    id

    character id for the object(default = "logViewer")

    -
    -

    Value

    +
    +

    Value

    downloadableReactTableUI instance

    -
    -

    Table columns

    +
    +

    Table columns

    • action - the action that id logged in any place in app

    • time - action time

    -
    -

    Example

    +
    +

    Example

    logViewerOutput('logViewer')

    -
    -

    Shiny Usage

    +
    +

    Shiny Usage

    Add the log viewer box to your box list

    It is paired with a call to fw_server_setup method in server.R file

    -
    -

    See also

    + -
    -

    Examples

    +
    +

    Examples

    # Inside ui_body add the log viewer box to your box list
     
     logViewerOutput('logViewerId')
    @@ -174,23 +136,19 @@ 

    Examples

    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/logging-entrypoints.html b/docs/reference/logging-entrypoints.html index 111425c9..8688266e 100644 --- a/docs/reference/logging-entrypoints.html +++ b/docs/reference/logging-entrypoints.html @@ -1,98 +1,58 @@ -Entry points for logging actions — logging-entrypoints • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    Generate a log record and pass it to the logging system.

    -
    +
    +

    Usage

    logdebug(msg, ..., logger = "")
     
     loginfo(msg, ..., logger = "")
    @@ -102,8 +62,8 @@ 

    Entry points for logging actions

    logerror(msg, ..., logger = "")
    -
    -

    Arguments

    +
    +

    Arguments

    msg
    @@ -120,34 +80,30 @@

    Arguments

    the name of the logger to which we pass the record

    -
    -

    Value

    +
    +

    Value

    no return value, prints log contents into R console and app log file

    -
    -

    Details

    +
    +

    Details

    A log record gets timestamped and will be independently formatted by each of the handlers handling it.

    Leading and trailing whitespace is stripped from the final message.

    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/periscope2.html b/docs/reference/periscope2.html index ba853cc6..71cfaedc 100644 --- a/docs/reference/periscope2.html +++ b/docs/reference/periscope2.html @@ -1,98 +1,61 @@ -Periscope2 Shiny Application Framework — periscope2 • periscope2Periscope2 Shiny Application Framework — periscope2 • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    Periscope2 is the next-generation package following the paradigm of the 'periscope' package to support a UI-standardized and rail-guarded enterprise quality application environment. This package also includes a variety of convenience functions for 'shiny' applications in a more modernized way. Base @@ -101,8 +64,8 @@

    Periscope2 Shiny Application Framework

    -
    -

    Details

    +
    +

    Details

    'periscope2' differs from the 'periscope' package as follows:

    • Upgraded dependency on bootstrap v4 instead of bootstrap v3

    • New user modules (i.e. announcements)

    • More functionality and finer control over existing modules such as @@ -111,8 +74,8 @@

      Details

    • Enhanced file structure to organize application UI, shiny modules, app configuration, .. etc

    A gallery of 'periscope' and 'periscope2' example apps is hosted at http://periscopeapps.org

    -
    -

    Function Overview

    +
    +

    Function Overview

    @@ -131,43 +94,39 @@

    Function Overview

    High-functionality standardized tooltips:
    ui_tooltip

    -
    -

    More Information

    +
    +

    More Information

    browseVignettes(package = 'periscope2')

    -
    -

    See also

    + -
    -

    Author

    +
    +

    Author

    Maintainer: Mohammed Ali mohammed@aggregate-genius.com

    Other contributors:

    • Constance Brett [contributor]

    • Aggregate Genius Inc [sponsor]

    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/set_app_parameters.html b/docs/reference/set_app_parameters.html index 17cac068..3f974798 100644 --- a/docs/reference/set_app_parameters.html +++ b/docs/reference/set_app_parameters.html @@ -1,98 +1,58 @@ -Set Application Parameters — set_app_parameters • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    This function sets global parameters customizing the shiny application.

    -
    +
    +

    Usage

    set_app_parameters(
       title = NULL,
       app_info = NULL,
    @@ -103,8 +63,8 @@ 

    Set Application Parameters

    )
    -
    -

    Arguments

    +
    +

    Arguments

    title
    @@ -142,19 +102,19 @@

    Arguments

    [Deprecated]. Use load_announcements to configure announcement.

    -
    -

    Value

    +
    +

    Value

    no return value, called for setting new application global properties

    -
    -

    Shiny Usage

    +
    +

    Shiny Usage

    Call this function from program/global.R to set the application parameters.

    -
    -

    See also

    + -
    -

    Examples

    +
    +

    Examples

      library(shiny)
       library(waiter)
       library(periscope2)
    @@ -182,23 +142,19 @@ 

    Examples

    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/themeConfigurationsAddin.html b/docs/reference/themeConfigurationsAddin.html index 431f5dfb..5ca77782 100644 --- a/docs/reference/themeConfigurationsAddin.html +++ b/docs/reference/themeConfigurationsAddin.html @@ -1,141 +1,98 @@ -Build application theme configuration YAML file — themeConfigurationsAddin • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    Call this as an addin to build valid yaml file that is needed for creating application periscope_style.yaml file. The generated file can be used in periscope2 app by putting it inside generated app www folder.

    -
    +
    +

    Usage

    themeConfigurationsAddin()
    -
    -

    Value

    +
    +

    Value

    launch gadget window

    -
    -

    Details

    +
    +

    Details

    The method can be called directly via R console or via RStudio addins menu

    -
    -

    See also

    + -
    -

    Examples

    +
    +

    Examples

    if (interactive()) {
        periscope2:::themeConfigurationsAddin()
     }
     
     
    -
    - -
    +
    -
    - +
    diff --git a/docs/reference/ui_tooltip.html b/docs/reference/ui_tooltip.html index f5f933c1..e675366e 100644 --- a/docs/reference/ui_tooltip.html +++ b/docs/reference/ui_tooltip.html @@ -1,105 +1,66 @@ -Add tooltip icon and text to UI elements labels — ui_tooltip • periscope2 - - -
    -
    -
    - + + +
    +
    +
    +
    -
    +

    This function inserts a standardized tooltip image, label (optional), and hovertext into the application UI

    -
    +
    +

    Usage

    ui_tooltip(id, label = "", text = "", placement = "top")
    -
    -

    Arguments

    +
    +

    Arguments

    id
    @@ -118,18 +79,18 @@

    Arguments

    Where to display tooltip label. Available places are "top", "bottom", "left", "right" (default is "top")

    -
    -

    Value

    +
    +

    Value

    html span with the label, tooltip image and tooltip text

    -
    -

    Shiny Usage

    +
    +

    Shiny Usage

    Call this function from program/ui_body.R to set tooltip parameters

    -
    -

    See also

    + -
    -

    Examples

    +
    +

    Examples

      library(shiny)
       library(periscope2)
     
    @@ -156,23 +117,19 @@ 

    Examples

    -
    - -
    +
    -
    - +
    diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 19a48ff5..e750b73f 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -1,52 +1,52 @@ -/404.html -/articles/announcement-module.html -/articles/announcement_addin.html -/articles/applicationReset-module.html -/articles/downloadFile-module.html -/articles/downloadablePlot-module.html -/articles/downloadableReactTable-module.html -/articles/downloadableTable-module.html -/articles/index.html -/articles/logViewer-module.html -/articles/migrate_to_v0_2_0.html -/articles/new-application.html -/articles/themeBuilder_addin.html -/authors.html -/index.html -/news/index.html -/reference/add_ui_body.html -/reference/add_ui_footer.html -/reference/add_ui_header.html -/reference/add_ui_left_sidebar.html -/reference/add_ui_right_sidebar.html -/reference/announcementConfigurationsAddin.html -/reference/appReset.html -/reference/appResetButton.html -/reference/createPSAlert.html -/reference/create_application.html -/reference/create_application_dashboard.html -/reference/create_left_sidebar.html -/reference/create_right_sidebar.html -/reference/downloadFile.html -/reference/downloadFileButton.html -/reference/downloadFile_AvailableTypes.html -/reference/downloadFile_ValidateTypes.html -/reference/downloadablePlot.html -/reference/downloadablePlotUI.html -/reference/downloadableReactTable.html -/reference/downloadableReactTableUI.html -/reference/downloadableTable.html -/reference/downloadableTableUI.html -/reference/get_url_parameters.html -/reference/index.html -/reference/load_announcements.html -/reference/logViewer.html -/reference/logViewerOutput.html -/reference/logging-entrypoints.html -/reference/periscope2.html -/reference/set_app_parameters.html -/reference/themeConfigurationsAddin.html -/reference/ui_tooltip.html +https://aggregate-genius.github.io/periscope2/404.html +https://aggregate-genius.github.io/periscope2/articles/announcement-module.html +https://aggregate-genius.github.io/periscope2/articles/announcement_addin.html +https://aggregate-genius.github.io/periscope2/articles/applicationReset-module.html +https://aggregate-genius.github.io/periscope2/articles/downloadFile-module.html +https://aggregate-genius.github.io/periscope2/articles/downloadablePlot-module.html +https://aggregate-genius.github.io/periscope2/articles/downloadableReactTable-module.html +https://aggregate-genius.github.io/periscope2/articles/downloadableTable-module.html +https://aggregate-genius.github.io/periscope2/articles/index.html +https://aggregate-genius.github.io/periscope2/articles/logViewer-module.html +https://aggregate-genius.github.io/periscope2/articles/migrate_to_v0_2_0.html +https://aggregate-genius.github.io/periscope2/articles/new-application.html +https://aggregate-genius.github.io/periscope2/articles/themeBuilder_addin.html +https://aggregate-genius.github.io/periscope2/authors.html +https://aggregate-genius.github.io/periscope2/index.html +https://aggregate-genius.github.io/periscope2/news/index.html +https://aggregate-genius.github.io/periscope2/reference/add_ui_body.html +https://aggregate-genius.github.io/periscope2/reference/add_ui_footer.html +https://aggregate-genius.github.io/periscope2/reference/add_ui_header.html +https://aggregate-genius.github.io/periscope2/reference/add_ui_left_sidebar.html +https://aggregate-genius.github.io/periscope2/reference/add_ui_right_sidebar.html +https://aggregate-genius.github.io/periscope2/reference/announcementConfigurationsAddin.html +https://aggregate-genius.github.io/periscope2/reference/appReset.html +https://aggregate-genius.github.io/periscope2/reference/appResetButton.html +https://aggregate-genius.github.io/periscope2/reference/createPSAlert.html +https://aggregate-genius.github.io/periscope2/reference/create_application.html +https://aggregate-genius.github.io/periscope2/reference/create_application_dashboard.html +https://aggregate-genius.github.io/periscope2/reference/create_left_sidebar.html +https://aggregate-genius.github.io/periscope2/reference/create_right_sidebar.html +https://aggregate-genius.github.io/periscope2/reference/downloadFile.html +https://aggregate-genius.github.io/periscope2/reference/downloadFileButton.html +https://aggregate-genius.github.io/periscope2/reference/downloadFile_AvailableTypes.html +https://aggregate-genius.github.io/periscope2/reference/downloadFile_ValidateTypes.html +https://aggregate-genius.github.io/periscope2/reference/downloadablePlot.html +https://aggregate-genius.github.io/periscope2/reference/downloadablePlotUI.html +https://aggregate-genius.github.io/periscope2/reference/downloadableReactTable.html +https://aggregate-genius.github.io/periscope2/reference/downloadableReactTableUI.html +https://aggregate-genius.github.io/periscope2/reference/downloadableTable.html +https://aggregate-genius.github.io/periscope2/reference/downloadableTableUI.html +https://aggregate-genius.github.io/periscope2/reference/get_url_parameters.html +https://aggregate-genius.github.io/periscope2/reference/index.html +https://aggregate-genius.github.io/periscope2/reference/load_announcements.html +https://aggregate-genius.github.io/periscope2/reference/logViewer.html +https://aggregate-genius.github.io/periscope2/reference/logViewerOutput.html +https://aggregate-genius.github.io/periscope2/reference/logging-entrypoints.html +https://aggregate-genius.github.io/periscope2/reference/periscope2.html +https://aggregate-genius.github.io/periscope2/reference/set_app_parameters.html +https://aggregate-genius.github.io/periscope2/reference/themeConfigurationsAddin.html +https://aggregate-genius.github.io/periscope2/reference/ui_tooltip.html