diff --git a/.lintr b/.lintr index 0a0bb22f32..34473d2738 100644 --- a/.lintr +++ b/.lintr @@ -1,6 +1,5 @@ linters: linters_with_defaults( line_length_linter = line_length_linter(120), cyclocomp_linter = NULL, - object_usage_linter = NULL, - indentation_linter = NULL + object_usage_linter = NULL ) diff --git a/DESCRIPTION b/DESCRIPTION index cd2f63c238..6991e0732e 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -90,7 +90,6 @@ Collate: 'module_tabs_with_filters.R' 'module_teal.R' 'module_teal_with_splash.R' - 'modules_debugging.R' 'reporter_previewer_module.R' 'show_rcode_modal.R' 'tdata.R' diff --git a/NAMESPACE b/NAMESPACE index 8c78f7e555..576d92a26c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,6 +1,8 @@ # Generated by roxygen2: do not edit by hand S3method(c,teal_slices) +S3method(format,teal_module) +S3method(format,teal_modules) S3method(get_metadata,default) S3method(get_metadata,tdata) S3method(join_keys,tdata) @@ -9,8 +11,6 @@ S3method(print,teal_modules) S3method(srv_nested_tabs,default) S3method(srv_nested_tabs,teal_module) S3method(srv_nested_tabs,teal_modules) -S3method(toString,teal_module) -S3method(toString,teal_modules) S3method(ui_nested_tabs,default) S3method(ui_nested_tabs,teal_module) S3method(ui_nested_tabs,teal_modules) diff --git a/NEWS.md b/NEWS.md index 8305ee2a3f..2eaa43eb96 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,7 +2,6 @@ ### New features -* `data` argument in `init` now accepts `teal_data` and `teal_data_module`. * Added `landing_popup_module` function which creates a module that will display a popup when the app starts. The popup will block access to the app until it is dismissed. * Filter state snapshots can now be uploaded from file. See `?snapshot`. * Added `as_tdata` function to facilitate migration of modules to the new `teal_data` class. @@ -10,9 +9,11 @@ ### Breaking changes +* `data` argument in `init` now accepts only `teal_data` and `teal_data_module`. * `tdata` has been deprecated and replaced with `teal_data`. Support for `tdata` passed to the `data` argument in `module(server)` will be removed in the next release. -* `module(ui)` argument no longer accepts `data` and `datasets` arguments. All data dependent logic should be set in the `server` function. -* `module(server)` argument deprecated `datasets` argument. `teal_module`s' `server` functions should accept `data` (`teal_data`) instead. +* `module(ui)` argument no longer accepts `data` and `datasets` arguments. All data dependent logic should be set in the server function. +* `module(server)` argument deprecated `datasets` argument. `teal_module`s' server functions should accept `data` (`teal_data`) instead. +* Changed the order of formal arguments in `init`. `filter` now comes directly after `modules`, before `title`. ### Miscellaneous @@ -56,7 +57,7 @@ * Added the `validate_inputs` function that transfers input validation messages to app output. * `modules` argument of `init` accepts `teal_module` type of object. There is no need to wrap up a single module in `modules()` or `list()`. -* Updated `module_nested_tabs` so that only active modules are calculated in a teal app. +* Updated `module_nested_tabs` so that only active modules are calculated in a `teal` app. ### Miscellaneous @@ -96,10 +97,10 @@ ### Enhancements * Added new function `reporter_previewer_module` to wrap the `teal.reporter` package previewer functionality as a `teal` module. * Updated `teal` to support `modules` which include reporting. If any `module` which supports reporting is included then a `reporter_previewer_module` is included. -* Added default arguments to `module()` and the `server` argument is now a function where the second argument can be `...` or `datasets`. +* Added default arguments to `module()` and the server argument is now a function where the second argument can be `...` or `datasets`. ### Breaking changes -* Deprecated `bookmarkableShinyApp`. In future releases the `teal` framework will stop supporting shiny bookmarking (which has not officially been supported); it may be officially supported in the future. Note the filter panel in `teal.slice` retains its ability to save and restore its state if used in a standalone `shiny` app with bookmarking. +* Deprecated `bookmarkableShinyApp`. In future releases the `teal` framework will stop supporting `shiny` bookmarking (which has not officially been supported); it may be officially supported in the future. Note the filter panel in `teal.slice` retains its ability to save and restore its state if used in a standalone `shiny` app with bookmarking. ### Miscellaneous * Added a template to the `pkgdown` configuration. @@ -110,7 +111,7 @@ # teal 0.11.0 * `teal.data`: creating and loading the data needed for `teal` applications. -* `teal.widgets`: shiny components used within `teal`. +* `teal.widgets`: `shiny` components used within `teal`. * `teal.slice`: provides a filtering panel to allow filtering of data. * `teal.code`: handles reproducibility of outputs. * `teal.transform`: standardizes extracting and merging data. @@ -128,7 +129,7 @@ The `teal` package contains the code to create apps (`teal::init`), to create a * Due to deprecation of `root_modules` any `label` argument to `modules` must be explicitly named. For example `modules("lab", mod1, mod2)` should be replaced with `modules(label = "lab", mod1, mod2)`. ### Miscellaneous -* Minor changes to internals of `teal`: main module panel now has fixed shiny name `root` and the active tab is named `active_tab` not `Active_tab`. +* Minor changes to internals of `teal`: main module panel now has fixed `shiny` name `root` and the active tab is named `active_tab` not `Active_tab`. * `MultiAssayExperiment` is now suggested packages, not required. Objects dependent on `MultiAssayExperiment` are changed to lazy-load this now suggested package. ### Bug fixes @@ -149,7 +150,7 @@ The `teal` package contains the code to create apps (`teal::init`), to create a * Added support for logging using the `logger` package. * Added a new function `register_logger`, which registers a logger in a given namespace. * Added trace and info levels log messages to the `teal` framework. -* Added `pid` and shiny session token into footnote so app developers can identify logs for apps. +* Added `pid` and `shiny` session token into footnote so app developers can identify logs for apps. #### Other * Added print methods to the `DatasetConnector`, `RelationalData`, `RelationalDataconnector` and `JoinKeys` classes and added input validation to the implementation of the print method that was already in the `Dataset` object. @@ -157,7 +158,7 @@ The `teal` package contains the code to create apps (`teal::init`), to create a * Added public facing constructor functions for `CDISCDataConnector`, `RelationalDataConnector`, and `DataConnection` classes. * Modified `data_extract_spec` to allow both the `filter` and `select` parameters to be `NULL`, which results in the `data_extract_ui` acting as if a `filter_spec` with all variables as possible choices had been supplied as the `filter` argument and a `select_spec` with the `multiple` parameter set to `TRUE` had been supplied as the `select` argument. * Added support of the full screen for a `module` when the `filters` argument is equal `NULL`. -* Added support for `shiny::moduleServer` passed to the `server` parameter of `teal::module`. +* Added support for `shiny::moduleServer` passed to the server parameter of `teal::module`. * Added `teal.threshold_slider_vs_checkboxgroup` as an R option: if a categorical variable has more than this number of unique values, the filter panel uses a drop-down select input instead of a checkbox group. * Extended the `FilteredData` API to allow managing filter states programmatically and not only from the UI of a `teal` application. * Hid the buttons to remove filters from all datasets and each dataset when there are no active filters. @@ -238,7 +239,7 @@ function call. * Changed the displayed format of the data name and the column name in `data_extract_spec` UI elements. Both are now compressed to `.` if they don't change during runtime of the app. * Added `ADSAFTTE` to the list of recognized `ADaM` dataset names. * Added another example to `data_extract_spec`'s doc string showcasing app users can choose a variable used for filtering in the encoding panel. -* Added CSS styling to tool tips in teal modules. +* Added CSS styling to tool tips in `teal` modules. ### Bug fixes * Fixed an edge case error when creating a filter on variable with all missing values crashed the app. @@ -248,8 +249,8 @@ function call. ### Enhancements * Released `snowflake` connection and connectors. -* Changed ordering of datasets to be more intuitive (topologically first for `CDISC` datasets only and then according to input datasets order). -* When closing a teal app (ending a user shiny session), all `DataConnection`s will now try to close their connections. +* Changed ordering of datasets to be more intuitive (topologically first for CDISC datasets only and then according to input datasets order). +* When closing a `teal` app (ending a user `shiny` session), all `DataConnection`s will now try to close their connections. * Added `ADHY` keys to configuration file. * Extended the `filter_spec` function: the parameter `choices` is no longer mandatory (the function will take all possible choices by default) and the `vars` parameter additionally accepts the `choices_selected` and allows to change the variables for filtering using the UI elements in the encoding panel. @@ -290,7 +291,7 @@ function call. * Fixed lack of labels for `character` and `factor` variables in the Filter Panel. * All variables are now displayed in `module_filter_panel`, not only those of types `numeric`, `logical`, `factor`, `character` and `Date` * Fixed `mutate_data` to accept the whole scope of objects for `vars`. -* Clarified `teal::init` function documentation to state that custom `CSS` loading code with `htmltools::htmlDependency` should be included in the `header` argument rather than inside `ui` arguments of modules. +* Clarified `teal::init` function documentation to state that custom `CSS` loading code with `htmltools::htmlDependency` should be included in the `header` argument rather than inside UI arguments of modules. * Enabled empty select field inside `data_extract_spec`. * Added new argument `drop_keys` to `filter_spec` to decide whether to drop or keep keys columns on single filter on those columns. * Added a new optional argument `keys` to `variable_choices`. `keys` specifies the names of the variables, which should have the new key icon shown next to them in the variable drop down menus in the left-hand side encoding panels instead of the icon appropriate for their original R variable type. `variable_choices` now also works with `RelationalDataset` and `RelationalDatasetConnector` objects. @@ -306,7 +307,7 @@ function call. * Added `lifecycle` badges to all exported functions. * Added new `code_dataset_connector` and `code_cdisc_dataset_connector` functions which enable the creation of new delayed data objects given a string of code. * Added new functions `csv_dataset_connector` and `csv_cdisc_dataset_connector`. -* Updated `set_ui_input` method of `RawDatasetConnector` and `NamedDatasetConnector` to handle user defined shiny inputs. +* Updated `set_ui_input` method of `RawDatasetConnector` and `NamedDatasetConnector` to handle user defined `shiny` inputs. * Include `Keep Inf` checkbox for numerical filter items. `Keep NA` and `Keep Inf` checkbox doesn't appear if there are no missing or infinite values. * Replace existing `RelationalData` class with abstract class `RelationalDataCollection` and rename `RelationalDataList` class as `RelationalData`. The `data` argument to `teal::init` is now always a `RelationalData` object. * Added `fun_cdisc_dataset_connector` to enable providing a custom function which returning a dataset. @@ -319,8 +320,8 @@ function call. # teal 0.9.0 * `cdisc_dataset` and `dataset` now return R6 class objects (`RelationalDataset`). -* A new `teal_data` function to include datasets and connectors into teal application. -* `cdisc_data` function to include datasets and connectors into teal application where a `check` argument still could be used and other consistency tests are performed. +* A new `teal_data` function to include datasets and connectors into `teal` application. +* `cdisc_data` function to include datasets and connectors into `teal` application where a `check` argument still could be used and other consistency tests are performed. * `get_raw_data` can be used to derive raw data from R6 objects e.g. (`RelationalDataset`). * `RawDatasetConnector`, `NamedDatasetConnector` and `RelationalDatasetConnector` to execute custom function call in order to get data from connection. * `CodeClass` to manage reproducibility of the data and relationships between datasets. Not directly exposed to the public interface. @@ -332,11 +333,11 @@ function call. `variable_choices` as S3 class applied on `data.frame` and also on delayed data. * You can no longer modify the `app$datasets`, but must instead use argument `filter` in the `init` function. -* New modules were created to create a module of nested teal modules, then another one that adds the right filter pane to each tab. The `teal::init` function stays unchanged. -* The `teal::init` function now returns a `UI` function with an optional `id` argument. This allows to embed it into other applications. A split view of two teal applications side-by-side is one such example and shown in a vignette. `teal::init` was turned into a wrapper function around `module_teal_with_splash.R` and developers that want to embed teal as a Shiny module should directly work with these functions (`ui_teal_with_splash` and `srv_teal_with_splash`) instead of `teal::init`. +* New modules were created to create a module of nested `teal` modules, then another one that adds the right filter pane to each tab. The `teal::init` function stays unchanged. +* The `teal::init` function now returns a UI function with an optional `id` argument. This allows to embed it into other applications. A split view of two `teal` applications side-by-side is one such example and shown in a vignette. `teal::init` was turned into a wrapper function around `module_teal_with_splash.R` and developers that want to embed `teal` as a `shiny` module should directly work with these functions (`ui_teal_with_splash` and `srv_teal_with_splash`) instead of `teal::init`. * The `teal::init` function now has a title parameter to set the title of the browser window. * Missing data `NA` is now explicitly addressed in the filter panel: `NA`s are excluded by default and a checkbox to include them was added. -* Statistics of the data are visually depicted in terms of histograms or bar charts overlayed onto the Shiny input elements. +* Statistics of the data are visually depicted in terms of histograms or bar charts overlayed onto the `shiny` input elements. * Added buttons to remove all filters applied to a dataset. * Restored the functionality to hide the filter panel for a module when it was constructed with `filters = NULL`. * Moved helper functions into `utils.nest` and removed unused functions `set_labels_df` and `get_labels_df`. @@ -348,7 +349,7 @@ function call. * Datasets and materialized connectors are provided to `FilteredData` by `set_datasets_data` function located in `init_datasets.R` file. * Renamed `get_dataset()` method to `get_data()`. * Renamed `get_filter_call()` method to `get_filter_expr()`; returns an expression rather than a list. -* Removed argument `isolate` from `get_data()` method and similar methods. You must `isolate` it yourself as needed. If you want to temporarily deactivate Shiny errors due to missing errors, you can set `options(shiny.suppressMissingContextError = TRUE)`. In general, avoid `isolate` as this breaks reactivity. +* Removed argument `isolate` from `get_data()` method and similar methods. You must `isolate` it yourself as needed. If you want to temporarily deactivate `shiny` errors due to missing errors, you can set `options(shiny.suppressMissingContextError = TRUE)`. In general, avoid `isolate` as this breaks reactivity. * We added a development module to add several filters at once, e.g. safety filters. This is to be evaluated before it is converted into a proper module and made available to end-users. @@ -366,11 +367,11 @@ function call. # teal 0.8.3 -* Enable `teal` app to initialize without data. The data are then loaded from within the teal app. +* Enable `teal` app to initialize without data. The data are then loaded from within the `teal` app. * New classes (`DatasetConnector`, `DataConnector`) to connect to various data sources, including: * connector to `rice` API - `rice_data` and `rice_dataset_connector` * connector to `RDS` files - `rds_data` and `rds_dataset_connector` -* Message appears at bottom right of Shiny app when Shiny is busy to update the views. +* Message appears at bottom right of `shiny` app when `shiny` is busy to update the views. * Remove `labels` argument of `cdisc_data` function. Labels should now already be present in the data passed to the `cdisc_data` function. This can be achieved using the `var_relabel` function. # teal 0.8.2 @@ -412,7 +413,7 @@ function call. # teal 0.0.4 -* Bug fix where teal crashes when a filter variable gets added that has many decimal places. +* Bug fix where `teal` crashes when a filter variable gets added that has many decimal places. # teal 0.0.3 diff --git a/R/dummy_functions.R b/R/dummy_functions.R index 8f1f2d3d5d..dd0b52800e 100644 --- a/R/dummy_functions.R +++ b/R/dummy_functions.R @@ -1,81 +1,9 @@ -#' Get dummy `CDISC` data -#' -#' Get dummy `CDISC` data including `ADSL`, `ADAE` and `ADLB`. -#' Some NAs are also introduced to stress test. -#' -#' @return `cdisc_data` -#' @keywords internal -example_cdisc_data <- function() { # nolint - ADSL <- data.frame( # nolint - STUDYID = "study", - USUBJID = 1:10, - SEX = sample(c("F", "M"), 10, replace = TRUE), - AGE = stats::rpois(10, 40) - ) - ADTTE <- rbind(ADSL, ADSL, ADSL) # nolint - ADTTE$PARAMCD <- rep(c("OS", "EFS", "PFS"), each = 10) # nolint - ADTTE$AVAL <- c( # nolint - stats::rnorm(10, mean = 700, sd = 200), # dummy OS level - stats::rnorm(10, mean = 400, sd = 100), # dummy EFS level - stats::rnorm(10, mean = 450, sd = 200) # dummy PFS level - ) - - ADSL$logical_test <- sample(c(TRUE, FALSE, NA), size = nrow(ADSL), replace = TRUE) # nolint - ADSL$SEX[c(2, 5)] <- NA # nolint - - res <- teal.data::cdisc_data( - ADSL = ADSL, - ADTTE = ADTTE, - code = ' - ADSL <- data.frame( - STUDYID = "study", - USUBJID = 1:10, - SEX = sample(c("F", "M"), 10, replace = TRUE), - AGE = rpois(10, 40) - ) - ADTTE <- rbind(ADSL, ADSL, ADSL) - ADTTE$PARAMCD <- rep(c("OS", "EFS", "PFS"), each = 10) - ADTTE$AVAL <- c( - rnorm(10, mean = 700, sd = 200), - rnorm(10, mean = 400, sd = 100), - rnorm(10, mean = 450, sd = 200) - ) - - ADSL$logical_test <- sample(c(TRUE, FALSE, NA), size = nrow(ADSL), replace = TRUE) - ADSL$SEX[c(2, 5)] <- NA - ' - ) - return(res) -} - -#' Get datasets to go with example modules. -#' -#' Creates a nested list, the structure of which matches the module hierarchy created by `example_modules`. -#' Each list leaf is the same `FilteredData` object. -#' -#' @return named list of `FilteredData` objects, each with `ADSL` set. -#' @keywords internal -example_datasets <- function() { # nolint - dummy_cdisc_data <- example_cdisc_data() - datasets <- teal_data_to_filtered_data(dummy_cdisc_data) - list( - "d2" = list( - "d3" = list( - "aaa1" = datasets, - "aaa2" = datasets, - "aaa3" = datasets - ), - "bbb" = datasets - ), - "ccc" = datasets - ) -} - #' An example `teal` module #' -#' @description `r lifecycle::badge("experimental")` -#' @inheritParams module -#' @return A `teal` module which can be included in the `modules` argument to [teal::init()]. +#' `r lifecycle::badge("experimental")` +#' +#' @inheritParams teal_modules +#' @return A `teal` module which can be included in the `modules` argument to [init()]. #' @examples #' app <- init( #' data = teal_data(IRIS = iris, MTCARS = mtcars), @@ -92,7 +20,6 @@ example_module <- function(label = "example teal module", datanames = "all") { server = function(id, data) { checkmate::assert_class(data(), "teal_data") moduleServer(id, function(input, output, session) { - ns <- session$ns updateSelectInput(session, "dataname", choices = isolate(teal.data::datanames(data()))) output$text <- renderPrint({ req(input$dataname) @@ -101,7 +28,7 @@ example_module <- function(label = "example teal module", datanames = "all") { teal.widgets::verbatim_popup_srv( id = "rcode", verbatim_content = reactive(teal.code::get_code(data())), - title = "Association Plot" + title = "Example Code" ) }) }, @@ -118,30 +45,3 @@ example_module <- function(label = "example teal module", datanames = "all") { datanames = datanames ) } - - -#' Get example modules. -#' -#' Creates an example hierarchy of `teal_modules` from which a `teal` app can be created. -#' @param datanames (`character`)\cr -#' names of the datasets to be used in the example modules. Possible choices are `ADSL`, `ADTTE`. -#' @return `teal_modules` -#' @keywords internal -example_modules <- function(datanames = c("ADSL", "ADTTE")) { - checkmate::assert_subset(datanames, c("ADSL", "ADTTE")) - mods <- modules( - label = "d1", - modules( - label = "d2", - modules( - label = "d3", - example_module(label = "aaa1", datanames = datanames), - example_module(label = "aaa2", datanames = datanames), - example_module(label = "aaa3", datanames = datanames) - ), - example_module(label = "bbb", datanames = datanames) - ), - example_module(label = "ccc", datanames = datanames) - ) - return(mods) -} diff --git a/R/get_rcode_utils.R b/R/get_rcode_utils.R index a7a862d8f8..713051a314 100644 --- a/R/get_rcode_utils.R +++ b/R/get_rcode_utils.R @@ -1,8 +1,8 @@ #' Generates library calls from current session info #' -#' Function to create multiple library calls out of current session info to make reproducible code works. +#' Function to create multiple library calls out of current session info to ensure reproducible code works. #' -#' @return Character object contain code +#' @return Character vector of `library()` calls. #' @keywords internal get_rcode_libraries <- function() { vapply( @@ -18,21 +18,20 @@ get_rcode_libraries <- function() { paste0(collapse = "") } - - +#' @noRd +#' @keywords internal get_rcode_str_install <- function() { code_string <- getOption("teal.load_nest_code") - - if (!is.null(code_string) && is.character(code_string)) { - return(code_string) + if (is.character(code_string)) { + code_string + } else { + "# Add any code to install/load your NEST environment here\n" } - - return("# Add any code to install/load your NEST environment here\n") } #' Get datasets code #' -#' Get combined code from `FilteredData` and from `CodeClass` object. +#' Retrieve complete code to create, verify, and filter a dataset. #' #' @param datanames (`character`) names of datasets to extract code from #' @param datasets (`FilteredData`) object diff --git a/R/include_css_js.R b/R/include_css_js.R index a23b34ec75..e763555011 100644 --- a/R/include_css_js.R +++ b/R/include_css_js.R @@ -2,21 +2,20 @@ #' #' `system.file` should not be used to access files in other packages, it does #' not work with `devtools`. Therefore, we redefine this method in each package -#' as needed. Thus, we do not export this method +#' as needed. Thus, we do not export this method. #' #' @param pattern (`character`) pattern of files to be included #' -#' @return HTML code that includes `CSS` files +#' @return HTML code that includes `CSS` files. #' @keywords internal include_css_files <- function(pattern = "*") { css_files <- list.files( system.file("css", package = "teal", mustWork = TRUE), pattern = pattern, full.names = TRUE ) - return( - shiny::singleton( - shiny::tags$head(lapply(css_files, shiny::includeCSS)) - ) + + singleton( + tags$head(lapply(css_files, includeCSS)) ) } @@ -29,14 +28,14 @@ include_css_files <- function(pattern = "*") { #' @param pattern (`character`) pattern of files to be included, passed to `system.file` #' @param except (`character`) vector of basename filenames to be excluded #' -#' @return HTML code that includes `JS` files +#' @return HTML code that includes `JS` files. #' @keywords internal include_js_files <- function(pattern = NULL, except = NULL) { checkmate::assert_character(except, min.len = 1, any.missing = FALSE, null.ok = TRUE) js_files <- list.files(system.file("js", package = "teal", mustWork = TRUE), pattern = pattern, full.names = TRUE) js_files <- js_files[!(basename(js_files) %in% except)] # no-op if except is NULL - return(singleton(lapply(js_files, includeScript))) + singleton(lapply(js_files, includeScript)) } #' Run `JS` file from `/inst/js/` package directory @@ -48,32 +47,38 @@ include_js_files <- function(pattern = NULL, except = NULL) { #' #' `system.file` should not be used to access files in other packages, it does #' not work with `devtools`. Therefore, we redefine this method in each package -#' as needed. Thus, we do not export this method +#' as needed. Thus, we do not export this method. #' -#' @param files (`character`) vector of filenames +#' @param files (`character`) vector of filenames. +#' +#' @return returns `NULL`, invisibly. #' @keywords internal run_js_files <- function(files) { checkmate::assert_character(files, min.len = 1, any.missing = FALSE) lapply(files, function(file) { shinyjs::runjs(paste0(readLines(system.file("js", file, package = "teal", mustWork = TRUE)), collapse = "\n")) }) - return(invisible(NULL)) + invisible(NULL) } -#' Code to include teal `CSS` and `JavaScript` files +#' Code to include `teal` `CSS` and `JavaScript` files #' #' This is useful when you want to use the same `JavaScript` and `CSS` files that are -#' used with the teal application. -#' This is also useful for running standalone modules in teal with the correct +#' used with the `teal` application. +#' This is also useful for running standalone modules in `teal` with the correct #' styles. #' Also initializes `shinyjs` so you can use it. #' -#' @return HTML code to include +#' Simply add `include_teal_css_js()` as one of the UI elements. +#' @return A `shiny.tag.list`. #' @examples +#' # use non-exported function from teal +#' include_teal_css_js <- getFromNamespace("include_teal_css_js", "teal") #' shiny_ui <- tagList( -#' teal:::include_teal_css_js(), +#' include_teal_css_js(), #' p("Hello") #' ) +#' #' @keywords internal include_teal_css_js <- function() { tagList( diff --git a/R/init.R b/R/init.R index 2bbe954795..01af3c0eea 100644 --- a/R/init.R +++ b/R/init.R @@ -1,47 +1,41 @@ # This is the main function from teal to be used by the end-users. Although it delegates -# directly to `module_teal_with_splash.R`, we keep it in a separate file because its doc is quite large +# directly to `module_teal_with_splash.R`, we keep it in a separate file because its documentation is quite large # and it is very end-user oriented. It may also perform more argument checking with more informative # error messages. - -#' Create the Server and UI Function For the Shiny App +#' Create the server and UI function for the `shiny` app #' #' @description `r lifecycle::badge("stable")` +#' #' End-users: This is the most important function for you to start a -#' teal app that is composed out of teal modules. +#' `teal` app that is composed of `teal` modules. #' #' @details #' When initializing the `teal` app, if `datanames` are not set for the `teal_data` object, #' defaults from the `teal_data` environment will be used. #' -#' @param data (`teal_data`, `teal_data_module`, `named list`)\cr -#' `teal_data` object as returned by [teal.data::teal_data()] or -#' `teal_data_module` or simply a list of a named list of objects -#' (`data.frame` or `MultiAssayExperiment`). -#' @param modules (`list`, `teal_modules` or `teal_module`)\cr +#' @param data (`teal_data` or `teal_data_module`) +#' For constructing the data object, refer to [teal_data()] and [teal_data_module()]. +#' @param modules (`list` or `teal_modules` or `teal_module`) #' nested list of `teal_modules` or `teal_module` objects or a single #' `teal_modules` or `teal_module` object. These are the specific output modules which -#' will be displayed in the teal application. See [modules()] and [module()] for +#' will be displayed in the `teal` application. See [modules()] and [module()] for #' more details. -#' @param title (`shiny.tag` or `character(1)`)\cr +#' @param filter (`teal_slices`) +#' Specifies the initial filter using [teal_slices()]. +#' @param title (`shiny.tag` or `character(1)`) #' The browser window title. Defaults to a title "teal app" with the icon of NEST. #' Can be created using the `build_app_title()` or #' by passing a valid `shiny.tag` which is a head tag with title and link tag. -#' @param filter (`teal_slices`)\cr -#' Specification of initial filter. Filters can be specified using [teal::teal_slices()]. -#' Old way of specifying filters through a list is deprecated and will be removed in the -#' next release. Please fix your applications to use [teal::teal_slices()]. -#' @param header (`shiny.tag` or `character(1)`) \cr +#' @param header (`shiny.tag` or `character(1)`) #' The header of the app. -#' @param footer (`shiny.tag` or `character(1)`)\cr +#' @param footer (`shiny.tag` or `character(1)`) #' The footer of the app. -#' @param id (`character`)\cr -#' module id to embed it, if provided, -#' the server function must be called with [shiny::moduleServer()]; -#' See the vignette for an example. However, [ui_teal_with_splash()] -#' is then preferred to this function. +#' @param id (`character`) +#' Optional string specifying the `shiny` module id in cases it is used as a `shiny` module +#' rather than a standalone `shiny` app. This is a legacy feature. #' -#' @return named list with `server` and `ui` function +#' @return Named list with server and UI functions. #' #' @export #' @@ -79,7 +73,6 @@ #' datanames = "new_iris" #' ) #' ), -#' title = "App title", #' filter = teal_slices( #' teal_slice(dataname = "new_iris", varname = "Species"), #' teal_slice(dataname = "new_iris", varname = "Sepal.Length"), @@ -91,6 +84,7 @@ #' global_filters = "new_mtcars cyl" #' ) #' ), +#' title = "App title", #' header = tags$h1("Sample App"), #' footer = tags$p("Copyright 2017 - 2023") #' ) @@ -100,8 +94,8 @@ #' init <- function(data, modules, - title = build_app_title(), filter = teal_slices(), + title = build_app_title(), header = tags$p(), footer = tags$p(), id = character(0)) { @@ -119,14 +113,7 @@ init <- function(data, ) ) } - checkmate::assert( - .var.name = "data", - checkmate::check_multi_class(data, c("teal_data", "teal_data_module")), - checkmate::check_list(data, names = "named") - ) - if (is.list(data) && !inherits(data, "teal_data_module")) { - data <- do.call(teal.data::teal_data, data) - } + checkmate::assert_multi_class(data, c("teal_data", "teal_data_module")) ## `modules` checkmate::assert( @@ -142,11 +129,7 @@ init <- function(data, } ## `filter` - checkmate::assert( - .var.name = "filter", - checkmate::check_class(filter, "teal_slices"), - checkmate::check_list(filter, names = "named") - ) + checkmate::assert_class(filter, "teal_slices") ## all other arguments checkmate::assert( @@ -217,7 +200,7 @@ init <- function(data, ## `data` - `modules` if (inherits(data, "teal_data")) { if (length(teal_data_datanames(data)) == 0) { - stop("`data` object has no datanames and its environment is empty. Specify `datanames(data)` and try again.") + stop("The environment of `data` is empty.") } # in case of teal_data_module this check is postponed to the srv_teal_with_splash is_modules_ok <- check_modules_datanames(modules, teal_data_datanames(data)) @@ -235,9 +218,8 @@ init <- function(data, } # Note regarding case `id = character(0)`: - # rather than using `callModule` and creating a submodule of this module, we directly modify - # the `ui` and `server` with `id = character(0)` and calling the server function directly - # rather than through `callModule` + # rather than creating a submodule of this module, we directly modify + # the UI and server with `id = character(0)` and calling the server function directly res <- list( ui = ui_teal_with_splash(id = id, data = data, title = title, header = header, footer = footer), server = function(input, output, session) { @@ -250,5 +232,5 @@ init <- function(data, logger::log_trace("init teal app has been initialized.") - return(res) + res } diff --git a/R/landing_popup_module.R b/R/landing_popup_module.R index fd2501c329..0637d153af 100644 --- a/R/landing_popup_module.R +++ b/R/landing_popup_module.R @@ -1,23 +1,23 @@ -#' Landing Popup Module +#' Landing popup module #' #' @description Creates a landing welcome popup for `teal` applications. #' #' This module is used to display a popup dialog when the application starts. -#' The dialog blocks the access to the application and must be closed with a button before the application is viewed. +#' The dialog blocks access to the application and must be closed with a button before the application can be viewed. #' -#' @param label `character(1)` the label of the module. -#' @param title `character(1)` the text to be displayed as a title of the popup. -#' @param content The content of the popup. Passed to `...` of `shiny::modalDialog`. Can be a `character` -#' or a list of `shiny.tag`s. See examples. -#' @param buttons `shiny.tag` or a list of tags (`tagList`). Typically a `modalButton` or `actionButton`. See examples. +#' @param label (`character(1)`) Label of the module. +#' @param title (`character(1)`) Text to be displayed as popup title. +#' @param content (`character(1)`, `shiny.tag` or `shiny.tag.list`) with the content of the popup. +#' Passed to `...` of `shiny::modalDialog`. See examples. +#' @param buttons (`shiny.tag` or `shiny.tag.list`) Typically a `modalButton` or `actionButton`. See examples. #' #' @return A `teal_module` (extended with `teal_landing_module` class) to be used in `teal` applications. #' #' @examples -#' app1 <- teal::init( +#' app1 <- init( #' data = teal_data(iris = iris), -#' modules = teal::modules( -#' teal::landing_popup_module( +#' modules = modules( +#' landing_popup_module( #' content = "A place for the welcome message or a disclaimer statement.", #' buttons = modalButton("Proceed") #' ), @@ -28,10 +28,10 @@ #' shinyApp(app1$ui, app1$server) #' } #' -#' app2 <- teal::init( +#' app2 <- init( #' data = teal_data(iris = iris), -#' modules = teal::modules( -#' teal::landing_popup_module( +#' modules = modules( +#' landing_popup_module( #' title = "Welcome", #' content = tags$b( #' "A place for the welcome message or a disclaimer statement.", diff --git a/R/module_filter_manager.R b/R/module_filter_manager.R index 1856b6816b..88d2907ff8 100644 --- a/R/module_filter_manager.R +++ b/R/module_filter_manager.R @@ -1,10 +1,38 @@ +#' Manage multiple `FilteredData` objects +#' +#' Oversee filter states across the entire application. +#' +#' This module observes changes in the filters of each `FilteredData` object +#' and keeps track of all filters used. A mapping of filters to modules +#' is kept in the `mapping_matrix` object (which is actually a `data.frame`) +#' that tracks which filters (rows) are active in which modules (columns). +#' +#' @name module_filter_manager +#' +#' @param id (`character(1)`) +#' `shiny` module id. +#' @param filtered_data_list (named `list`) +#' A list, possibly nested, of `FilteredData` objects. +#' Each `FilteredData` will be served to one module in the `teal` application. +#' The structure of the list must reflect the nesting of modules in tabs +#' and the names of the list must match the labels of their respective modules. +#' @inheritParams init +#' @return A list of `reactive`s, each holding a `teal_slices`, as returned by `filter_manager_module_srv`. +#' @keywords internal +#' +NULL + #' Filter manager modal #' -#' Opens modal containing the filter manager UI. +#' Opens a modal containing the filter manager UI. #' #' @name module_filter_manager_modal -#' @inheritParams filter_manager_srv +#' @inheritParams module_filter_manager #' @examples +#' # use non-exported function from teal +#' filter_manager_modal_ui <- getFromNamespace("filter_manager_modal_ui", "teal") +#' filter_manager_modal_srv <- getFromNamespace("filter_manager_modal_srv", "teal") +#' #' fd1 <- teal.slice::init_filtered_data(list(iris = list(dataset = iris))) #' fd2 <- teal.slice::init_filtered_data( #' list(iris = list(dataset = iris), mtcars = list(dataset = mtcars)) @@ -24,20 +52,22 @@ #' ) #' ) #' -#' app <- shinyApp( -#' ui = fluidPage( -#' teal:::filter_manager_modal_ui("manager") -#' ), -#' server = function(input, output, session) { -#' teal:::filter_manager_modal_srv( +#' ui <- fluidPage( +#' filter_manager_modal_ui("manager") +#' ) +#' +#' server <- function(input, output, session) { +#' observe({ +#' filter_manager_modal_srv( #' "manager", #' filtered_data_list = list(module1 = fd1, module2 = fd2, module3 = fd3), #' filter = filter #' ) -#' } -#' ) +#' }) +#' } +#' #' if (interactive()) { -#' shinyApp(app$ui, app$server) +#' shinyApp(ui, server) #' } #' #' @keywords internal @@ -84,35 +114,14 @@ filter_manager_ui <- function(id) { ) } -#' Manage multiple `FilteredData` objects -#' -#' Oversee filter states in the whole application. -#' #' @rdname module_filter_manager -#' @details -#' This module observes the changes of the filters in each `FilteredData` object -#' and keeps track of all filters used. A mapping of filters to modules -#' is kept in the `mapping_matrix` object (which is actually a `data.frame`) -#' that tracks which filters (rows) are active in which modules (columns). -#' -#' @param id (`character(1)`)\cr -#' `shiny` module id. -#' @param filtered_data_list (`named list`)\cr -#' A list, possibly nested, of `FilteredData` objects. -#' Each `FilteredData` will be served to one module in the `teal` application. -#' The structure of the list must reflect the nesting of modules in tabs -#' and names of the list must be the same as labels of their respective modules. -#' @inheritParams init -#' @return A list of `reactive`s, each holding a `teal_slices`, as returned by `filter_manager_module_srv`. -#' @keywords internal -#' filter_manager_srv <- function(id, filtered_data_list, filter) { moduleServer(id, function(input, output, session) { logger::log_trace("filter_manager_srv initializing for: { paste(names(filtered_data_list), collapse = ', ')}.") is_module_specific <- isTRUE(attr(filter, "module_specific")) - # Create global list of slices. + # Create a global list of slices. # Contains all available teal_slice objects available to all modules. # Passed whole to instances of FilteredData used for individual modules. # Down there a subset that pertains to the data sets used in that module is applied and displayed. @@ -136,7 +145,7 @@ filter_manager_srv <- function(id, filtered_data_list, filter) { flatten_nested(filtered_data_list) } - # Create mapping fo filters to modules in matrix form (presented as data.frame). + # Create mapping of filters to modules in matrix form (presented as data.frame). # Modules get NAs for filters that cannot be set for them. mapping_matrix <- reactive({ state_ids_global <- vapply(slices_global(), `[[`, character(1L), "id") @@ -189,7 +198,7 @@ filter_manager_srv <- function(id, filtered_data_list, filter) { #' Module specific filter manager #' -#' Track filter states in single module. +#' Tracks filter states in a single module. #' #' This module tracks the state of a single `FilteredData` object and global `teal_slices` #' and updates both objects as necessary. Filter states added in different modules @@ -197,11 +206,11 @@ filter_manager_srv <- function(id, filtered_data_list, filter) { #' and from there become available in other modules #' by setting `private$available_teal_slices` in each `FilteredData`. #' -#' @param id (`character(1)`)\cr +#' @param id (`character(1)`) #' `shiny` module id. -#' @param module_fd (`FilteredData`)\cr -#' object to filter data in the teal-module -#' @param slices_global (`reactiveVal`)\cr +#' @param module_fd (`FilteredData`) +#' Object containing the data to be filtered in a single `teal` module. +#' @param slices_global (`reactiveVal`) #' stores `teal_slices` with all available filters; allows the following actions: #' - to disable/enable a specific filter in a module #' - to restore saved filter settings diff --git a/R/module_nested_tabs.R b/R/module_nested_tabs.R index dc84f86c75..6d88a3e946 100644 --- a/R/module_nested_tabs.R +++ b/R/module_nested_tabs.R @@ -3,14 +3,13 @@ #' @section `ui_nested_tabs`: #' Each `teal_modules` is translated to a `tabsetPanel` and each #' of its children is another tab-module called recursively. The UI of a -#' `teal_module` is obtained by calling the `ui` function on it. +#' `teal_module` is obtained by calling its UI function. #' -#' The `datasets` argument is required to resolve the teal arguments in an -#' isolated context (with respect to reactivity) +#' The `datasets` argument is required to resolve the `teal` arguments in an +#' isolated context (with respect to reactivity). #' #' @section `srv_nested_tabs`: -#' This module calls recursively all elements of the `modules` returns one which -#' is currently active. +#' This module recursively calls all elements of `modules` and returns currently active one. #' - `teal_module` returns self as a active module. #' - `teal_modules` also returns module active within self which is determined by the `input$active_tab`. #' @@ -18,45 +17,83 @@ #' #' @inheritParams module_tabs_with_filters #' -#' @param depth (`integer(1)`)\cr +#' @param depth (`integer(1)`) #' number which helps to determine depth of the modules nesting. -#' @param is_module_specific (`logical(1)`)\cr +#' @param is_module_specific (`logical(1)`) #' flag determining if the filter panel is global or module-specific. #' When set to `TRUE`, a filter panel is called inside of each module tab. #' -#' @return depending on class of `modules`, `ui_nested_tabs` returns: -#' - `teal_module`: instantiated UI of the module +#' @return +#' Depending on the class of `modules`, `ui_nested_tabs` returns: +#' - `teal_module`: instantiated UI of the module. #' - `teal_modules`: `tabsetPanel` with each tab corresponding to recursively -#' calling this function on it.\cr +#' calling this function on it. +#' #' `srv_nested_tabs` returns a reactive which returns the active module that corresponds to the selected tab. #' #' @examples -#' mods <- teal:::example_modules() -#' datasets <- teal:::example_datasets() -#' app <- shinyApp( -#' ui = function() { -#' tagList( -#' teal:::include_teal_css_js(), -#' textOutput("info"), -#' fluidPage( # needed for nice tabs -#' teal:::ui_nested_tabs("dummy", modules = mods, datasets = datasets) -#' ) -#' ) -#' }, -#' server = function(input, output, session) { -#' active_module <- teal:::srv_nested_tabs( -#' "dummy", -#' datasets = datasets, -#' modules = mods -#' ) -#' output$info <- renderText({ -#' paste0("The currently active tab name is ", active_module()$label) -#' }) -#' } +#' # use non-exported function from teal +#' include_teal_css_js <- getFromNamespace("include_teal_css_js", "teal") +#' teal_data_to_filtered_data <- getFromNamespace("teal_data_to_filtered_data", "teal") +#' ui_nested_tabs <- getFromNamespace("ui_nested_tabs", "teal") +#' srv_nested_tabs <- getFromNamespace("srv_nested_tabs", "teal") +#' +#' # create `teal_data` +#' data <- teal_data(iris = iris, mtcars = mtcars) +#' datanames <- datanames(data) +#' +#' # creates a hierarchy of `teal_modules` from which a `teal` app can be created. +#' mods <- modules( +#' label = "d1", +#' modules( +#' label = "d2", +#' modules( +#' label = "d3", +#' example_module(label = "aaa1", datanames = datanames), +#' example_module(label = "aaa2", datanames = datanames) +#' ), +#' example_module(label = "bbb", datanames = datanames) +#' ), +#' example_module(label = "ccc", datanames = datanames) +#' ) +#' +#' # creates nested list aligned with the module hierarchy created above, +#' # each leaf holding the same `FilteredData` object. +#' datasets <- teal_data_to_filtered_data(data) +#' datasets <- list( +#' "d2" = list( +#' "d3" = list( +#' "aaa1" = datasets, +#' "aaa2" = datasets +#' ), +#' "bbb" = datasets +#' ), +#' "ccc" = datasets #' ) +#' +#' ui <- function() { +#' tagList( +#' include_teal_css_js(), +#' textOutput("info"), +#' fluidPage( # needed for nice tabs +#' ui_nested_tabs("dummy", modules = mods, datasets = datasets) +#' ) +#' ) +#' } +#' server <- function(input, output, session) { +#' active_module <- srv_nested_tabs( +#' "dummy", +#' datasets = datasets, +#' modules = mods +#' ) +#' output$info <- renderText({ +#' paste0("The currently active tab name is ", active_module()$label) +#' }) +#' } #' if (interactive()) { -#' shinyApp(app$ui, app$server) +#' shinyApp(ui, server) #' } +#' #' @keywords internal NULL @@ -263,7 +300,7 @@ srv_nested_tabs.teal_module <- function(id, datasets, modules, is_module_specifi #' Convert `FilteredData` to reactive list of datasets of the `teal_data` type. #' #' Converts `FilteredData` object to `teal_data` object containing datasets needed for a specific module. -#' Please note that if module needs dataset which has a parent, then parent will be also returned. +#' Please note that if a module needs a dataset which has a parent, then the parent will also be returned. #' A hash per `dataset` is calculated internally and returned in the code. #' #' @param module (`teal_module`) module where needed filters are taken from @@ -279,7 +316,10 @@ srv_nested_tabs.teal_module <- function(id, datasets, modules, is_module_specifi datanames <- if (is.null(module$datanames) || identical(module$datanames, "all")) { datasets$datanames() } else { - unique(module$datanames) # todo: include parents! unique shouldn't be needed here! + include_parent_datanames( + module$datanames, + datasets$get_join_keys() + ) } # list of reactive filtered data @@ -300,7 +340,7 @@ srv_nested_tabs.teal_module <- function(id, datasets, modules, is_module_specifi ) data@verified <- attr(datasets, "verification_status") - return(data) + data } #' Get the hash of a dataset @@ -308,7 +348,7 @@ srv_nested_tabs.teal_module <- function(id, datasets, modules, is_module_specifi #' @param datanames (`character`) names of datasets #' @param datasets (`FilteredData`) object holding the data #' -#' @return A list of hashes per dataset +#' @return A list of hashes per dataset. #' @keywords internal #' calculate_hashes <- function(datanames, datasets) { diff --git a/R/module_snapshot_manager.R b/R/module_snapshot_manager.R index da49dd842e..ba7693e788 100644 --- a/R/module_snapshot_manager.R +++ b/R/module_snapshot_manager.R @@ -40,7 +40,7 @@ #' The snapshot contains the `mapping` attribute of the initial application state #' (or one that has been restored), which may not reflect the current one, #' so `mapping_matrix` is transformed to obtain the current mapping, i.e. a list that, -#' when passed to the `mapping` argument of [`teal::teal_slices`], would result in the current mapping. +#' when passed to the `mapping` argument of [teal_slices()], would result in the current mapping. #' This is substituted as the snapshot's `mapping` attribute and the snapshot is added to the snapshot list. #' #' To restore app state, a snapshot is retrieved from storage and rebuilt into a `teal_slices` object. @@ -49,7 +49,7 @@ #' The snapshot is then set as the current content of `slices_global`. #' #' To save a snapshot, the snapshot is retrieved and reassembled just like for restoring, -#' and then saved to file with [`slices_store`]. +#' and then saved to file with [slices_store()]. #' #' When a snapshot is uploaded, it will first be added to storage just like a newly created one, #' and then used to restore app state much like a snapshot taken from storage. @@ -71,7 +71,7 @@ #' @param mapping_matrix (`reactive`) that contains a `data.frame` representation #' of the mapping of filter state ids (rows) to modules labels (columns); #' all columns are `logical` vectors -#' @param filtered_data_list non-nested (`named list`) that contains `FilteredData` objects +#' @param filtered_data_list non-nested (named `list`) that contains `FilteredData` objects #' #' @return Nothing is returned. #' @@ -328,16 +328,13 @@ snapshot_manager_srv <- function(id, slices_global, mapping_matrix, filtered_dat }) } - - - ### utility functions ---- #' Explicitly enumerate global filters. #' #' Transform module mapping such that global filters are explicitly specified for every module. #' -#' @param mapping (`named list`) as stored in mapping parameter of `teal_slices` +#' @param mapping (named `list`) as stored in mapping parameter of `teal_slices` #' @param module_names (`character`) vector containing names of all modules in the app #' @return A `named_list` with one element per module, each element containing all filters applied to that module. #' @keywords internal @@ -356,7 +353,7 @@ unfold_mapping <- function(mapping, module_names) { #' #' @param mapping_matrix (`data.frame`) of logical vectors where #' columns represent modules and row represent `teal_slice`s -#' @return `named list` like that in the `mapping` attribute of a `teal_slices` object. +#' @return Named `list` like that in the `mapping` attribute of a `teal_slices` object. #' @keywords internal #' matrix_to_mapping <- function(mapping_matrix) { diff --git a/R/module_tabs_with_filters.R b/R/module_tabs_with_filters.R index 7c42fc2ea0..a9afb72711 100644 --- a/R/module_tabs_with_filters.R +++ b/R/module_tabs_with_filters.R @@ -14,44 +14,76 @@ #' #' @inheritParams module_teal #' -#' @param datasets (`named list` of `FilteredData`)\cr +#' @param datasets (named `list` of `FilteredData`) #' object to store filter state and filtered datasets, shared across modules. For more #' details see [`teal.slice::FilteredData`]. Structure of the list must be the same as structure #' of the `modules` argument and list names must correspond to the labels in `modules`. #' When filter is not module-specific then list contains the same object in all elements. #' @param reporter (`Reporter`) object from `teal.reporter` #' -#' @return A `tagList` of The main menu, place holders for filters and -#' place holders for the teal modules +#' @return +#' A `shiny.tag.list` containing the main menu, placeholders for filters and placeholders for the `teal` modules. #' +#' @examples +#' # use non-exported function from teal +#' include_teal_css_js <- getFromNamespace("include_teal_css_js", "teal") +#' teal_data_to_filtered_data <- getFromNamespace("teal_data_to_filtered_data", "teal") +#' ui_tabs_with_filters <- getFromNamespace("ui_tabs_with_filters", "teal") +#' srv_tabs_with_filters <- getFromNamespace("srv_tabs_with_filters", "teal") #' -#' @keywords internal +#' # creates `teal_data` +#' data <- teal_data(iris = iris, mtcars = mtcars) +#' datanames <- datanames(data) #' -#' @examples +#' # creates a hierarchy of `teal_modules` from which a `teal` app can be created. +#' mods <- modules( +#' label = "d1", +#' modules( +#' label = "d2", +#' modules( +#' label = "d3", +#' example_module(label = "aaa1", datanames = datanames), +#' example_module(label = "aaa2", datanames = datanames) +#' ), +#' example_module(label = "bbb", datanames = datanames) +#' ), +#' example_module(label = "ccc", datanames = datanames) +#' ) #' -#' mods <- teal:::example_modules() -#' datasets <- teal:::example_datasets() +#' # creates nested list aligned with the module hierarchy created above, +#' # each leaf holding the same `FilteredData` object. +#' datasets <- teal_data_to_filtered_data(data) +#' datasets <- list( +#' "d2" = list( +#' "d3" = list( +#' "aaa1" = datasets, +#' "aaa2" = datasets +#' ), +#' "bbb" = datasets +#' ), +#' "ccc" = datasets +#' ) #' -#' app <- shinyApp( -#' ui = function() { -#' tagList( -#' teal:::include_teal_css_js(), -#' textOutput("info"), -#' fluidPage( # needed for nice tabs -#' ui_tabs_with_filters("dummy", modules = mods, datasets = datasets) -#' ) +#' ui <- function() { +#' tagList( +#' include_teal_css_js(), +#' textOutput("info"), +#' fluidPage( # needed for nice tabs +#' ui_tabs_with_filters("dummy", modules = mods, datasets = datasets) #' ) -#' }, -#' server = function(input, output, session) { -#' output$info <- renderText({ -#' paste0("The currently active tab name is ", active_module()$label) -#' }) -#' active_module <- srv_tabs_with_filters(id = "dummy", datasets = datasets, modules = mods) -#' } -#' ) +#' ) +#' } +#' server <- function(input, output, session) { +#' output$info <- renderText({ +#' paste0("The currently active tab name is ", active_module()$label) +#' }) +#' active_module <- srv_tabs_with_filters(id = "dummy", datasets = datasets, modules = mods) +#' } +#' #' if (interactive()) { -#' shinyApp(app$ui, app$server) +#' shinyApp(ui, server) #' } +#' @keywords internal #' NULL @@ -126,7 +158,10 @@ srv_tabs_with_filters <- function(id, if (identical(active_module()$datanames, "all")) { singleton$datanames() } else { - active_module()$datanames + include_parent_datanames( + active_module()$datanames, + singleton$get_join_keys() + ) } }) singleton <- unlist(datasets)[[1]] @@ -150,6 +185,7 @@ srv_tabs_with_filters <- function(id, showNotification("Data loaded - App fully started up") logger::log_trace("srv_tabs_with_filters initialized the module") - return(active_module) + + active_module }) } diff --git a/R/module_teal.R b/R/module_teal.R index cdecfaf639..1b319a9892 100644 --- a/R/module_teal.R +++ b/R/module_teal.R @@ -1,61 +1,70 @@ # This module is the main teal module that puts everything together. -#' teal main app module +#' `teal` main app module #' -#' This is the main teal app that puts everything together. +#' This is the main `teal` app that puts everything together. #' #' It displays the splash UI which is used to fetch the data, possibly #' prompting for a password input to fetch the data. Once the data is ready, -#' the splash screen is replaced by the actual teal UI that is tabsetted and +#' the splash screen is replaced by the actual `teal` UI that is tabsetted and #' has a filter panel with `datanames` that are relevant for the current tab. #' Nested tabs are possible, but we limit it to two nesting levels for reasons #' of clarity of the UI. #' #' The splash screen functionality can also be used #' for non-delayed data which takes time to load into memory, avoiding -#' Shiny session timeouts. +#' `shiny` session timeouts. #' #' Server evaluates the `teal_data_rv` (delayed data mechanism) and creates the #' `datasets` object that is shared across modules. #' Once it is ready and non-`NULL`, the splash screen is replaced by the -#' main teal UI that depends on the data. +#' main `teal` UI that depends on the data. #' The currently active tab is tracked and the right filter panel #' updates the displayed datasets to filter for according to the active `datanames` #' of the tab. #' -#' It is written as a Shiny module so it can be added into other apps as well. +#' It is written as a `shiny` module so it can be added into other apps as well. #' #' @name module_teal #' -#' @inheritParams ui_teal_with_splash +#' @inheritParams module_teal_with_splash #' -#' @param splash_ui (`shiny.tag`)\cr UI to display initially, -#' can be a splash screen or a Shiny module UI. For the latter, see +#' @param splash_ui (`shiny.tag`) UI to display initially, +#' can be a splash screen or a `shiny` module UI. For the latter, see #' [init()] about how to call the corresponding server function. #' -#' @param teal_data_rv (`reactive`)\cr +#' @param teal_data_rv (`reactive`) #' returns the `teal_data`, only evaluated once, `NULL` value is ignored #' #' @return -#' `ui_teal` returns `HTML` for Shiny module UI. -#' `srv_teal` returns `reactive` which returns the currently active module. -#' -#' @keywords internal +#' Returns a `reactive` expression which returns the currently active module. #' #' @examples -#' mods <- teal:::example_modules() -#' teal_data_rv <- reactive(teal:::example_cdisc_data()) -#' app <- shinyApp( -#' ui = function() { -#' teal:::ui_teal("dummy") -#' }, -#' server = function(input, output, session) { -#' active_module <- teal:::srv_teal(id = "dummy", modules = mods, teal_data_rv = teal_data_rv) -#' } +#' # use non-exported function from teal +#' ui_teal <- getFromNamespace("ui_teal", "teal") +#' srv_teal <- getFromNamespace("srv_teal", "teal") +#' +#' mods <- modules( +#' label = "example app", +#' example_module(label = "example dataset", datanames = c("iris", "mtcars")) #' ) +#' +#' teal_data_rv <- reactive(teal_data(iris = iris, mtcars = mtcars)) +#' +#' ui <- function() { +#' ui_teal("dummy") +#' } +#' +#' server <- function(input, output, session) { +#' active_module <- srv_teal(id = "dummy", modules = mods, teal_data_rv = teal_data_rv) +#' } +#' #' if (interactive()) { -#' shinyApp(app$ui, app$server) +#' shinyApp(ui, server) #' } +#' +#' @keywords internal +#' NULL #' @rdname module_teal @@ -103,7 +112,7 @@ ui_teal <- function(id, div(splash_ui) ) - # show busy icon when shiny session is busy computing stuff + # show busy icon when `shiny` session is busy computing stuff # based on https://stackoverflow.com/questions/17325521/r-shiny-display-loading-message-while-function-is-running/22475216#22475216 #nolint shiny_busy_message_panel <- conditionalPanel( condition = "(($('html').hasClass('shiny-busy')) && (document.getElementById('shiny-notification-panel') == null))", # nolint @@ -115,7 +124,7 @@ ui_teal <- function(id, ) ) - res <- fluidPage( + fluidPage( title = title, theme = get_teal_bs_theme(), include_teal_css_js(), @@ -132,7 +141,6 @@ ui_teal <- function(id, ) ) ) - return(res) } @@ -257,14 +265,13 @@ srv_teal <- function(id, modules, teal_data_rv, filter = teal_slices()) { # must make sure that this is only executed once as modules assume their observers are only # registered once (calling server functions twice would trigger observers twice each time) - active_module <- srv_tabs_with_filters( + srv_tabs_with_filters( id = "main_ui", datasets = datasets, modules = modules, reporter = reporter, filter = filter ) - return(active_module) }) }) } diff --git a/R/module_teal_with_splash.R b/R/module_teal_with_splash.R index 252aa8406e..a87f48fa64 100644 --- a/R/module_teal_with_splash.R +++ b/R/module_teal_with_splash.R @@ -1,22 +1,57 @@ # This file adds a splash screen for delayed data loading on top of teal -#' UI to show a splash screen in the beginning, then delegate to [srv_teal()] +#' Add splash screen to `teal` application. #' #' @description `r lifecycle::badge("stable")` -#' The splash screen could be used to query for a password to fetch the data. -#' [init()] is a very thin wrapper around this module useful for end-users which -#' assumes that it is a top-level module and cannot be embedded. -#' This function instead adheres to the Shiny module conventions. #' -#' If data is obtained through delayed loading, its splash screen is used. Otherwise, -#' a default splash screen is shown. +#' Displays custom splash screen during initial delayed data loading. #' -#' Please also refer to the doc of [init()]. +#' @details +#' This module pauses app initialization pending delayed data loading. +#' This is necessary because the filter panel and modules depend on the data to initialize. #' -#' @param id (`character(1)`)\cr +#' `teal_with_splash` follows the `shiny` module convention. +#' [`init()`] is a wrapper around this that assumes that `teal` it is +#' the top-level module and cannot be embedded. +#' +#' Note: It is no longer recommended to embed `teal` in `shiny` apps as a module. +#' but rather use `init` to create a standalone application. +#' +#' @seealso [init()] +#' +#' @param id (`character(1)`) #' module id #' @inheritParams init +#' @param modules (`teal_modules`) object containing the output modules which +#' will be displayed in the `teal` application. See [modules()] and [module()] for +#' more details. +#' @inheritParams shiny::moduleServer +#' @return +#' Returns a `reactive` expression containing a `teal_data` object when data is loaded or `NULL` when it is not. +#' @name module_teal_with_splash +#' @examples +#' teal_modules <- modules(example_module()) +#' # Shiny app with modular integration of teal +#' ui <- fluidPage( +#' ui_teal_with_splash(id = "app1", data = teal_data()) +#' ) +#' +#' server <- function(input, output, session) { +#' srv_teal_with_splash( +#' id = "app1", +#' data = teal_data(iris = iris), +#' modules = teal_modules +#' ) +#' } +#' +#' if (interactive()) { +#' shinyApp(ui, server) +#' } +#' +NULL + #' @export +#' @rdname module_teal_with_splash ui_teal_with_splash <- function(id, data, title = build_app_title(), @@ -60,23 +95,11 @@ ui_teal_with_splash <- function(id, ) } -#' Server function that loads the data through reactive loading and then delegates -#' to [srv_teal()]. -#' -#' @description `r lifecycle::badge("stable")` -#' Please also refer to the doc of [init()]. -#' -#' @inheritParams init -#' @param modules `teal_modules` object containing the output modules which -#' will be displayed in the teal application. See [modules()] and [module()] for -#' more details. -#' @inheritParams shiny::moduleServer -#' @return `reactive` containing `teal_data` object when data is loaded. -#' If data is not loaded yet, `reactive` returns `NULL`. #' @export +#' @rdname module_teal_with_splash srv_teal_with_splash <- function(id, data, modules, filter = teal_slices()) { checkmate::assert_character(id, max.len = 1, any.missing = FALSE) - checkmate::check_multi_class(data, c("teal_data", "teal_data_module")) + checkmate::assert_multi_class(data, c("teal_data", "teal_data_module")) checkmate::assert_class(modules, "teal_modules") checkmate::assert_class(filter, "teal_slices") @@ -178,6 +201,7 @@ srv_teal_with_splash <- function(id, data, modules, filter = teal_slices()) { res <- srv_teal(id = "teal", modules = modules, teal_data_rv = teal_data_rv_validate, filter = filter) logger::log_trace("srv_teal_with_splash initialized module with data.") - return(res) + + res }) } diff --git a/R/modules.R b/R/modules.R index dc769758af..05b91cd2a5 100644 --- a/R/modules.R +++ b/R/modules.R @@ -1,224 +1,120 @@ -#' Creates a `teal_modules` object. +#' Create `teal_module` and `teal_modules` objects. #' -#' @description `r lifecycle::badge("stable")` -#' This function collects a list of `teal_modules` and `teal_module` objects and returns a `teal_modules` object -#' containing the passed objects. +#' @description +#' `r lifecycle::badge("stable")` #' -#' This function dictates what modules are included in a `teal` application. The internal structure of `teal_modules` -#' shapes the navigation panel of a `teal` application. +#' Create a nested tab structure to embed modules in a `teal` application. #' -#' @param ... (`teal_module` or `teal_modules`) see [module()] and [modules()] for more details -#' @param label (`character(1)`) label of modules collection (default `"root"`). -#' If using the `label` argument then it must be explicitly named. -#' For example `modules("lab", ...)` should be converted to `modules(label = "lab", ...)` +#' @details +#' `module()` creates an instance of a `teal_module` that can be placed in a `teal` application. +#' `modules()` shapes the structure of a the application by organizing `teal_module` within the navigation panel. +#' It wraps `teal_module` and `teal_modules` objects in a `teal_modules` object, +#' which results in a nested structure corresponding to the nested tabs in the final application. #' -#' @export -#' -#' @return object of class \code{teal_modules}. Object contains following fields -#' - `label`: taken from the `label` argument -#' - `children`: a list containing objects passed in `...`. List elements are named after -#' their `label` attribute converted to a valid `shiny` id. -#' @examples -#' library(shiny) -#' -#' app <- init( -#' data = teal_data(iris = iris), -#' modules = modules( -#' label = "Modules", -#' modules( -#' label = "Module", -#' module( -#' label = "Inner module", -#' server = function(id, data) { -#' moduleServer( -#' id, -#' module = function(input, output, session) { -#' output$data <- renderDataTable(data[["iris"]]()) -#' } -#' ) -#' }, -#' ui = function(id) { -#' ns <- NS(id) -#' tagList(dataTableOutput(ns("data"))) -#' }, -#' datanames = "all" -#' ) -#' ), -#' module( -#' label = "Another module", -#' server = function(id) { -#' moduleServer( -#' id, -#' module = function(input, output, session) { -#' output$text <- renderText("Another module") -#' } -#' ) -#' }, -#' ui = function(id) { -#' ns <- NS(id) -#' tagList(textOutput(ns("text"))) -#' }, -#' datanames = NULL -#' ) -#' ) -#' ) -#' if (interactive()) { -#' shinyApp(app$ui, app$server) -#' } -modules <- function(..., label = "root") { - checkmate::assert_string(label) - submodules <- list(...) - if (any(vapply(submodules, is.character, FUN.VALUE = logical(1)))) { - stop( - "The only character argument to modules() must be 'label' and it must be named, ", - "change modules('lab', ...) to modules(label = 'lab', ...)" - ) - } - - checkmate::assert_list(submodules, min.len = 1, any.missing = FALSE, types = c("teal_module", "teal_modules")) - # name them so we can more easily access the children - # beware however that the label of the submodules should not be changed as it must be kept synced - labels <- vapply(submodules, function(submodule) submodule$label, character(1)) - names(submodules) <- make.unique(gsub("[^[:alnum:]]+", "_", labels), sep = "_") - structure( - list( - label = label, - children = submodules - ), - class = "teal_modules" - ) -} - -#' Append a `teal_module` to `children` of a `teal_modules` object -#' @keywords internal -#' @param modules `teal_modules` -#' @param module `teal_module` object to be appended onto the children of `modules` -#' @return `teal_modules` object with `module` appended -append_module <- function(modules, module) { - checkmate::assert_class(modules, "teal_modules") - checkmate::assert_class(module, "teal_module") - modules$children <- c(modules$children, list(module)) - labels <- vapply(modules$children, function(submodule) submodule$label, character(1)) - names(modules$children) <- make.unique(gsub("[^[:alnum:]]", "_", tolower(labels)), sep = "_") - modules -} - -#' Extract/Remove module(s) of specific class -#' -#' Given a `teal_module` or a `teal_modules`, return the elements of the structure according to `class`. -#' -#' @param modules `teal_modules` -#' @param class The class name of `teal_module` to be extracted or dropped. -#' @keywords internal -#' @return -#' For `extract_module`, a `teal_module` of class `class` or `teal_modules` containing modules of class `class`. -#' For `drop_module`, the opposite, which is all `teal_modules` of class other than `class`. -#' @rdname module_management -extract_module <- function(modules, class) { - if (inherits(modules, class)) { - modules - } else if (inherits(modules, "teal_module")) { - NULL - } else if (inherits(modules, "teal_modules")) { - Filter(function(x) length(x) > 0L, lapply(modules$children, extract_module, class)) - } -} - -#' @keywords internal -#' @return `teal_modules` -#' @rdname module_management -drop_module <- function(modules, class) { - if (inherits(modules, class)) { - NULL - } else if (inherits(modules, "teal_module")) { - modules - } else if (inherits(modules, "teal_modules")) { - do.call( - "modules", - c(Filter(function(x) length(x) > 0L, lapply(modules$children, drop_module, class)), label = modules$label) - ) - } -} - -#' Does the object make use of the `arg` +#' Note that for `modules()` `label` comes after `...`, so it must be passed as a named argument, +#' otherwise it will be captured by `...`. #' -#' @param modules (`teal_module` or `teal_modules`) object -#' @param arg (`character(1)`) names of the arguments to be checked against formals of `teal` modules. -#' @return `logical` whether the object makes use of `arg` -#' @rdname is_arg_used -#' @keywords internal -is_arg_used <- function(modules, arg) { - checkmate::assert_string(arg) - if (inherits(modules, "teal_modules")) { - any(unlist(lapply(modules$children, is_arg_used, arg))) - } else if (inherits(modules, "teal_module")) { - is_arg_used(modules$server, arg) || is_arg_used(modules$ui, arg) - } else if (is.function(modules)) { - isTRUE(arg %in% names(formals(modules))) - } else { - stop("is_arg_used function not implemented for this object") - } -} - - -#' Creates a `teal_module` object. +#' The labels `"global_filters"` and `"Report previewer"` are reserved +#' because they are used by the `mapping` argument of [teal_slices()] +#' and the report previewer module [reporter_previewer_module()], respectively. #' -#' @description `r lifecycle::badge("stable")` -#' This function embeds a `shiny` module inside a `teal` application. One `teal_module` maps to one `shiny` module. -#' -#' @param label (`character(1)`) Label shown in the navigation item for the module. Any label possible except -#' `"global_filters"` - read more in `mapping` argument of [teal::teal_slices]. +#' @param label (`character(1)`) Label shown in the navigation item for the module or module group. +#' For `modules()` defaults to `"root"`. See `Details`. #' @param server (`function`) `shiny` module with following arguments: -#' - `id` - teal will set proper shiny namespace for this module (see [shiny::moduleServer()]). +#' - `id` - `teal` will set proper `shiny` namespace for this module (see [shiny::moduleServer()]). #' - `input`, `output`, `session` - (not recommended) then [shiny::callModule()] will be used to call a module. #' - `data` (optional) module will receive a `teal_data` object, a list of reactive (filtered) data specified in #' the `filters` argument. -#' - `datasets` (optional) module will receive `FilteredData`. (See `[teal.slice::FilteredData]`). -#' - `reporter` (optional) module will receive `Reporter`. (See [teal.reporter::Reporter]). -# - `filter_panel_api` (optional) module will receive `FilterPanelAPI`. (See [teal.slice::FilterPanelAPI]). +#' - `datasets` (optional) module will receive `FilteredData`. (See [`teal.slice::FilteredData`]). +#' - `reporter` (optional) module will receive `Reporter`. (See [`teal.reporter::Reporter`]). +#' - `filter_panel_api` (optional) module will receive `FilterPanelAPI`. (See [`teal.slice::FilterPanelAPI`]). #' - `...` (optional) `server_args` elements will be passed to the module named argument or to the `...`. -#' @param ui (`function`) Shiny `ui` module function with following arguments: -#' - `id` - teal will set proper shiny namespace for this module. +#' @param ui (`function`) `shiny` UI module function with following arguments: +#' - `id` - `teal` will set proper `shiny` namespace for this module. #' - `...` (optional) `ui_args` elements will be passed to the module named argument or to the `...`. #' @param filters (`character`) Deprecated. Use `datanames` instead. #' @param datanames (`character`) A vector with `datanames` that are relevant for the item. The #' filter panel will automatically update the shown filters to include only #' filters in the listed datasets. `NULL` will hide the filter panel, -#' and the keyword `'all'` will show filters of all datasets. `datanames` also determines -#' a subset of datasets which are appended to the `data` argument in `server` function. -#' @param server_args (named `list`) with additional arguments passed on to the -#' `server` function. -#' @param ui_args (named `list`) with additional arguments passed on to the -#' `ui` function. +#' and the keyword `"all"` will show filters of all datasets. `datanames` also determines +#' a subset of datasets which are appended to the `data` argument in server function. +#' @param server_args (named `list`) with additional arguments passed on to the server function. +#' @param ui_args (named `list`) with additional arguments passed on to the UI function. +#' @param x (`teal_module` or `teal_modules`) Object to format/print. +#' @param indent (`integer(1)`) Indention level; each nested element is indented one level more. +#' @param ... +#' - For `modules()`: (`teal_module` or `teal_modules`) Objects to wrap into a tab. +#' - For `format()` and `print()`: Arguments passed to other methods. +#' +#' @return +#' `module()` returns an object of class `teal_module`. +#' +#' `modules()` returns a `teal_modules` object which contains following fields: +#' - `label`: taken from the `label` argument. +#' - `children`: a list containing objects passed in `...`. List elements are named after +#' their `label` attribute converted to a valid `shiny` id. +#' +#' @name teal_modules +#' @aliases teal_module #' -#' @return object of class `teal_module`. -#' @export #' @examples #' library(shiny) #' -#' app <- init( -#' data = teal_data(iris = iris), -#' modules = list( -#' module( -#' label = "Module", -#' server = function(id, data) { -#' moduleServer( -#' id, -#' module = function(input, output, session) { -#' output$data <- renderDataTable(data[["iris"]]()) -#' } -#' ) -#' }, -#' ui = function(id) { -#' ns <- NS(id) -#' tagList(dataTableOutput(ns("data"))) +#' module_1 <- module( +#' label = "a module", +#' server = function(id, data) { +#' moduleServer( +#' id, +#' module = function(input, output, session) { +#' output$data <- renderDataTable(data()[["iris"]]) #' } #' ) -#' ) +#' }, +#' ui = function(id) { +#' ns <- NS(id) +#' tagList(dataTableOutput(ns("data"))) +#' }, +#' datanames = "all" #' ) +#' +#' module_2 <- module( +#' label = "another module", +#' server = function(id) { +#' moduleServer( +#' id, +#' module = function(input, output, session) { +#' output$text <- renderText("Another Module") +#' } +#' ) +#' }, +#' ui = function(id) { +#' ns <- NS(id) +#' tagList(textOutput(ns("text"))) +#' }, +#' datanames = NULL +#' ) +#' +#' modules <- modules( +#' label = "modules", +#' modules( +#' label = "nested modules", +#' module_1 +#' ), +#' module_2 +#' ) +#' +#' app <- init( +#' data = teal_data(iris = iris), +#' modules = modules +#' ) +#' #' if (interactive()) { #' shinyApp(app$ui, app$server) #' } + +#' @rdname teal_modules +#' @export +#' module <- function(label = "module", server = function(id, ...) { moduleServer(id, function(input, output, session) {}) # nolint @@ -248,7 +144,7 @@ module <- function(label = "module", ) } - ## `server` + ## server checkmate::assert_function(server) server_formals <- names(formals(server)) if (!( @@ -257,8 +153,8 @@ module <- function(label = "module", )) { stop( "\nmodule() `server` argument requires a function with following arguments:", - "\n - id - teal will set proper shiny namespace for this module.", - "\n - input, output, session (not recommended) - then shiny::callModule will be used to call a module.", + "\n - id - `teal` will set proper `shiny` namespace for this module.", + "\n - input, output, session (not recommended) - then `shiny::callModule` will be used to call a module.", "\n\nFollowing arguments can be used optionaly:", "\n - `data` - module will receive list of reactive (filtered) data specified in the `filters` argument", "\n - `datasets` - module will receive `FilteredData`. See `help(teal.slice::FilteredData)`", @@ -270,20 +166,20 @@ module <- function(label = "module", if ("datasets" %in% server_formals) { warning( sprintf("Called from module(label = \"%s\", ...)\n ", label), - "`datasets` argument in the `server` is deprecated and will be removed in the next release. ", + "`datasets` argument in the server is deprecated and will be removed in the next release. ", "Please use `data` instead.", call. = FALSE ) } - ## `ui` + ## UI checkmate::assert_function(ui) ui_formals <- names(formals(ui)) if (!"id" %in% ui_formals) { stop( "\nmodule() `ui` argument requires a function with following arguments:", - "\n - id - teal will set proper shiny namespace for this module.", + "\n - id - `teal` will set proper `shiny` namespace for this module.", "\n\nFollowing arguments can be used optionally:", "\n - `...` ui_args elements will be passed to the module argument of the same name or to the `...`" ) @@ -291,8 +187,8 @@ module <- function(label = "module", if (any(c("data", "datasets") %in% ui_formals)) { stop( sprintf("Called from module(label = \"%s\", ...)\n ", label), - "`ui` with `data` or `datasets` argument is no longer accepted.\n ", - "If some `ui` inputs depend on data, please move the logic to your `server` instead.\n ", + "UI with `data` or `datasets` argument is no longer accepted.\n ", + "If some UI inputs depend on data, please move the logic to your server instead.\n ", "Possible solutions are renderUI() or updateXyzInput() functions." ) } @@ -320,9 +216,9 @@ module <- function(label = "module", srv_extra_args <- setdiff(names(server_args), server_formals) if (length(srv_extra_args) > 0 && !"..." %in% server_formals) { stop( - "\nFollowing `server_args` elements have no equivalent in the formals of the `server`:\n", + "\nFollowing `server_args` elements have no equivalent in the formals of the server:\n", paste(paste(" -", srv_extra_args), collapse = "\n"), - "\n\nUpdate the `server` arguments by including above or add `...`" + "\n\nUpdate the server arguments by including above or add `...`" ) } @@ -331,9 +227,9 @@ module <- function(label = "module", ui_extra_args <- setdiff(names(ui_args), ui_formals) if (length(ui_extra_args) > 0 && !"..." %in% ui_formals) { stop( - "\nFollowing `ui_args` elements have no equivalent in the formals of `ui`:\n", + "\nFollowing `ui_args` elements have no equivalent in the formals of UI:\n", paste(paste(" -", ui_extra_args), collapse = "\n"), - "\n\nUpdate the `ui` arguments by including above or add `...`" + "\n\nUpdate the UI arguments by including above or add `...`" ) } @@ -347,6 +243,144 @@ module <- function(label = "module", ) } +#' @rdname teal_modules +#' @export +#' +modules <- function(..., label = "root") { + checkmate::assert_string(label) + submodules <- list(...) + if (any(vapply(submodules, is.character, FUN.VALUE = logical(1)))) { + stop( + "The only character argument to modules() must be 'label' and it must be named, ", + "change modules('lab', ...) to modules(label = 'lab', ...)" + ) + } + + checkmate::assert_list(submodules, min.len = 1, any.missing = FALSE, types = c("teal_module", "teal_modules")) + # name them so we can more easily access the children + # beware however that the label of the submodules should not be changed as it must be kept synced + labels <- vapply(submodules, function(submodule) submodule$label, character(1)) + names(submodules) <- make.unique(gsub("[^[:alnum:]]+", "_", labels), sep = "_") + structure( + list( + label = label, + children = submodules + ), + class = "teal_modules" + ) +} + +# printing methods ---- + +#' @rdname teal_modules +#' @export +format.teal_module <- function(x, indent = 0, ...) { # nolint + paste0(paste(rep(" ", indent), collapse = ""), "+ ", x$label, "\n", collapse = "") +} + + +#' @rdname teal_modules +#' @export +print.teal_module <- function(x, ...) { + cat(format(x, ...)) + invisible(x) +} + + +#' @rdname teal_modules +#' @export +format.teal_modules <- function(x, indent = 0, ...) { # nolint + paste( + c( + paste0(rep(" ", indent), "+ ", x$label, "\n"), + unlist(lapply(x$children, format, indent = indent + 1, ...)) + ), + collapse = "" + ) +} + + +#' @rdname teal_modules +#' @export +print.teal_modules <- print.teal_module + + +# utilities ---- +## subset or modify modules ---- + +#' Append a `teal_module` to `children` of a `teal_modules` object +#' @keywords internal +#' @param modules (`teal_modules`) +#' @param module (`teal_module`) object to be appended onto the children of `modules` +#' @return A `teal_modules` object with `module` appended. +append_module <- function(modules, module) { + checkmate::assert_class(modules, "teal_modules") + checkmate::assert_class(module, "teal_module") + modules$children <- c(modules$children, list(module)) + labels <- vapply(modules$children, function(submodule) submodule$label, character(1)) + names(modules$children) <- make.unique(gsub("[^[:alnum:]]", "_", tolower(labels)), sep = "_") + modules +} + +#' Extract/Remove module(s) of specific class +#' +#' Given a `teal_module` or a `teal_modules`, return the elements of the structure according to `class`. +#' +#' @param modules (`teal_modules`) +#' @param class The class name of `teal_module` to be extracted or dropped. +#' @keywords internal +#' @return +#' - For `extract_module`, a `teal_module` of class `class` or `teal_modules` containing modules of class `class`. +#' - For `drop_module`, the opposite, which is all `teal_modules` of class other than `class`. +#' @rdname module_management +extract_module <- function(modules, class) { + if (inherits(modules, class)) { + modules + } else if (inherits(modules, "teal_module")) { + NULL + } else if (inherits(modules, "teal_modules")) { + Filter(function(x) length(x) > 0L, lapply(modules$children, extract_module, class)) + } +} + +#' @keywords internal +#' @return `teal_modules` +#' @rdname module_management +drop_module <- function(modules, class) { + if (inherits(modules, class)) { + NULL + } else if (inherits(modules, "teal_module")) { + modules + } else if (inherits(modules, "teal_modules")) { + do.call( + "modules", + c(Filter(function(x) length(x) > 0L, lapply(modules$children, drop_module, class)), label = modules$label) + ) + } +} + +## read modules ---- + +#' Does the object make use of the `arg` +#' +#' @param modules (`teal_module` or `teal_modules`) object +#' @param arg (`character(1)`) names of the arguments to be checked against formals of `teal` modules. +#' @return `logical` whether the object makes use of `arg`. +#' @rdname is_arg_used +#' @keywords internal +is_arg_used <- function(modules, arg) { + checkmate::assert_string(arg) + if (inherits(modules, "teal_modules")) { + any(unlist(lapply(modules$children, is_arg_used, arg))) + } else if (inherits(modules, "teal_module")) { + is_arg_used(modules$server, arg) || is_arg_used(modules$ui, arg) + } else if (is.function(modules)) { + isTRUE(arg %in% names(formals(modules))) + } else { + stop("is_arg_used function not implemented for this object") + } +} + #' Get module depth #' @@ -356,10 +390,11 @@ module <- function(label = "module", #' @inheritParams init #' @param depth optional, integer determining current depth level #' -#' @return depth level for given module -#' @keywords internal -#' +#' @return Depth level for given module. #' @examples +#' # use non-exported function from teal +#' modules_depth <- getFromNamespace("modules_depth", "teal") +#' #' mods <- modules( #' label = "d1", #' modules( @@ -372,7 +407,7 @@ module <- function(label = "module", #' ), #' module(label = "ccc") #' ) -#' stopifnot(teal:::modules_depth(mods) == 3L) +#' stopifnot(modules_depth(mods) == 3L) #' #' mods <- modules( #' label = "a", @@ -381,7 +416,8 @@ module <- function(label = "module", #' ), #' module(label = "b2") #' ) -#' stopifnot(teal:::modules_depth(mods) == 2L) +#' stopifnot(modules_depth(mods) == 2L) +#' @keywords internal modules_depth <- function(modules, depth = 0L) { checkmate::assert_multi_class(modules, c("teal_module", "teal_modules")) checkmate::assert_int(depth, lower = 0) @@ -392,7 +428,12 @@ modules_depth <- function(modules, depth = 0L) { } } - +#' Retrieve labels from `teal_modules` +#' +#' @param modules (`teal_modules`) +#' @return A `list` containing the labels of the modules. If the modules are nested, +#' the function returns a nested `list` of labels. +#' @keywords internal module_labels <- function(modules) { if (inherits(modules, "teal_modules")) { lapply(modules$children, module_labels) @@ -400,49 +441,3 @@ module_labels <- function(modules) { modules$label } } - -#' Converts `teal_modules` to a string -#' -#' @param x (`teal_modules`) to print -#' @param indent (`integer`) indent level; -#' each `submodule` is indented one level more -#' @param ... (optional) additional parameters to pass to recursive calls of `toString` -#' @return (`character`) -#' @export -#' @rdname modules -toString.teal_modules <- function(x, indent = 0, ...) { # nolint - # argument must be `x` to be consistent with base method - paste(c( - paste0(rep(" ", indent), "+ ", x$label), - unlist(lapply(x$children, toString, indent = indent + 1, ...)) - ), collapse = "\n") -} - -#' Converts `teal_module` to a string -#' -#' @inheritParams toString.teal_modules -#' @param x `teal_module` -#' @param ... ignored -#' @export -#' @rdname module -toString.teal_module <- function(x, indent = 0, ...) { # nolint - paste0(paste(rep(" ", indent), collapse = ""), "+ ", x$label, collapse = "") -} - -#' Prints `teal_modules` -#' @param x `teal_modules` -#' @param ... parameters passed to `toString` -#' @export -#' @rdname modules -print.teal_modules <- function(x, ...) { - s <- toString(x, ...) - cat(s) - return(invisible(s)) -} - -#' Prints `teal_module` -#' @param x `teal_module` -#' @param ... parameters passed to `toString` -#' @export -#' @rdname module -print.teal_module <- print.teal_modules diff --git a/R/modules_debugging.R b/R/modules_debugging.R deleted file mode 100644 index bb7167c5d3..0000000000 --- a/R/modules_debugging.R +++ /dev/null @@ -1,46 +0,0 @@ -# This file contains Shiny modules useful for debugging and developing teal. -# We do not export the functions in this file. They are for -# developers only and can be accessed via `:::`. - -#' Dummy module to show the filter calls generated by the right encoding panel -#' -#' -#' Please do not remove, this is useful for debugging teal without -#' dependencies and simplifies `\link[devtools]{load_all}` which otherwise fails -#' and avoids session restarts! -#' -#' @param label `character` label of module -#' @keywords internal -#' -#' @examples -#' app <- init( -#' data = teal_data(iris = iris, mtcars = mtcars), -#' modules = teal:::filter_calls_module(), -#' header = "Simple teal app" -#' ) -#' if (interactive()) { -#' shinyApp(app$ui, app$server) -#' } -filter_calls_module <- function(label = "Filter Calls Module") { # nolint - checkmate::assert_string(label) - - module( - label = label, - server = function(input, output, session, data) { - checkmate::assert_class(data, "reactive") - checkmate::assert_class(isolate(data()), "teal_data") - - output$filter_calls <- renderText({ - teal.data::get_code(data()) - }) - }, - ui = function(id, ...) { - ns <- NS(id) - div( - h2("The following filter calls are generated:"), - verbatimTextOutput(ns("filter_calls")) - ) - }, - datanames = "all" - ) -} diff --git a/R/reporter_previewer_module.R b/R/reporter_previewer_module.R index e24174f2d7..2ee7560684 100644 --- a/R/reporter_previewer_module.R +++ b/R/reporter_previewer_module.R @@ -1,20 +1,24 @@ #' Create a `teal` module for previewing a report #' #' @description `r lifecycle::badge("experimental")` +#' #' This function wraps [teal.reporter::reporter_previewer_ui()] and #' [teal.reporter::reporter_previewer_srv()] into a `teal_module` to be #' used in `teal` applications. #' -#' If you are creating a `teal` application using [teal::init()] then this -#' module will be added to your application automatically if any of your `teal modules` +#' If you are creating a `teal` application using [init()] then this +#' module will be added to your application automatically if any of your `teal_modules` #' support report generation. #' -#' @inheritParams module -#' @param server_args (`named list`)\cr +#' @inheritParams teal_modules +#' @param server_args (named `list`) #' Arguments passed to [teal.reporter::reporter_previewer_srv()]. -#' @return `teal_module` (extended with `teal_module_previewer` class) containing the `teal.reporter` previewer -#' functionality. +#' +#' @return +#' `teal_module` (extended with `teal_module_previewer` class) containing the `teal.reporter` previewer functionality. +#' #' @export +#' reporter_previewer_module <- function(label = "Report previewer", server_args = list()) { checkmate::assert_string(label) checkmate::assert_list(server_args, names = "named") diff --git a/R/show_rcode_modal.R b/R/show_rcode_modal.R index 4161a17a9a..9ef042ed99 100644 --- a/R/show_rcode_modal.R +++ b/R/show_rcode_modal.R @@ -1,17 +1,18 @@ -#' Show R Code Modal +#' Show `R` code modal #' -#' @export #' @description `r lifecycle::badge("stable")` -#' Use the [shiny::showModal()] function to show the R code inside. #' -#' @param title (`character(1)`)\cr -#' Title of the modal, displayed in the first comment of the R-code. -#' @param rcode (`character`)\cr -#' vector with R code to show inside the modal. -#' @param session (`ShinySession` optional)\cr -#' `shiny` Session object, if missing then [shiny::getDefaultReactiveDomain()] is used. +#' Use the [shiny::showModal()] function to show the `R` code inside. +#' +#' @param title (`character(1)`) +#' Title of the modal, displayed in the first comment of the `R` code. +#' @param rcode (`character`) +#' vector with `R` code to show inside the modal. +#' @param session (`ShinySession` optional) +#' `shiny` session object, if missing then [shiny::getDefaultReactiveDomain()] is used. #' #' @references [shiny::showModal()] +#' @export show_rcode_modal <- function(title = NULL, rcode, session = getDefaultReactiveDomain()) { rcode <- paste(rcode, collapse = "\n") @@ -33,6 +34,4 @@ show_rcode_modal <- function(title = NULL, rcode, session = getDefaultReactiveDo size = "l", easyClose = TRUE )) - - return(NULL) } diff --git a/R/tdata.R b/R/tdata.R index a996cc69e3..d079b57eb6 100644 --- a/R/tdata.R +++ b/R/tdata.R @@ -1,26 +1,26 @@ -#' Create a `tdata` Object +#' Create a `tdata` object #' #' @description `r lifecycle::badge("deprecated")` -#' Create a new object called `tdata` which contains `data`, a `reactive` list of data.frames +#' +#' Create a new object called `tdata` which contains `data`, a `reactive` list of `data.frames` #' (or `MultiAssayExperiment`), with attributes: -#' \itemize{ -#' \item{`code` (`reactive`) containing code used to generate the data} -#' \item{join_keys (`join_keys`) containing the relationships between the data} -#' \item{metadata (`named list`) containing any metadata associated with the data frames} -#' } +#' - `code` (`reactive`) containing code used to generate the data +#' - join_keys (`join_keys`) containing the relationships between the data +#' - metadata (named `list`) containing any metadata associated with the data frames +#' #' @name tdata -#' @param data A `named list` of `data.frames` (or `MultiAssayExperiment`) +#' @param data (named `list`) A list of `data.frame` or `MultiAssayExperiment` objects, #' which optionally can be `reactive`. #' Inside this object all of these items will be made `reactive`. -#' @param code A `character` (or `reactive` which evaluates to a `character`) containing +#' @param code (`character` or `reactive` which evaluates to a `character`) containing #' the code used to generate the data. This should be `reactive` if the code is changing #' during a reactive context (e.g. if filtering changes the code). Inside this #' object `code` will be made reactive -#' @param join_keys A `teal.data::join_keys` object containing relationships between the +#' @param join_keys (`teal.data::join_keys`) object containing relationships between the #' datasets. -#' @param metadata A `named list` each element contains a list of metadata about the named data.frame +#' @param metadata (named `list`) each element contains a list of metadata about the named `data.frame` #' Each element of these list should be atomic and length one. -#' @return A `tdata` object +#' @return A `tdata` object. #' #' @seealso `as_tdata` #' @@ -86,9 +86,10 @@ new_tdata <- function(data, code = "", join_keys = NULL, metadata = NULL) { } #' Function to convert a `tdata` object to an `environment` -#' Any `reactives` inside `tdata` are first evaluated -#' @param data a `tdata` object -#' @return an `environment` +#' +#' Any `reactive` expressions inside `tdata` are evaluated first. +#' @param data (`tdata`) object +#' @return An `environment`. #' @examples #' #' data <- new_tdata( @@ -107,7 +108,8 @@ tdata2env <- function(data) { # nolint #' Wrapper for `get_code.tdata` -#' This wrapper is to be used by downstream packages to extract the code of a `tdata` object +#' +#' This wrapper is to be used by downstream packages to extract the code of a `tdata` object. #' #' @param data (`tdata`) object #' @@ -119,18 +121,17 @@ get_code_tdata <- function(data) { } #' Extract `join_keys` from `tdata` -#' @param data A `tdata` object +#' @param data (`tdata`) object #' @param ... Additional arguments (not used) #' @export join_keys.tdata <- function(data, ...) { attr(data, "join_keys") } - #' Function to get metadata from a `tdata` object -#' @param data `tdata` - object to extract the data from -#' @param dataname `character(1)` the dataset name whose metadata is requested -#' @return Either list of metadata or NULL if no metadata +#' @param data (`tdata` - object) to extract the data from +#' @param dataname (`character(1)`) the dataset name whose metadata is requested +#' @return Either list of metadata or NULL if no metadata. #' @export get_metadata <- function(data, dataname) { checkmate::assert_string(dataname) @@ -154,7 +155,7 @@ get_metadata.default <- function(data, dataname) { } -#' Downgrade `teal_data` objects in modules for compatibility. +#' Downgrade `teal_data` objects in modules for compatibility #' #' Convert `teal_data` to `tdata` in `teal` modules. #' diff --git a/R/teal.R b/R/teal.R index ddd5072512..c44a795e7f 100644 --- a/R/teal.R +++ b/R/teal.R @@ -1,10 +1,10 @@ -#' teal: Interactive Exploration of Clinical Trials Data +#' `teal`: Interactive exploration of clinical trials data #' -#' The teal package provides a shiny based framework for creating an +#' The `teal` package provides a `shiny` based framework for creating an #' interactive data analysis environment. #' -#' To learn mode about the package either read the project website at -#' \url{Project Website} or read the \code{\link{init}} manual pages. +#' To learn mode about the package, visit the [project website](https://insightsengineering.github.io/teal/latest-tag/) +#' or read the [init()] manual page. #' #' @keywords internal "_PACKAGE" diff --git a/R/teal_data_module-eval_code.R b/R/teal_data_module-eval_code.R index 4f80f49010..20e270b605 100644 --- a/R/teal_data_module-eval_code.R +++ b/R/teal_data_module-eval_code.R @@ -1,6 +1,6 @@ setOldClass("teal_data_module") -#' Evaluate Code on `teal_data_module` +#' Evaluate code on `teal_data_module` #' #' @details #' `eval_code` evaluates given code in the environment of the `teal_data` object created by the `teal_data_module`. @@ -13,15 +13,7 @@ setOldClass("teal_data_module") #' `eval_code` returns a `teal_data_module` object with a delayed evaluation of `code` when the module is run. #' #' @examples -#' tdm <- teal_data_module( -#' ui = function(id) div(id = shiny::NS(id)("div_id")), -#' server = function(id) { -#' shiny::moduleServer(id, function(input, output, session) { -#' shiny::reactive(teal_data(IRIS = iris)) -#' }) -#' } -#' ) -#' eval_code(tdm, "IRIS <- subset(IRIS, Species == 'virginica')") +#' eval_code(tdm, "dataset1 <- subset(dataset1, Species == 'virginica')") #' #' @include teal_data_module.R #' @name eval_code diff --git a/R/teal_data_module-within.R b/R/teal_data_module-within.R index e8ab3020b3..e4c56b4fcc 100644 --- a/R/teal_data_module-within.R +++ b/R/teal_data_module-within.R @@ -1,7 +1,9 @@ -#' Evaluate Expression on `teal_data_module` +#' Evaluate expression on `teal_data_module` #' #' @details #' `within` is a convenience function for evaluating inline code inside the environment of a `teal_data_module`. +#' It accepts only inline expressions (both simple and compound) and allows for injecting values into `expr` through +#' the `...` argument: as `name:value` pairs are passed to `...`, `name` in `expr` will be replaced with `value.` #' #' @param data (`teal_data_module`) object #' @param expr (`expression`) to evaluate. Must be inline code. See @@ -11,16 +13,11 @@ #' `within` returns a `teal_data_module` object with a delayed evaluation of `expr` when the module is run. #' #' @examples -#' tdm <- teal_data_module( -#' ui = function(id) div(id = shiny::NS(id)("div_id")), -#' server = function(id) { -#' shiny::moduleServer(id, function(input, output, session) { -#' shiny::reactive(teal_data(IRIS = iris)) -#' }) -#' } -#' ) -#' within(tdm, IRIS <- subset(IRIS, Species == "virginica")) +#' within(tdm, dataset1 <- subset(dataset1, Species == "virginica")) #' +#' # use additional parameter for expression value substitution. +#' valid_species <- "versicolor" +#' within(tdm, dataset1 <- subset(dataset1, Species %in% species), species = valid_species) #' @include teal_data_module.R #' @name within #' @rdname teal_data_module diff --git a/R/teal_data_module.R b/R/teal_data_module.R index 6b5a4eb5b1..70258cda07 100644 --- a/R/teal_data_module.R +++ b/R/teal_data_module.R @@ -1,4 +1,4 @@ -#' Data Module for `teal` Applications +#' Data module for `teal` applications #' #' @description #' `r lifecycle::badge("experimental")` @@ -9,22 +9,24 @@ #' `teal_data_module` creates a `shiny` module to supply or modify data in a `teal` application. #' The module allows for running data pre-processing code (creation _and_ some modification) after the app starts. #' The body of the server function will be run in the app rather than in the global environment. -#' This means it will be run every time the app starts, so use sparingly.\cr +#' This means it will be run every time the app starts, so use sparingly. +#' #' Pass this module instead of a `teal_data` object in a call to [init()]. -#' Note that the server function must always return a `teal_data` object wrapped in a reactive expression.\cr +#' Note that the server function must always return a `teal_data` object wrapped in a reactive expression. +#' #' See vignette `vignette("data-as-shiny-module", package = "teal")` for more details. #' -#' @param ui (`function(id)`)\cr -#' `shiny` module `ui` function; must only take `id` argument -#' @param server (`function(id)`)\cr -#' `shiny` module `ui` function; must only take `id` argument; +#' @param ui (`function(id)`) +#' `shiny` module UI function; must only take `id` argument +#' @param server (`function(id)`) +#' `shiny` module server function; must only take `id` argument; #' must return reactive expression containing `teal_data` object #' #' @return #' `teal_data_module` returns an object of class `teal_data_module`. #' #' @examples -#' data <- teal_data_module( +#' tdm <- teal_data_module( #' ui = function(id) { #' ns <- NS(id) #' actionButton(ns("submit"), label = "Load data") @@ -48,9 +50,7 @@ #' ) #' #' @name teal_data_module -#' @rdname teal_data_module -#' -#' @seealso [`teal_data-class`], [`base::within()`], [`teal.code::within.qenv()`] +#' @seealso [`teal.data::teal_data-class`], [teal.code::qenv()] #' #' @export teal_data_module <- function(ui, server) { diff --git a/R/teal_reporter.R b/R/teal_reporter.R index 6835822fa6..93ca17420f 100644 --- a/R/teal_reporter.R +++ b/R/teal_reporter.R @@ -1,7 +1,7 @@ #' @title `TealReportCard` #' @description `r lifecycle::badge("experimental")` -#' A child of [`ReportCard`] that is used for teal specific applications. -#' In addition to the parent methods, it supports rendering teal specific elements such as +#' Child class of [`ReportCard`] that is used for `teal` specific applications. +#' In addition to the parent methods, it supports rendering `teal` specific elements such as #' the source code, the encodings panel content and the filter panel content as part of the #' meta data. #' @export @@ -13,9 +13,9 @@ TealReportCard <- R6::R6Class( # nolint: object_name_linter. #' @description Appends the source code to the `content` meta data of this `TealReportCard`. #' #' @param src (`character(1)`) code as text. - #' @param ... any `rmarkdown` R chunk parameter and its value. + #' @param ... any `rmarkdown` `R` chunk parameter and its value. #' But `eval` parameter is always set to `FALSE`. - #' @return invisibly self + #' @return Object of class `TealReportCard`, invisibly. #' @examples #' card <- TealReportCard$new()$append_src( #' "plot(iris)" @@ -37,7 +37,7 @@ TealReportCard <- R6::R6Class( # nolint: object_name_linter. #' If the filter state list is empty, nothing is appended to the `content`. #' #' @param fs (`teal_slices`) object returned from [teal_slices()] function. - #' @return invisibly self + #' @return `self`, invisibly. append_fs = function(fs) { checkmate::assert_class(fs, "teal_slices") self$append_text("Filter State", "header3") @@ -46,8 +46,8 @@ TealReportCard <- R6::R6Class( # nolint: object_name_linter. }, #' @description Appends the encodings list to the `content` and `metadata` of this `TealReportCard`. #' - #' @param encodings (`list`) list of encodings selections of the teal app. - #' @return invisibly self + #' @param encodings (`list`) list of encodings selections of the `teal` app. + #' @return `self`, invisibly. #' @examples #' card <- TealReportCard$new()$append_encodings(list(variable1 = "X")) #' card$get_content()[[1]]$get_content() @@ -84,9 +84,7 @@ TealSlicesBlock <- R6::R6Class( # nolint: object_name_linter. #' @param content (`teal_slices`) object returned from [teal_slices()] function. #' @param style (`character(1)`) string specifying style to apply. #' - #' @return `TealSlicesBlock` - #' @examples - #' block <- teal:::TealSlicesBlock$new() + #' @return Object of class `TealSlicesBlock`, invisibly. #' initialize = function(content = teal_slices(), style = "verbatim") { self$set_content(content) @@ -102,7 +100,7 @@ TealSlicesBlock <- R6::R6Class( # nolint: object_name_linter. #' #' #' @param content (`teal_slices`) object returned from [teal_slices()] function. - #' @return invisibly self + #' @return `self`, invisibly. set_content = function(content) { checkmate::assert_class(content, "teal_slices") if (length(content) != 0) { @@ -141,9 +139,9 @@ TealSlicesBlock <- R6::R6Class( # nolint: object_name_linter. invisible(self) }, #' @description Create the `RcodeBlock` from a list. - #' @param x `named list` with two fields `c("text", "params")`. + #' @param x (named `list`) with two fields `c("text", "params")`. #' Use the `get_available_params` method to get all possible parameters. - #' @return invisibly self + #' @return `self`, invisibly. from_list = function(x) { checkmate::assert_list(x) checkmate::assert_names(names(x), must.include = c("teal_slices")) @@ -151,7 +149,7 @@ TealSlicesBlock <- R6::R6Class( # nolint: object_name_linter. invisible(self) }, #' @description Convert the `RcodeBlock` to a list. - #' @return `named list` with a text and `params`. + #' @return named `list` with a text and `params`. to_list = function() { list(teal_slices = private$teal_slices) diff --git a/R/teal_slices-store.R b/R/teal_slices-store.R index a426ccf99c..c9fcc6bd6f 100644 --- a/R/teal_slices-store.R +++ b/R/teal_slices-store.R @@ -1,35 +1,43 @@ -#' Store teal_slices object to a file +#' Store and restore `teal_slices` object #' -#' This function takes a `teal_slices` object and saves it to a file in `JSON` format. -#' The `teal_slices` object contains information about filter states and can be used to -#' create, modify, and delete filter states. The saved file can be later loaded using -#' the `slices_restore` function. +#' Functions that write a `teal_slices` object to a file in the `JSON` format, +#' and also restore the object from disk. #' -#' @param tss (`teal_slices`) object to be stored. -#' @param file (`character(1)`) The file path where `teal_slices` object will be saved. -#' The file extension should be `".json"`. +#' Date and date time objects are stored in the following formats: #' -#' @details `Date` class is stored in `"ISO8601"` format (`YYYY-MM-DD`). `POSIX*t` classes are converted to a -#' character by using `format.POSIX*t(usetz = TRUE, tz = "UTC")` (`YYYY-MM-DD {N}{N}:{N}{N}:{N}{N} UTC`, where -#' `{N} = [0-9]` is a number and `UTC` is `Coordinated Universal Time` timezone short-code). -#' This format is assumed during `slices_restore`. All `POSIX*t` objects in `selected` or `choices` fields of -#' `teal_slice` objects are always printed in `UTC` timezone as well. +#' - `Date` class is converted to the `"ISO8601"` standard (`YYYY-MM-DD`). +#' - `POSIX*t` classes are converted to character by using +#' `format.POSIX*t(usetz = TRUE, tz = "UTC")` (`YYYY-MM-DD HH:MM:SS UTC`, where +#' `UTC` is the `Coordinated Universal Time` timezone short-code). #' -#' @return `NULL`, invisibly. +#' This format is assumed during `slices_restore`. All `POSIX*t` objects in +#' `selected` or `choices` fields of `teal_slice` objects are always printed in +#' `UTC` timezone as well. #' -#' @keywords internal +#' @param tss (`teal_slices`) object to be stored. +#' @param file (`character(1)`) file path where `teal_slices` object will be +#' saved and restored. The file extension should be `".json"`. +#' +#' @return `slices_store` returns `NULL`, invisibly. +#' +#' @seealso [teal_slices()] #' #' @examples +#' # use non-exported function from teal +#' slices_store <- getFromNamespace("slices_store", "teal") +#' #' # Create a teal_slices object #' tss <- teal_slices( #' teal_slice(dataname = "data", varname = "var"), #' teal_slice(dataname = "data", expr = "x > 0", id = "positive_x", title = "Positive x") #' ) #' -#' if (interactive()) { -#' # Store the teal_slices object to a file -#' slices_store(tss, "path/to/file.json") -#' } +#' slices_path <- tempfile(pattern = "teal_slices", fileext = ".json") +#' print(slices_path) +#' +#' # Store the teal_slices object to a file +#' slices_store(tss, slices_path) +#' @keywords internal #' slices_store <- function(tss, file) { checkmate::assert_class(tss, "teal_slices") @@ -38,24 +46,17 @@ slices_store <- function(tss, file) { cat(format(tss, trim_lines = FALSE), "\n", file = file) } -#' Restore teal_slices object from a file -#' -#' This function takes a file path to a `JSON` file containing a `teal_slices` object -#' and restores it to its original form. The restored `teal_slices` object can be used -#' to access filter states and their corresponding attributes. +#' @rdname slices_store +#' @return `slices_restore` returns a `teal_slices` object restored from the file. +#' @examples #' -#' @param file Path to file where `teal_slices` is stored. Must have a `.json` extension and read access. +#' # use non-exported function from teal +#' slices_restore <- getFromNamespace("slices_restore", "teal") #' -#' @return A `teal_slices` object restored from the file. +#' # Restore a teal_slices object from a file +#' tss_restored <- slices_restore(slices_path) #' #' @keywords internal -#' -#' @examples -#' if (interactive()) { -#' # Restore a teal_slices object from a file -#' tss_restored <- slices_restore("path/to/file.json") -#' } -#' slices_restore <- function(file) { checkmate::assert_file_exists(file, access = "r", extension = "json") diff --git a/R/teal_slices.R b/R/teal_slices.R index 077d94d8c8..accd5b4d26 100644 --- a/R/teal_slices.R +++ b/R/teal_slices.R @@ -1,4 +1,4 @@ -#' Filter settings for teal applications +#' Filter settings for `teal` applications #' #' Specify initial filter states and filtering settings for a `teal` app. #' @@ -9,21 +9,23 @@ #' #' @inheritParams teal.slice::teal_slices #' -#' @param module_specific optional (`logical(1)`)\cr +#' @param module_specific optional (`logical(1)`) #' - `FALSE` (default) when one filter panel applied to all modules. #' All filters will be shared by all modules. #' - `TRUE` when filter panel module-specific. #' Modules can have different set of filters specified - see `mapping` argument. -#' @param mapping `r lifecycle::badge("experimental")` _This is a new feature. Do kindly share your opinions.\cr_ -#' (`named list`)\cr -#' Specifies which filters will be active in which modules on app start. -#' Elements should contain character vector of `teal_slice` `id`s (see [teal.slice::teal_slice()]). +#' @param mapping `r lifecycle::badge("experimental")` +#' _This is a new feature. Do kindly share your opinions on +#' [`teal`'s GitHub repository](https://github.com/insightsengineering/teal/)._ +#' +#' (named `list`) specifies which filters will be active in which modules on app start. +#' Elements should contain character vector of `teal_slice` `id`s (see [`teal.slice::teal_slice`]). #' Names of the list should correspond to `teal_module` `label` set in [module()] function. -#' `id`s listed under `"global_filters` will be active in all modules. -#' If missing, all filters will be applied to all modules. -#' If empty list, all filters will be available to all modules but will start inactive. -#' If `module_specific` is `FALSE`, only `global_filters` will be active on start. -#' @param app_id (`character(1)`)\cr +#' - `id`s listed under `"global_filters` will be active in all modules. +#' - If missing, all filters will be applied to all modules. +#' - If empty list, all filters will be available to all modules but will start inactive. +#' - If `module_specific` is `FALSE`, only `global_filters` will be active on start. +#' @param app_id (`character(1)`) #' For internal use only, do not set manually. #' Added by `init` so that a `teal_slices` can be matched to the app in which it was used. #' Used for verifying snapshots uploaded from file. See `snapshot`. @@ -33,16 +35,16 @@ #' @return #' A `teal_slices` object. #' -#' @seealso [`teal.slice::teal_slices`], [`teal.slice::teal_slice`], [`slices_store`] +#' @seealso [`teal.slice::teal_slices`], [`teal.slice::teal_slice`], [slices_store()] #' #' @examples #' filter <- teal_slices( -#' teal.slice::teal_slice(dataname = "iris", varname = "Species", id = "species"), -#' teal.slice::teal_slice(dataname = "iris", varname = "Sepal.Length", id = "sepal_length"), -#' teal.slice::teal_slice( +#' teal_slice(dataname = "iris", varname = "Species", id = "species"), +#' teal_slice(dataname = "iris", varname = "Sepal.Length", id = "sepal_length"), +#' teal_slice( #' dataname = "iris", id = "long_petals", title = "Long petals", expr = "Petal.Length > 5" #' ), -#' teal.slice::teal_slice(dataname = "mtcars", varname = "mpg", id = "mtcars_mpg"), +#' teal_slice(dataname = "mtcars", varname = "mpg", id = "mtcars_mpg"), #' mapping = list( #' module1 = c("species", "sepal_length"), #' module2 = c("mtcars_mpg"), @@ -50,8 +52,8 @@ #' ) #' ) #' -#' app <- teal::init( -#' data = list(iris = iris, mtcars = mtcars), +#' app <- init( +#' data = teal_data(iris = iris, mtcars = mtcars), #' modules = list( #' module("module1"), #' module("module2") diff --git a/R/utils.R b/R/utils.R index 63434747c7..673383d57f 100644 --- a/R/utils.R +++ b/R/utils.R @@ -1,26 +1,27 @@ -#' Get Client Timezone +#' Get client timezone #' -#' Local timezone in the browser may differ from the system timezone from the server. -#' This script can be run to register a shiny input which contains information about -#' the timezone in the browser. +#' User timezone in the browser may be different to the one on the server. +#' This script can be run to register a `shiny` input which contains information about the timezone in the browser. #' -#' @param ns (`function`) namespace function passed from the `session` object in the -#' Shiny server. For Shiny modules this will allow for proper name spacing of the -#' registered input. +#' @param ns (`function`) namespace function passed from the `session` object in the `shiny` server. +#' For `shiny` modules this will allow for proper name spacing of the registered input. #' -#' @return (`Shiny`) input variable accessible with `input$tz` which is a (`character`) +#' @return (`shiny`) input variable accessible with `input$tz` which is a (`character`) #' string containing the timezone of the browser/client. +#' #' @keywords internal +#' get_client_timezone <- function(ns) { script <- sprintf( "Shiny.setInputValue(`%s`, Intl.DateTimeFormat().resolvedOptions().timeZone)", ns("timezone") ) shinyjs::runjs(script) # function does not return anything - return(invisible(NULL)) + invisible(NULL) } #' Resolve the expected bootstrap theme +#' @noRd #' @keywords internal get_teal_bs_theme <- function() { bs_theme <- getOption("teal.bs_theme") @@ -34,6 +35,9 @@ get_teal_bs_theme <- function() { } } +#' Return parentnames along with datanames. +#' @noRd +#' @keywords internal include_parent_datanames <- function(dataname, join_keys) { parents <- character(0) for (i in dataname) { @@ -44,17 +48,16 @@ include_parent_datanames <- function(dataname, join_keys) { } } - return(unique(c(parents, dataname))) + unique(c(parents, dataname)) } - - #' Create a `FilteredData` #' -#' Create a `FilteredData` object from a `teal_data` object +#' Create a `FilteredData` object from a `teal_data` object. +#' #' @param x (`teal_data`) object #' @param datanames (`character`) vector of data set names to include; must be subset of `datanames(x)` -#' @return (`FilteredData`) object +#' @return A `FilteredData` object. #' @keywords internal teal_data_to_filtered_data <- function(x, datanames = teal_data_datanames(x)) { checkmate::assert_class(x, "teal_data") @@ -70,7 +73,7 @@ teal_data_to_filtered_data <- function(x, datanames = teal_data_datanames(x)) { ans } -#' Template Function for `TealReportCard` Creation and Customization +#' Template function for `TealReportCard` creation and customization #' #' This function generates a report card with a title, #' an optional description, and the option to append the filter state list. @@ -82,7 +85,7 @@ teal_data_to_filtered_data <- function(x, datanames = teal_data_datanames(x)) { #' @param filter_panel_api (`FilterPanelAPI`) object with API that allows the generation #' of the filter state in the report #' -#' @return (`TealReportCard`) populated with a title, description and filter state +#' @return (`TealReportCard`) populated with a title, description and filter state. #' #' @export report_card_template <- function(title, label, description = NULL, with_filter, filter_panel_api) { @@ -100,6 +103,7 @@ report_card_template <- function(title, label, description = NULL, with_filter, if (with_filter) card$append_fs(filter_panel_api$get_filter_state()) card } + #' Resolve `datanames` for the modules #' #' Modifies `module$datanames` to include names of the parent dataset (taken from `join_keys`). @@ -107,7 +111,7 @@ report_card_template <- function(title, label, description = NULL, with_filter, #' @param modules (`teal_modules`) object #' @param datanames (`character`) names of datasets available in the `data` object #' @param join_keys (`join_keys`) object -#' @return `teal_modules` with resolved `datanames` +#' @return `teal_modules` with resolved `datanames`. #' @keywords internal resolve_modules_datanames <- function(modules, datanames, join_keys) { if (inherits(modules, "teal_modules")) { @@ -182,7 +186,7 @@ check_modules_datanames <- function(modules, datanames) { #' Check `datanames` in filters #' #' This function checks whether `datanames` in filters correspond to those in `data`, -#' returning character vector with error messages or TRUE if all checks pass. +#' returning character vector with error messages or `TRUE` if all checks pass. #' #' @param filters (`teal_slices`) object #' @param datanames (`character`) names of datasets available in the `data` object @@ -255,13 +259,15 @@ validate_app_title_tag <- function(shiny_tag) { #' #' A helper function to create the browser title along with a logo. #' -#' @param title (`character`) The browser title for the teal app +#' @param title (`character`) The browser title for the `teal` app. #' @param favicon (`character`) The path for the icon for the title. -#' The image/icon path can be remote or the static path accessible by shiny, like the `www/` +#' The image/icon path can be remote or the static path accessible by `shiny`, like the `www/` #' -#' @return A `shiny.tag` containing the element that adds the title and logo to the shiny app +#' @return A `shiny.tag` containing the element that adds the title and logo to the `shiny` app. #' @export -build_app_title <- function(title = "teal app", favicon = "https://raw.githubusercontent.com/insightsengineering/hex-stickers/main/PNG/nest.png") { # nolint +build_app_title <- function( + title = "teal app", + favicon = "https://raw.githubusercontent.com/insightsengineering/hex-stickers/main/PNG/nest.png") { checkmate::assert_string(title, null.ok = TRUE) checkmate::assert_string(favicon, null.ok = TRUE) tags$head( @@ -282,8 +288,8 @@ build_app_title <- function(title = "teal app", favicon = "https://raw.githubuse #' App ID is a hash of the app's data and modules. #' See "transferring snapshots" section in ?snapshot. #' -#' @param data `teal_data` or `teal_data_module` as accepted by `init` -#' @param modules `teal_modules` object as accepted by `init` +#' @param data (`teal_data` or `teal_data_module`) as accepted by `init` +#' @param modules (`teal_modules`) object as accepted by `init` #' #' @return A single character string. #' @@ -292,11 +298,25 @@ create_app_id <- function(data, modules) { checkmate::assert_multi_class(data, c("teal_data", "teal_data_module")) checkmate::assert_class(modules, "teal_modules") - hashables <- c(data, modules) - hashables$data <- if (inherits(hashables$data, "teal_data")) { - as.list(hashables$data@env) + data <- if (inherits(data, "teal_data")) { + as.list(data@env) } else if (inherits(data, "teal_data_module")) { - body(data$server) + deparse1(body(data$server)) + } + modules <- lapply(modules, defunction) + + rlang::hash(list(data = data, modules = modules)) +} + +#' Go through list and extract bodies of encountered functions as string, recursively. +#' @keywords internal +#' @noRd +defunction <- function(x) { + if (is.list(x)) { + lapply(x, defunction) + } else if (is.function(x)) { + deparse1(body(x)) + } else { + x } - rlang::hash(hashables) } diff --git a/R/validate_inputs.R b/R/validate_inputs.R index 8377d2018a..b11073af86 100644 --- a/R/validate_inputs.R +++ b/R/validate_inputs.R @@ -24,7 +24,7 @@ #' @param ... either any number of `InputValidator` objects #' or an optionally named, possibly nested `list` of `InputValidator` #' objects, see `Details` -#' @param header `character(1)` generic validation message; set to NULL to omit +#' @param header (`character(1)`) generic validation message; set to NULL to omit #' #' @return #' Returns NULL if the final validation call passes and a `shiny.silent.error` if it fails. @@ -116,6 +116,7 @@ validate_inputs <- function(..., header = "Some inputs require attention") { ### internal functions +#' @noRd #' @keywords internal # recursive object type test # returns logical of length 1 @@ -123,6 +124,7 @@ is_validators <- function(x) { all(if (is.list(x)) unlist(lapply(x, is_validators)) else inherits(x, "InputValidator")) } +#' @noRd #' @keywords internal # test if an InputValidator object is enabled # returns logical of length 1 @@ -131,9 +133,10 @@ validator_enabled <- function(x) { x$.__enclos_env__$private$enabled } +#' Recursively extract messages from validator list +#' @return A character vector or a list of character vectors, possibly nested and named. +#' @noRd #' @keywords internal -# recursively extract messages from validator list -# returns character vector or a list of character vectors, possibly nested and named extract_validator <- function(iv, header) { if (inherits(iv, "InputValidator")) { add_header(gather_messages(iv), header) @@ -143,9 +146,10 @@ extract_validator <- function(iv, header) { } } +#' Collate failing messages from validator. +#' @return `list` +#' @noRd #' @keywords internal -# collate failing messages from validator -# returns list gather_messages <- function(iv) { if (validator_enabled(iv)) { status <- iv$validate() @@ -157,8 +161,9 @@ gather_messages <- function(iv) { } } +#' Add optional header to failing messages +#' @noRd #' @keywords internal -# add optional header to failing messages add_header <- function(messages, header = "") { ans <- unlist(messages) if (length(ans) != 0L && isTRUE(nchar(header) > 0L)) { @@ -167,8 +172,9 @@ add_header <- function(messages, header = "") { ans } +#' Recursively check if the object contains a named list +#' @noRd #' @keywords internal -# recursively check if the object contains a named list any_names <- function(x) { any( if (is.list(x)) { diff --git a/R/validations.R b/R/validations.R index 6a85aecea6..5be1d853c1 100644 --- a/R/validations.R +++ b/R/validations.R @@ -1,14 +1,14 @@ #' Validate that dataset has a minimum number of observations #' -#' @description `r lifecycle::badge("stable")` -#' @param x a data.frame -#' @param min_nrow minimum number of rows in \code{x} -#' @param complete \code{logical} default \code{FALSE} when set to \code{TRUE} then complete cases are checked. -#' @param allow_inf \code{logical} default \code{TRUE} when set to \code{FALSE} then error thrown if any values are -#' infinite. -#' @param msg (`character(1)`) additional message to display alongside the default message. +#' `r lifecycle::badge("stable")` #' -#' @details This function is a wrapper for `shiny::validate`. +#' This function is a wrapper for `shiny::validate`. +#' +#' @param x (`data.frame`) +#' @param min_nrow (`numeric(1)`) Minimum allowed number of rows in `x`. +#' @param complete (`logical(1)`) Flag specifying whether to check only complete cases. Defaults to `FALSE`. +#' @param allow_inf (`logical(1)`) Flag specifying whether to allow infinite values. Defaults to `TRUE`. +#' @param msg (`character(1)`) Additional message to display alongside the default message. #' #' @export #' @@ -23,15 +23,15 @@ #' #' server <- function(input, output) { #' output$plot <- renderPlot({ -#' df <- iris[iris$Sepal.Length <= input$len, ] +#' iris_df <- iris[iris$Sepal.Length <= input$len, ] #' validate_has_data( -#' iris_f, +#' iris_df, #' min_nrow = 10, #' complete = FALSE, #' msg = "Please adjust Max Length of Sepal" #' ) #' -#' hist(iris_f$Sepal.Length, breaks = 5) +#' hist(iris_df$Sepal.Length, breaks = 5) #' }) #' } #' if (interactive()) { @@ -73,11 +73,12 @@ validate_has_data <- function(x, #' Validate that dataset has unique rows for key variables #' -#' @description `r lifecycle::badge("stable")` -#' @param x a data.frame -#' @param key a vector of ID variables from \code{x} that identify unique records +#' `r lifecycle::badge("stable")` #' -#' @details This function is a wrapper for `shiny::validate`. +#' This function is a wrapper for `shiny::validate`. +#' +#' @param x (`data.frame`) +#' @param key (`character`) Vector of ID variables from `x` that identify unique records. #' #' @export #' @@ -111,12 +112,13 @@ validate_one_row_per_id <- function(x, key = c("USUBJID", "STUDYID")) { #' Validates that vector includes all expected values #' -#' @description `r lifecycle::badge("stable")` -#' @param x values to test. All must be in \code{choices} -#' @param choices a vector to test for values of \code{x} -#' @param msg warning message to display +#' `r lifecycle::badge("stable")` +#' +#' This function is a wrapper for `shiny::validate`. #' -#' @details This function is a wrapper for `shiny::validate`. +#' @param x Vector of values to test. +#' @param choices Vector to test against. +#' @param msg (`character(1)`) Error message to display if some elements of `x` are not elements of `choices`. #' #' @export #' @@ -148,12 +150,13 @@ validate_in <- function(x, choices, msg) { #' Validates that vector has length greater than 0 #' -#' @description `r lifecycle::badge("stable")` +#' `r lifecycle::badge("stable")` +#' +#' This function is a wrapper for `shiny::validate`. +#' #' @param x vector #' @param msg message to display #' -#' @details This function is a wrapper for `shiny::validate`. -#' #' @export #' #' @examples @@ -194,12 +197,13 @@ validate_has_elements <- function(x, msg) { #' Validates no intersection between two vectors #' -#' @description `r lifecycle::badge("stable")` +#' `r lifecycle::badge("stable")` +#' +#' This function is a wrapper for `shiny::validate`. +#' #' @param x vector #' @param y vector -#' @param msg message to display if \code{x} and \code{y} intersect -#' -#' @details This function is a wrapper for `shiny::validate`. +#' @param msg (`character(1)`) message to display if `x` and `y` intersect #' #' @export #' @@ -247,12 +251,13 @@ validate_no_intersection <- function(x, y, msg) { #' Validates that dataset contains specific variable #' -#' @description `r lifecycle::badge("stable")` -#' @param data a data.frame -#' @param varname name of variable in \code{data} -#' @param msg message to display if \code{data} does not include \code{varname} +#' `r lifecycle::badge("stable")` +#' +#' This function is a wrapper for `shiny::validate`. #' -#' @details This function is a wrapper for `shiny::validate`. +#' @param data (`data.frame`) +#' @param varname (`character(1)`) name of variable to check for in `data` +#' @param msg (`character(1)`) message to display if `data` does not include `varname` #' #' @export #' @@ -299,18 +304,19 @@ validate_has_variable <- function(data, varname, msg) { #' Validate that variables has expected number of levels #' -#' @description `r lifecycle::badge("stable")` -#' @param x variable name. If \code{x} is not a factor, the unique values +#' `r lifecycle::badge("stable")` +#' +#' If the number of levels of `x` is less than `min_levels` +#' or greater than `max_levels` the validation will fail. +#' This function is a wrapper for `shiny::validate`. +#' +#' @param x variable name. If `x` is not a factor, the unique values #' are treated as levels. -#' @param min_levels cutoff for minimum number of levels of \code{x} -#' @param max_levels cutoff for maximum number of levels of \code{x} +#' @param min_levels cutoff for minimum number of levels of `x` +#' @param max_levels cutoff for maximum number of levels of `x` #' @param var_name name of variable being validated for use in #' validation message #' -#' @details If the number of levels of \code{x} is less than \code{min_levels} -#' or greater than \code{max_levels} the validation will fail. -#' This function is a wrapper for `shiny::validate`. -#' #' @export #' @examples #' data <- data.frame( diff --git a/README.md b/README.md index ec27cede9b..6c5ec4d8e7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ -# `teal`: Interactive Exploratory Data Analysis with Shiny Web-Applications +# `teal`: Interactive Exploratory Data Analysis with `Shiny` Web-Applications +[![CRAN Version](https://www.r-pkg.org/badges/version/teal?color=green)](https://cran.r-project.org/package=teal) +[![Total Downloads](http://cranlogs.r-pkg.org/badges/grand-total/teal?color=green)](https://cran.r-project.org/package=teal) +[![Last Month Downloads](http://cranlogs.r-pkg.org/badges/last-month/teal?color=green)](https://cran.r-project.org/package=teal) +[![Last Week Downloads](http://cranlogs.r-pkg.org/badges/last-week/teal?color=green)](https://cran.r-project.org/package=teal) + [![Check 🛠](https://github.com/insightsengineering/teal/actions/workflows/check.yaml/badge.svg)](https://insightsengineering.github.io/teal/main/unit-test-report/) [![Docs 📚](https://github.com/insightsengineering/teal/actions/workflows/docs.yaml/badge.svg)](https://insightsengineering.github.io/teal/) [![Code Coverage 📔](https://raw.githubusercontent.com/insightsengineering/teal/_xml_coverage_reports/data/main/badge.svg)](https://insightsengineering.github.io/teal/main/coverage-report/) @@ -19,38 +24,38 @@ [![Open Issues](https://img.shields.io/github/issues-raw/insightsengineering/teal?color=red\&label=open%20issues)](https://github.com/insightsengineering/teal/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) -`teal` is a shiny-based interactive exploration framework for analyzing data. `teal` applications require app developers to specify: +`teal` is a `shiny`-based interactive exploration framework for analyzing data. `teal` applications require app developers to specify: - Data, which can be: - - `CDISC` data, commonly used for clinical trial reporting + - CDISC data, commonly used for clinical trial reporting - Independent datasets, for example from a `data.frame` - Related datasets, for example a set of `data.frames` with key columns to enable data joins - - `MultiAssayExperiment` objects which are R data structures for representing and analyzing multi-omics experiments + - `MultiAssayExperiment` objects which are `R` data structures for representing and analyzing multi-omics experiments - `teal` modules: - - `teal modules` are shiny modules built within the `teal` framework that specify analysis to be performed. For example, it can be a module for exploring outliers in the data, or a module for visualizing the data in line plots. Although these can be created from scratch, many `teal` modules have been released and we recommend starting with modules found in the following packages: - - [`teal.modules.general`](https://insightsengineering.github.io/teal.modules.general/): general modules for exploring relational/independent/`CDISC` data - - [`teal.modules.clinical`](https://insightsengineering.github.io/teal.modules.clinical/): modules specific to `CDISC` data and clinical trial reporting - - [`teal.modules.hermes`](https://insightsengineering.github.io/teal.modules.hermes/): modules for analyzing `MultiAssayExperiment` objects + - `teal modules` are `shiny` modules built within the `teal` framework that specify analysis to be performed. For example, it can be a module for exploring outliers in the data, or a module for visualizing the data in line plots. Although these can be created from scratch, many `teal` modules have been released and we recommend starting with modules found in the following packages: + - [`teal.modules.general`](https://insightsengineering.github.io/teal.modules.general/latest-tag/): general modules for exploring relational/independent/CDISC data + - [`teal.modules.clinical`](https://insightsengineering.github.io/teal.modules.clinical/latest-tag/): modules specific to CDISC data and clinical trial reporting + - [`teal.modules.hermes`](https://insightsengineering.github.io/teal.modules.hermes/latest-tag/): modules for analyzing `MultiAssayExperiment` objects A lot of the functionality of the `teal` framework derives from the following packages: -- [`teal.data`](https://insightsengineering.github.io/teal.data/): creating and loading the data needed for `teal` applications. -- [`teal.widgets`](https://insightsengineering.github.io/teal.widgets/): shiny components used within `teal`. -- [`teal.slice`](https://insightsengineering.github.io/teal.slice/): provides a filtering panel to allow filtering of data. -- [`teal.code`](https://insightsengineering.github.io/teal.code/): handles reproducibility of outputs. -- [`teal.logger`](https://insightsengineering.github.io/teal.logger/): standardizes logging within `teal` framework. -- [`teal.reporter`](https://insightsengineering.github.io/teal.reporter/): allows `teal` applications to generate reports. +- [`teal.data`](https://insightsengineering.github.io/teal.data/latest-tag/): creating and loading the data needed for `teal` applications. +- [`teal.widgets`](https://insightsengineering.github.io/teal.widgets/latest-tag/): `shiny` components used within `teal`. +- [`teal.slice`](https://insightsengineering.github.io/teal.slice/latest-tag/): provides a filtering panel to allow filtering of data. +- [`teal.code`](https://insightsengineering.github.io/teal.code/latest-tag/): handles reproducibility of outputs. +- [`teal.logger`](https://insightsengineering.github.io/teal.logger/latest-tag/): standardizes logging within `teal` framework. +- [`teal.reporter`](https://insightsengineering.github.io/teal.reporter/latest-tag/): allows `teal` applications to generate reports. ## Installation ```r -install.packages("teal", repos = c("https://insightsengineering.r-universe.dev", getOption("repos"))) +install.packages("teal") # install.packages("pak") pak::pak("insightsengineering/teal@*release") @@ -71,26 +76,27 @@ pak::pak("insightsengineering/teal") library(teal) app <- init( - data = teal_data( - dataset("iris", iris) - ), + data = teal_data(iris = iris), modules = list( - module( - "iris histogram", + module( + label = "iris histogram", server = function(input, output, session, data) { - output$hist <- renderPlot( - hist(data[["iris"]]()[[input$var]]) - ) + updateSelectInput(session = session, + inputId = "var", + choices = names(data()[["iris"]])[1:4]) + + output$hist <- renderPlot({ + req(input$var) + hist(x = data()[["iris"]][[input$var]]) + }) }, - ui = function(id, data, ...) { + ui = function(id) { ns <- NS(id) list( - shiny::selectInput( - ns("var"), - "Column name", - names(data[["iris"]]())[1:4] - ), - plotOutput(ns("hist")) + selectInput(inputId = ns("var"), + label = "Column name", + choices = NULL), + plotOutput(outputId = ns("hist")) ) } ) @@ -100,7 +106,7 @@ app <- init( shinyApp(app$ui, app$server) ``` -![App recording](man/figures/readme_app.gif) +![App recording](man/figures/readme_app.webp) Please see [`teal.gallery`](https://insightsengineering.github.io/teal.gallery) and [TLG Catalog](https://insightsengineering.github.io/tlg-catalog) to see examples of `teal` apps. @@ -108,7 +114,7 @@ Please start with the ["Technical Blueprint" article](https://insightsengineerin ## Getting help -If you encounter a bug or you have a feature request - please file an issue. For questions, discussions and staying up to date, please use the "teal" channel in the [pharmaverse slack workspace](https://pharmaverse.slack.com). +If you encounter a bug or have a feature request, please file an issue. For questions, discussions, and updates, use the `teal` channel in the [`pharmaverse` slack workspace](https://pharmaverse.slack.com). ## Acknowledgment diff --git a/_pkgdown.yml b/_pkgdown.yml index caf2b07604..d52c067412 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -11,7 +11,7 @@ navbar: components: get-started: text: Get started - href: articles/getting-started-with-teal.html + href: articles/getting-started-with-teal.html reports: text: Reports menu: @@ -20,27 +20,27 @@ navbar: - text: Unit test report href: unit-test-report/ blueprint: - text: Technical Blueprint + text: Technical blueprint menu: - - text: About Blueprint + - text: About blueprint href: articles/blueprint/index.html - text: Introduction href: articles/blueprint/intro.html - text: Actors href: articles/blueprint/actors.html - - text: Data Flow + - text: Data flow href: articles/blueprint/dataflow.html - - text: Product Map + - text: Product map href: articles/blueprint/product_map.html - text: Features navbar: Features - - text: Input Data + - text: Input data href: articles/blueprint/input_data.html - - text: In-App Data + - text: In-app data href: articles/blueprint/in_app_data.html - - text: Filter Panel + - text: Filter panel href: articles/blueprint/filter_panel.html - - text: Module and Encapsulation + - text: Module and encapsulation href: articles/blueprint/module_encapsulation.html github: @@ -52,23 +52,23 @@ articles: navbar: Get started contents: - getting-started-with-teal -- title: Using teal - navbar: Using teal +- title: Using `teal` + navbar: Using `teal` contents: - filter-panel - teal-options - bootstrap-themes-in-teal -- title: Data in teal Apps - navbar: Data in teal Apps +- title: Data in `teal` apps + navbar: Data in `teal` apps contents: - including-data-in-teal-applications - data-as-shiny-module -- title: Extending teal - navbar: Extending teal +- title: Extending `teal` + navbar: Extending `teal` contents: - creating-custom-modules - adding-support-for-reporting -- title: 📃 Technical Blueprint +- title: 📃 Technical blueprint desc: > The purpose of the blueprint is to aid new developer’s comprehension of the fundamental principles of the `teal` framework. We will explore crucial `teal` @@ -81,7 +81,7 @@ articles: - blueprint/product_map - title: "" desc: > - Features + Features. contents: - blueprint/input_data - blueprint/in_app_data @@ -90,11 +90,11 @@ articles: reference: - - title: Core `teal` Functions + - title: Core `teal` functions desc: Main functions needed to build a `teal` app contents: - - teal_data_module - init + - teal_data_module - module - modules - srv_teal_with_splash @@ -104,19 +104,19 @@ reference: desc: Helper functions for `teal` contents: - build_app_title - - title: Example Module + - title: Example module desc: A simple `teal` module contents: - example_module - - title: Creating Reports + - title: Creating reports contents: - reporter_previewer_module - TealReportCard - report_card_template - - title: Landing Popup + - title: Landing popup contents: - landing_popup_module - - title: Functions for Module Developers + - title: Functions for module developers contents: - as_tdata - tdata @@ -127,6 +127,6 @@ reference: - within.teal_data_module - show_rcode_modal - join_keys.tdata - - title: Validation Functions + - title: Validation functions contents: - starts_with("validate_") diff --git a/inst/WORDLIST b/inst/WORDLIST index 74db826371..7051e8828d 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -1,4 +1,6 @@ +ADaM CDISC +Biomarker Forkers Hoffmann MAEs @@ -7,15 +9,12 @@ TLG UI UIs UX -analysing cloneable customizable -dropdown favicon favicons funder omics -pharmaverse pre programmatically repo diff --git a/inst/css/custom.css b/inst/css/custom.css index c22169b932..d0ee28c782 100644 --- a/inst/css/custom.css +++ b/inst/css/custom.css @@ -68,3 +68,12 @@ footer { #teal_secondary_col .well { margin: 0 0 15px 0; } + +#shiny-modal:has(> .modal-dialog > .modal-content > #landingpopup) { + backdrop-filter: blur(10px); +} + +body:not(.modal-open) { + padding-right: 0 !important; + overflow: auto !important; +} diff --git a/inst/design/filters-mapping.drawio b/inst/design/filters-mapping.drawio new file mode 100644 index 0000000000..a280a0f9e4 --- /dev/null +++ b/inst/design/filters-mapping.drawio @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/inst/design/teal-app-components-hover.drawio b/inst/design/teal-app-components-hover.drawio new file mode 100644 index 0000000000..0bc3aed3d4 --- /dev/null +++ b/inst/design/teal-app-components-hover.drawio @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/inst/design/teal-app-components.drawio b/inst/design/teal-app-components.drawio new file mode 100644 index 0000000000..ac78609d62 --- /dev/null +++ b/inst/design/teal-app-components.drawio @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/man/TealReportCard.Rd b/man/TealReportCard.Rd index 37782e6a78..efeb681258 100644 --- a/man/TealReportCard.Rd +++ b/man/TealReportCard.Rd @@ -5,8 +5,8 @@ \title{\code{TealReportCard}} \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} -A child of \code{\link{ReportCard}} that is used for teal specific applications. -In addition to the parent methods, it supports rendering teal specific elements such as +Child class of \code{\link{ReportCard}} that is used for \code{teal} specific applications. +In addition to the parent methods, it supports rendering \code{teal} specific elements such as the source code, the encodings panel content and the filter panel content as part of the meta data. } @@ -75,13 +75,13 @@ Appends the source code to the \code{content} meta data of this \code{TealReport \describe{ \item{\code{src}}{(\code{character(1)}) code as text.} -\item{\code{...}}{any \code{rmarkdown} R chunk parameter and its value. +\item{\code{...}}{any \code{rmarkdown} \code{R} chunk parameter and its value. But \code{eval} parameter is always set to \code{FALSE}.} } \if{html}{\out{}} } \subsection{Returns}{ -invisibly self +Object of class \code{TealReportCard}, invisibly. } \subsection{Examples}{ \if{html}{\out{
}} @@ -115,7 +115,7 @@ If the filter state list is empty, nothing is appended to the \code{content}. \if{html}{\out{
}} } \subsection{Returns}{ -invisibly self +\code{self}, invisibly. } } \if{html}{\out{
}} @@ -130,12 +130,12 @@ Appends the encodings list to the \code{content} and \code{metadata} of this \co \subsection{Arguments}{ \if{html}{\out{
}} \describe{ -\item{\code{encodings}}{(\code{list}) list of encodings selections of the teal app.} +\item{\code{encodings}}{(\code{list}) list of encodings selections of the \code{teal} app.} } \if{html}{\out{
}} } \subsection{Returns}{ -invisibly self +\code{self}, invisibly. } \subsection{Examples}{ \if{html}{\out{
}} diff --git a/man/TealSlicesBlock.Rd b/man/TealSlicesBlock.Rd index 29031fda9a..842ab3e78d 100644 --- a/man/TealSlicesBlock.Rd +++ b/man/TealSlicesBlock.Rd @@ -7,15 +7,6 @@ \code{RcodeBlock} \code{RcodeBlock} -} -\examples{ - -## ------------------------------------------------ -## Method `TealSlicesBlock$new` -## ------------------------------------------------ - -block <- teal:::TealSlicesBlock$new() - } \keyword{internal} \section{Super classes}{ @@ -64,17 +55,8 @@ Returns a \code{TealSlicesBlock} object with no content and no parameters. } \subsection{Returns}{ -\code{TealSlicesBlock} -} -\subsection{Examples}{ -\if{html}{\out{
}} -\preformatted{block <- teal:::TealSlicesBlock$new() - +Object of class \code{TealSlicesBlock}, invisibly. } -\if{html}{\out{
}} - -} - } \if{html}{\out{
}} \if{html}{\out{}} @@ -97,7 +79,7 @@ When \code{selected} field in \code{teal_slice} object is a range, then it is di \if{html}{\out{
}} } \subsection{Returns}{ -invisibly self +\code{self}, invisibly. } } \if{html}{\out{
}} @@ -112,13 +94,13 @@ Create the \code{RcodeBlock} from a list. \subsection{Arguments}{ \if{html}{\out{
}} \describe{ -\item{\code{x}}{\verb{named list} with two fields \code{c("text", "params")}. +\item{\code{x}}{(named \code{list}) with two fields \code{c("text", "params")}. Use the \code{get_available_params} method to get all possible parameters.} } \if{html}{\out{
}} } \subsection{Returns}{ -invisibly self +\code{self}, invisibly. } } \if{html}{\out{
}} @@ -131,7 +113,7 @@ Convert the \code{RcodeBlock} to a list. } \subsection{Returns}{ -\verb{named list} with a text and \code{params}. +named \code{list} with a text and \code{params}. } } \if{html}{\out{
}} diff --git a/man/append_module.Rd b/man/append_module.Rd index 379e474ba9..74ddc41930 100644 --- a/man/append_module.Rd +++ b/man/append_module.Rd @@ -7,12 +7,12 @@ append_module(modules, module) } \arguments{ -\item{modules}{\code{teal_modules}} +\item{modules}{(\code{teal_modules})} -\item{module}{\code{teal_module} object to be appended onto the children of \code{modules}} +\item{module}{(\code{teal_module}) object to be appended onto the children of \code{modules}} } \value{ -\code{teal_modules} object with \code{module} appended +A \code{teal_modules} object with \code{module} appended. } \description{ Append a \code{teal_module} to \code{children} of a \code{teal_modules} object diff --git a/man/build_app_title.Rd b/man/build_app_title.Rd index 9354b85b8d..ac484f80f2 100644 --- a/man/build_app_title.Rd +++ b/man/build_app_title.Rd @@ -11,13 +11,13 @@ build_app_title( ) } \arguments{ -\item{title}{(\code{character}) The browser title for the teal app} +\item{title}{(\code{character}) The browser title for the \code{teal} app.} \item{favicon}{(\code{character}) The path for the icon for the title. -The image/icon path can be remote or the static path accessible by shiny, like the \verb{www/}} +The image/icon path can be remote or the static path accessible by \code{shiny}, like the \verb{www/}} } \value{ -A \code{shiny.tag} containing the element that adds the title and logo to the shiny app +A \code{shiny.tag} containing the element that adds the title and logo to the \code{shiny} app. } \description{ A helper function to create the browser title along with a logo. diff --git a/man/calculate_hashes.Rd b/man/calculate_hashes.Rd index 120b0f36bb..12215b5319 100644 --- a/man/calculate_hashes.Rd +++ b/man/calculate_hashes.Rd @@ -12,7 +12,7 @@ calculate_hashes(datanames, datasets) \item{datasets}{(\code{FilteredData}) object holding the data} } \value{ -A list of hashes per dataset +A list of hashes per dataset. } \description{ Get the hash of a dataset diff --git a/man/check_filter_datanames.Rd b/man/check_filter_datanames.Rd index 2827604a27..cc6b7615e0 100644 --- a/man/check_filter_datanames.Rd +++ b/man/check_filter_datanames.Rd @@ -16,6 +16,6 @@ A \code{character(1)} containing error message or TRUE if validation passes. } \description{ This function checks whether \code{datanames} in filters correspond to those in \code{data}, -returning character vector with error messages or TRUE if all checks pass. +returning character vector with error messages or \code{TRUE} if all checks pass. } \keyword{internal} diff --git a/man/create_app_id.Rd b/man/create_app_id.Rd index 755e50497d..62656beddb 100644 --- a/man/create_app_id.Rd +++ b/man/create_app_id.Rd @@ -7,9 +7,9 @@ create_app_id(data, modules) } \arguments{ -\item{data}{\code{teal_data} or \code{teal_data_module} as accepted by \code{init}} +\item{data}{(\code{teal_data} or \code{teal_data_module}) as accepted by \code{init}} -\item{modules}{\code{teal_modules} object as accepted by \code{init}} +\item{modules}{(\code{teal_modules}) object as accepted by \code{init}} } \value{ A single character string. diff --git a/man/dot-datasets_to_data.Rd b/man/dot-datasets_to_data.Rd index 1789e9bd71..36077d1795 100644 --- a/man/dot-datasets_to_data.Rd +++ b/man/dot-datasets_to_data.Rd @@ -16,7 +16,7 @@ A \code{teal_data} object. } \description{ Converts \code{FilteredData} object to \code{teal_data} object containing datasets needed for a specific module. -Please note that if module needs dataset which has a parent, then parent will be also returned. +Please note that if a module needs a dataset which has a parent, then the parent will also be returned. A hash per \code{dataset} is calculated internally and returned in the code. } \keyword{internal} diff --git a/man/example_cdisc_data.Rd b/man/example_cdisc_data.Rd deleted file mode 100644 index 3be2c91d65..0000000000 --- a/man/example_cdisc_data.Rd +++ /dev/null @@ -1,16 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/dummy_functions.R -\name{example_cdisc_data} -\alias{example_cdisc_data} -\title{Get dummy \code{CDISC} data} -\usage{ -example_cdisc_data() -} -\value{ -\code{cdisc_data} -} -\description{ -Get dummy \code{CDISC} data including \code{ADSL}, \code{ADAE} and \code{ADLB}. -Some NAs are also introduced to stress test. -} -\keyword{internal} diff --git a/man/example_datasets.Rd b/man/example_datasets.Rd deleted file mode 100644 index 6a5990d814..0000000000 --- a/man/example_datasets.Rd +++ /dev/null @@ -1,16 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/dummy_functions.R -\name{example_datasets} -\alias{example_datasets} -\title{Get datasets to go with example modules.} -\usage{ -example_datasets() -} -\value{ -named list of \code{FilteredData} objects, each with \code{ADSL} set. -} -\description{ -Creates a nested list, the structure of which matches the module hierarchy created by \code{example_modules}. -Each list leaf is the same \code{FilteredData} object. -} -\keyword{internal} diff --git a/man/example_module.Rd b/man/example_module.Rd index 130acb0f11..6aa96b0325 100644 --- a/man/example_module.Rd +++ b/man/example_module.Rd @@ -7,14 +7,14 @@ example_module(label = "example teal module", datanames = "all") } \arguments{ -\item{label}{(\code{character(1)}) Label shown in the navigation item for the module. Any label possible except -\code{"global_filters"} - read more in \code{mapping} argument of \link{teal_slices}.} +\item{label}{(\code{character(1)}) Label shown in the navigation item for the module or module group. +For \code{modules()} defaults to \code{"root"}. See \code{Details}.} \item{datanames}{(\code{character}) A vector with \code{datanames} that are relevant for the item. The filter panel will automatically update the shown filters to include only filters in the listed datasets. \code{NULL} will hide the filter panel, -and the keyword \code{'all'} will show filters of all datasets. \code{datanames} also determines -a subset of datasets which are appended to the \code{data} argument in \code{server} function.} +and the keyword \code{"all"} will show filters of all datasets. \code{datanames} also determines +a subset of datasets which are appended to the \code{data} argument in server function.} } \value{ A \code{teal} module which can be included in the \code{modules} argument to \code{\link[=init]{init()}}. diff --git a/man/example_modules.Rd b/man/example_modules.Rd deleted file mode 100644 index 59bcb88425..0000000000 --- a/man/example_modules.Rd +++ /dev/null @@ -1,19 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/dummy_functions.R -\name{example_modules} -\alias{example_modules} -\title{Get example modules.} -\usage{ -example_modules(datanames = c("ADSL", "ADTTE")) -} -\arguments{ -\item{datanames}{(\code{character})\cr -names of the datasets to be used in the example modules. Possible choices are \code{ADSL}, \code{ADTTE}.} -} -\value{ -\code{teal_modules} -} -\description{ -Creates an example hierarchy of \code{teal_modules} from which a \code{teal} app can be created. -} -\keyword{internal} diff --git a/man/figures/readme_app.gif b/man/figures/readme_app.gif deleted file mode 100644 index 5b64ad6374..0000000000 Binary files a/man/figures/readme_app.gif and /dev/null differ diff --git a/man/figures/readme_app.webp b/man/figures/readme_app.webp new file mode 100644 index 0000000000..039eb7677f Binary files /dev/null and b/man/figures/readme_app.webp differ diff --git a/man/figures/teal.png b/man/figures/teal.png index 7c8438f2df..0bf07febc2 100644 Binary files a/man/figures/teal.png and b/man/figures/teal.png differ diff --git a/man/filter_calls_module.Rd b/man/filter_calls_module.Rd deleted file mode 100644 index 53bf4b6faf..0000000000 --- a/man/filter_calls_module.Rd +++ /dev/null @@ -1,27 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/modules_debugging.R -\name{filter_calls_module} -\alias{filter_calls_module} -\title{Dummy module to show the filter calls generated by the right encoding panel} -\usage{ -filter_calls_module(label = "Filter Calls Module") -} -\arguments{ -\item{label}{\code{character} label of module} -} -\description{ -Please do not remove, this is useful for debugging teal without -dependencies and simplifies \verb{\link[devtools]\{load_all\}} which otherwise fails -and avoids session restarts! -} -\examples{ -app <- init( - data = teal_data(iris = iris, mtcars = mtcars), - modules = teal:::filter_calls_module(), - header = "Simple teal app" -) -if (interactive()) { - shinyApp(app$ui, app$server) -} -} -\keyword{internal} diff --git a/man/filter_manager_module_srv.Rd b/man/filter_manager_module_srv.Rd index d5ef33f781..e00afd6e38 100644 --- a/man/filter_manager_module_srv.Rd +++ b/man/filter_manager_module_srv.Rd @@ -7,13 +7,13 @@ filter_manager_module_srv(id, module_fd, slices_global) } \arguments{ -\item{id}{(\code{character(1)})\cr +\item{id}{(\code{character(1)}) \code{shiny} module id.} -\item{module_fd}{(\code{FilteredData})\cr -object to filter data in the teal-module} +\item{module_fd}{(\code{FilteredData}) +Object containing the data to be filtered in a single \code{teal} module.} -\item{slices_global}{(\code{reactiveVal})\cr +\item{slices_global}{(\code{reactiveVal}) stores \code{teal_slices} with all available filters; allows the following actions: \itemize{ \item to disable/enable a specific filter in a module @@ -25,7 +25,7 @@ stores \code{teal_slices} with all available filters; allows the following actio A \code{reactive} expression containing the slices active in this module. } \description{ -Track filter states in single module. +Tracks filter states in a single module. } \details{ This module tracks the state of a single \code{FilteredData} object and global \code{teal_slices} diff --git a/man/get_client_timezone.Rd b/man/get_client_timezone.Rd index aba8ecbc27..b878a00b3f 100644 --- a/man/get_client_timezone.Rd +++ b/man/get_client_timezone.Rd @@ -2,22 +2,20 @@ % Please edit documentation in R/utils.R \name{get_client_timezone} \alias{get_client_timezone} -\title{Get Client Timezone} +\title{Get client timezone} \usage{ get_client_timezone(ns) } \arguments{ -\item{ns}{(\code{function}) namespace function passed from the \code{session} object in the -Shiny server. For Shiny modules this will allow for proper name spacing of the -registered input.} +\item{ns}{(\code{function}) namespace function passed from the \code{session} object in the \code{shiny} server. +For \code{shiny} modules this will allow for proper name spacing of the registered input.} } \value{ -(\code{Shiny}) input variable accessible with \code{input$tz} which is a (\code{character}) +(\code{shiny}) input variable accessible with \code{input$tz} which is a (\code{character}) string containing the timezone of the browser/client. } \description{ -Local timezone in the browser may differ from the system timezone from the server. -This script can be run to register a shiny input which contains information about -the timezone in the browser. +User timezone in the browser may be different to the one on the server. +This script can be run to register a \code{shiny} input which contains information about the timezone in the browser. } \keyword{internal} diff --git a/man/get_code_tdata.Rd b/man/get_code_tdata.Rd index c220f29959..cc9f336dd3 100644 --- a/man/get_code_tdata.Rd +++ b/man/get_code_tdata.Rd @@ -2,8 +2,7 @@ % Please edit documentation in R/tdata.R \name{get_code_tdata} \alias{get_code_tdata} -\title{Wrapper for \code{get_code.tdata} -This wrapper is to be used by downstream packages to extract the code of a \code{tdata} object} +\title{Wrapper for \code{get_code.tdata}} \usage{ get_code_tdata(data) } @@ -14,6 +13,5 @@ get_code_tdata(data) (\code{character}) code used in the \code{tdata} object. } \description{ -Wrapper for \code{get_code.tdata} -This wrapper is to be used by downstream packages to extract the code of a \code{tdata} object +This wrapper is to be used by downstream packages to extract the code of a \code{tdata} object. } diff --git a/man/get_datasets_code.Rd b/man/get_datasets_code.Rd index b68839cef8..d8108ec921 100644 --- a/man/get_datasets_code.Rd +++ b/man/get_datasets_code.Rd @@ -22,6 +22,6 @@ Character string concatenated from the following elements: } } \description{ -Get combined code from \code{FilteredData} and from \code{CodeClass} object. +Retrieve complete code to create, verify, and filter a dataset. } \keyword{internal} diff --git a/man/get_metadata.Rd b/man/get_metadata.Rd index 31bf8dc0a9..0f45078ccd 100644 --- a/man/get_metadata.Rd +++ b/man/get_metadata.Rd @@ -13,12 +13,12 @@ get_metadata(data, dataname) \method{get_metadata}{default}(data, dataname) } \arguments{ -\item{data}{\code{tdata} - object to extract the data from} +\item{data}{(\code{tdata} - object) to extract the data from} -\item{dataname}{\code{character(1)} the dataset name whose metadata is requested} +\item{dataname}{(\code{character(1)}) the dataset name whose metadata is requested} } \value{ -Either list of metadata or NULL if no metadata +Either list of metadata or NULL if no metadata. } \description{ Function to get metadata from a \code{tdata} object diff --git a/man/get_rcode_libraries.Rd b/man/get_rcode_libraries.Rd index f85a6885c4..e2c6e57bc7 100644 --- a/man/get_rcode_libraries.Rd +++ b/man/get_rcode_libraries.Rd @@ -7,9 +7,9 @@ get_rcode_libraries() } \value{ -Character object contain code +Character vector of \verb{library()} calls. } \description{ -Function to create multiple library calls out of current session info to make reproducible code works. +Function to create multiple library calls out of current session info to ensure reproducible code works. } \keyword{internal} diff --git a/man/get_teal_bs_theme.Rd b/man/get_teal_bs_theme.Rd deleted file mode 100644 index 6027ecf352..0000000000 --- a/man/get_teal_bs_theme.Rd +++ /dev/null @@ -1,12 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils.R -\name{get_teal_bs_theme} -\alias{get_teal_bs_theme} -\title{Resolve the expected bootstrap theme} -\usage{ -get_teal_bs_theme() -} -\description{ -Resolve the expected bootstrap theme -} -\keyword{internal} diff --git a/man/include_css_files.Rd b/man/include_css_files.Rd index 76ac430da1..2a72bcda14 100644 --- a/man/include_css_files.Rd +++ b/man/include_css_files.Rd @@ -10,11 +10,11 @@ include_css_files(pattern = "*") \item{pattern}{(\code{character}) pattern of files to be included} } \value{ -HTML code that includes \code{CSS} files +HTML code that includes \code{CSS} files. } \description{ \code{system.file} should not be used to access files in other packages, it does not work with \code{devtools}. Therefore, we redefine this method in each package -as needed. Thus, we do not export this method +as needed. Thus, we do not export this method. } \keyword{internal} diff --git a/man/include_js_files.Rd b/man/include_js_files.Rd index 45133cf249..fdf1d94f2b 100644 --- a/man/include_js_files.Rd +++ b/man/include_js_files.Rd @@ -12,7 +12,7 @@ include_js_files(pattern = NULL, except = NULL) \item{except}{(\code{character}) vector of basename filenames to be excluded} } \value{ -HTML code that includes \code{JS} files +HTML code that includes \code{JS} files. } \description{ \code{system.file} should not be used to access files in other packages, it does diff --git a/man/include_teal_css_js.Rd b/man/include_teal_css_js.Rd index c46171d99a..a371ee2de8 100644 --- a/man/include_teal_css_js.Rd +++ b/man/include_teal_css_js.Rd @@ -2,24 +2,30 @@ % Please edit documentation in R/include_css_js.R \name{include_teal_css_js} \alias{include_teal_css_js} -\title{Code to include teal \code{CSS} and \code{JavaScript} files} +\title{Code to include \code{teal} \code{CSS} and \code{JavaScript} files} \usage{ include_teal_css_js() } \value{ -HTML code to include +A \code{shiny.tag.list}. } \description{ This is useful when you want to use the same \code{JavaScript} and \code{CSS} files that are -used with the teal application. -This is also useful for running standalone modules in teal with the correct +used with the \code{teal} application. +This is also useful for running standalone modules in \code{teal} with the correct styles. Also initializes \code{shinyjs} so you can use it. } +\details{ +Simply add \code{include_teal_css_js()} as one of the UI elements. +} \examples{ +# use non-exported function from teal +include_teal_css_js <- getFromNamespace("include_teal_css_js", "teal") shiny_ui <- tagList( - teal:::include_teal_css_js(), + include_teal_css_js(), p("Hello") ) + } \keyword{internal} diff --git a/man/init.Rd b/man/init.Rd index 8c7c0ccf3d..c84edf1c26 100644 --- a/man/init.Rd +++ b/man/init.Rd @@ -2,59 +2,54 @@ % Please edit documentation in R/init.R \name{init} \alias{init} -\title{Create the Server and UI Function For the Shiny App} +\title{Create the server and UI function for the \code{shiny} app} \usage{ init( data, modules, - title = build_app_title(), filter = teal_slices(), + title = build_app_title(), header = tags$p(), footer = tags$p(), id = character(0) ) } \arguments{ -\item{data}{(\code{teal_data}, \code{teal_data_module}, \verb{named list})\cr -\code{teal_data} object as returned by \code{\link[teal.data:teal_data]{teal.data::teal_data()}} or -\code{teal_data_module} or simply a list of a named list of objects -(\code{data.frame} or \code{MultiAssayExperiment}).} +\item{data}{(\code{teal_data} or \code{teal_data_module}) +For constructing the data object, refer to \code{\link[=teal_data]{teal_data()}} and \code{\link[=teal_data_module]{teal_data_module()}}.} -\item{modules}{(\code{list}, \code{teal_modules} or \code{teal_module})\cr +\item{modules}{(\code{list} or \code{teal_modules} or \code{teal_module}) nested list of \code{teal_modules} or \code{teal_module} objects or a single \code{teal_modules} or \code{teal_module} object. These are the specific output modules which -will be displayed in the teal application. See \code{\link[=modules]{modules()}} and \code{\link[=module]{module()}} for +will be displayed in the \code{teal} application. See \code{\link[=modules]{modules()}} and \code{\link[=module]{module()}} for more details.} -\item{title}{(\code{shiny.tag} or \code{character(1)})\cr +\item{filter}{(\code{teal_slices}) +Specifies the initial filter using \code{\link[=teal_slices]{teal_slices()}}.} + +\item{title}{(\code{shiny.tag} or \code{character(1)}) The browser window title. Defaults to a title "teal app" with the icon of NEST. Can be created using the \code{build_app_title()} or by passing a valid \code{shiny.tag} which is a head tag with title and link tag.} -\item{filter}{(\code{teal_slices})\cr -Specification of initial filter. Filters can be specified using \code{\link[=teal_slices]{teal_slices()}}. -Old way of specifying filters through a list is deprecated and will be removed in the -next release. Please fix your applications to use \code{\link[=teal_slices]{teal_slices()}}.} - -\item{header}{(\code{shiny.tag} or \code{character(1)}) \cr +\item{header}{(\code{shiny.tag} or \code{character(1)}) The header of the app.} -\item{footer}{(\code{shiny.tag} or \code{character(1)})\cr +\item{footer}{(\code{shiny.tag} or \code{character(1)}) The footer of the app.} -\item{id}{(\code{character})\cr -module id to embed it, if provided, -the server function must be called with \code{\link[shiny:moduleServer]{shiny::moduleServer()}}; -See the vignette for an example. However, \code{\link[=ui_teal_with_splash]{ui_teal_with_splash()}} -is then preferred to this function.} +\item{id}{(\code{character}) +Optional string specifying the \code{shiny} module id in cases it is used as a \code{shiny} module +rather than a standalone \code{shiny} app. This is a legacy feature.} } \value{ -named list with \code{server} and \code{ui} function +Named list with server and UI functions. } \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} + End-users: This is the most important function for you to start a -teal app that is composed out of teal modules. +\code{teal} app that is composed of \code{teal} modules. } \details{ When initializing the \code{teal} app, if \code{datanames} are not set for the \code{teal_data} object, @@ -92,7 +87,6 @@ app <- init( datanames = "new_iris" ) ), - title = "App title", filter = teal_slices( teal_slice(dataname = "new_iris", varname = "Species"), teal_slice(dataname = "new_iris", varname = "Sepal.Length"), @@ -104,6 +98,7 @@ app <- init( global_filters = "new_mtcars cyl" ) ), + title = "App title", header = tags$h1("Sample App"), footer = tags$p("Copyright 2017 - 2023") ) diff --git a/man/is_arg_used.Rd b/man/is_arg_used.Rd index 16e06d09fa..15500324d2 100644 --- a/man/is_arg_used.Rd +++ b/man/is_arg_used.Rd @@ -12,7 +12,7 @@ is_arg_used(modules, arg) \item{arg}{(\code{character(1)}) names of the arguments to be checked against formals of \code{teal} modules.} } \value{ -\code{logical} whether the object makes use of \code{arg} +\code{logical} whether the object makes use of \code{arg}. } \description{ Does the object make use of the \code{arg} diff --git a/man/join_keys.tdata.Rd b/man/join_keys.tdata.Rd index 19d022c7c5..ef18d05686 100644 --- a/man/join_keys.tdata.Rd +++ b/man/join_keys.tdata.Rd @@ -7,7 +7,7 @@ \method{join_keys}{tdata}(data, ...) } \arguments{ -\item{data}{A \code{tdata} object} +\item{data}{(\code{tdata}) object} \item{...}{Additional arguments (not used)} } diff --git a/man/landing_popup_module.Rd b/man/landing_popup_module.Rd index 1c8ead4e86..2979af6555 100644 --- a/man/landing_popup_module.Rd +++ b/man/landing_popup_module.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/landing_popup_module.R \name{landing_popup_module} \alias{landing_popup_module} -\title{Landing Popup Module} +\title{Landing popup module} \usage{ landing_popup_module( label = "Landing Popup", @@ -12,14 +12,14 @@ landing_popup_module( ) } \arguments{ -\item{label}{\code{character(1)} the label of the module.} +\item{label}{(\code{character(1)}) Label of the module.} -\item{title}{\code{character(1)} the text to be displayed as a title of the popup.} +\item{title}{(\code{character(1)}) Text to be displayed as popup title.} -\item{content}{The content of the popup. Passed to \code{...} of \code{shiny::modalDialog}. Can be a \code{character} -or a list of \code{shiny.tag}s. See examples.} +\item{content}{(\code{character(1)}, \code{shiny.tag} or \code{shiny.tag.list}) with the content of the popup. +Passed to \code{...} of \code{shiny::modalDialog}. See examples.} -\item{buttons}{\code{shiny.tag} or a list of tags (\code{tagList}). Typically a \code{modalButton} or \code{actionButton}. See examples.} +\item{buttons}{(\code{shiny.tag} or \code{shiny.tag.list}) Typically a \code{modalButton} or \code{actionButton}. See examples.} } \value{ A \code{teal_module} (extended with \code{teal_landing_module} class) to be used in \code{teal} applications. @@ -28,13 +28,13 @@ A \code{teal_module} (extended with \code{teal_landing_module} class) to be used Creates a landing welcome popup for \code{teal} applications. This module is used to display a popup dialog when the application starts. -The dialog blocks the access to the application and must be closed with a button before the application is viewed. +The dialog blocks access to the application and must be closed with a button before the application can be viewed. } \examples{ -app1 <- teal::init( +app1 <- init( data = teal_data(iris = iris), - modules = teal::modules( - teal::landing_popup_module( + modules = modules( + landing_popup_module( content = "A place for the welcome message or a disclaimer statement.", buttons = modalButton("Proceed") ), @@ -45,10 +45,10 @@ if (interactive()) { shinyApp(app1$ui, app1$server) } -app2 <- teal::init( +app2 <- init( data = teal_data(iris = iris), - modules = teal::modules( - teal::landing_popup_module( + modules = modules( + landing_popup_module( title = "Welcome", content = tags$b( "A place for the welcome message or a disclaimer statement.", diff --git a/man/matrix_to_mapping.Rd b/man/matrix_to_mapping.Rd index 67b1d96d57..4683866db0 100644 --- a/man/matrix_to_mapping.Rd +++ b/man/matrix_to_mapping.Rd @@ -11,7 +11,7 @@ matrix_to_mapping(mapping_matrix) columns represent modules and row represent \code{teal_slice}s} } \value{ -\verb{named list} like that in the \code{mapping} attribute of a \code{teal_slices} object. +Named \code{list} like that in the \code{mapping} attribute of a \code{teal_slices} object. } \description{ Transform a mapping matrix, i.e. a data frame that maps each filter state to each module, diff --git a/man/module.Rd b/man/module.Rd deleted file mode 100644 index e40022d00e..0000000000 --- a/man/module.Rd +++ /dev/null @@ -1,105 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/modules.R -\name{module} -\alias{module} -\alias{toString.teal_module} -\alias{print.teal_module} -\title{Creates a \code{teal_module} object.} -\usage{ -module( - label = "module", - server = function(id, ...) { - moduleServer(id, function(input, output, session) { - - }) - }, - ui = function(id, ...) { - tags$p(paste0("This module has no UI (id: ", id, " )")) - - }, - filters, - datanames = "all", - server_args = NULL, - ui_args = NULL -) - -\method{toString}{teal_module}(x, indent = 0, ...) - -\method{print}{teal_module}(x, ...) -} -\arguments{ -\item{label}{(\code{character(1)}) Label shown in the navigation item for the module. Any label possible except -\code{"global_filters"} - read more in \code{mapping} argument of \link{teal_slices}.} - -\item{server}{(\code{function}) \code{shiny} module with following arguments: -\itemize{ -\item \code{id} - teal will set proper shiny namespace for this module (see \code{\link[shiny:moduleServer]{shiny::moduleServer()}}). -\item \code{input}, \code{output}, \code{session} - (not recommended) then \code{\link[shiny:callModule]{shiny::callModule()}} will be used to call a module. -\item \code{data} (optional) module will receive a \code{teal_data} object, a list of reactive (filtered) data specified in -the \code{filters} argument. -\item \code{datasets} (optional) module will receive \code{FilteredData}. (See \verb{[teal.slice::FilteredData]}). -\item \code{reporter} (optional) module will receive \code{Reporter}. (See \link[teal.reporter:Reporter]{teal.reporter::Reporter}). -\item \code{...} (optional) \code{server_args} elements will be passed to the module named argument or to the \code{...}. -}} - -\item{ui}{(\code{function}) Shiny \code{ui} module function with following arguments: -\itemize{ -\item \code{id} - teal will set proper shiny namespace for this module. -\item \code{...} (optional) \code{ui_args} elements will be passed to the module named argument or to the \code{...}. -}} - -\item{filters}{(\code{character}) Deprecated. Use \code{datanames} instead.} - -\item{datanames}{(\code{character}) A vector with \code{datanames} that are relevant for the item. The -filter panel will automatically update the shown filters to include only -filters in the listed datasets. \code{NULL} will hide the filter panel, -and the keyword \code{'all'} will show filters of all datasets. \code{datanames} also determines -a subset of datasets which are appended to the \code{data} argument in \code{server} function.} - -\item{server_args}{(named \code{list}) with additional arguments passed on to the -\code{server} function.} - -\item{ui_args}{(named \code{list}) with additional arguments passed on to the -\code{ui} function.} - -\item{x}{\code{teal_module}} - -\item{indent}{(\code{integer}) indent level; -each \code{submodule} is indented one level more} - -\item{...}{parameters passed to \code{toString}} -} -\value{ -object of class \code{teal_module}. -} -\description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} -This function embeds a \code{shiny} module inside a \code{teal} application. One \code{teal_module} maps to one \code{shiny} module. -} -\examples{ -library(shiny) - -app <- init( - data = teal_data(iris = iris), - modules = list( - module( - label = "Module", - server = function(id, data) { - moduleServer( - id, - module = function(input, output, session) { - output$data <- renderDataTable(data[["iris"]]()) - } - ) - }, - ui = function(id) { - ns <- NS(id) - tagList(dataTableOutput(ns("data"))) - } - ) - ) -) -if (interactive()) { - shinyApp(app$ui, app$server) -} -} diff --git a/man/module_filter_manager.Rd b/man/module_filter_manager.Rd index 5428396151..3d487118fd 100644 --- a/man/module_filter_manager.Rd +++ b/man/module_filter_manager.Rd @@ -1,6 +1,7 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/module_filter_manager.R -\name{filter_manager_ui} +\name{module_filter_manager} +\alias{module_filter_manager} \alias{filter_manager_ui} \alias{filter_manager_srv} \title{Manage multiple \code{FilteredData} objects} @@ -10,28 +11,26 @@ filter_manager_ui(id) filter_manager_srv(id, filtered_data_list, filter) } \arguments{ -\item{id}{(\code{character(1)})\cr +\item{id}{(\code{character(1)}) \code{shiny} module id.} -\item{filtered_data_list}{(\verb{named list})\cr +\item{filtered_data_list}{(named \code{list}) A list, possibly nested, of \code{FilteredData} objects. Each \code{FilteredData} will be served to one module in the \code{teal} application. The structure of the list must reflect the nesting of modules in tabs -and names of the list must be the same as labels of their respective modules.} +and the names of the list must match the labels of their respective modules.} -\item{filter}{(\code{teal_slices})\cr -Specification of initial filter. Filters can be specified using \code{\link[=teal_slices]{teal_slices()}}. -Old way of specifying filters through a list is deprecated and will be removed in the -next release. Please fix your applications to use \code{\link[=teal_slices]{teal_slices()}}.} +\item{filter}{(\code{teal_slices}) +Specifies the initial filter using \code{\link[=teal_slices]{teal_slices()}}.} } \value{ A list of \code{reactive}s, each holding a \code{teal_slices}, as returned by \code{filter_manager_module_srv}. } \description{ -Oversee filter states in the whole application. +Oversee filter states across the entire application. } \details{ -This module observes the changes of the filters in each \code{FilteredData} object +This module observes changes in the filters of each \code{FilteredData} object and keeps track of all filters used. A mapping of filters to modules is kept in the \code{mapping_matrix} object (which is actually a \code{data.frame}) that tracks which filters (rows) are active in which modules (columns). diff --git a/man/module_filter_manager_modal.Rd b/man/module_filter_manager_modal.Rd index dea4ad4955..e4308eae4e 100644 --- a/man/module_filter_manager_modal.Rd +++ b/man/module_filter_manager_modal.Rd @@ -11,24 +11,26 @@ filter_manager_modal_ui(id) filter_manager_modal_srv(id, filtered_data_list, filter) } \arguments{ -\item{id}{(\code{character(1)})\cr +\item{id}{(\code{character(1)}) \code{shiny} module id.} -\item{filtered_data_list}{(\verb{named list})\cr +\item{filtered_data_list}{(named \code{list}) A list, possibly nested, of \code{FilteredData} objects. Each \code{FilteredData} will be served to one module in the \code{teal} application. The structure of the list must reflect the nesting of modules in tabs -and names of the list must be the same as labels of their respective modules.} +and the names of the list must match the labels of their respective modules.} -\item{filter}{(\code{teal_slices})\cr -Specification of initial filter. Filters can be specified using \code{\link[=teal_slices]{teal_slices()}}. -Old way of specifying filters through a list is deprecated and will be removed in the -next release. Please fix your applications to use \code{\link[=teal_slices]{teal_slices()}}.} +\item{filter}{(\code{teal_slices}) +Specifies the initial filter using \code{\link[=teal_slices]{teal_slices()}}.} } \description{ -Opens modal containing the filter manager UI. +Opens a modal containing the filter manager UI. } \examples{ +# use non-exported function from teal +filter_manager_modal_ui <- getFromNamespace("filter_manager_modal_ui", "teal") +filter_manager_modal_srv <- getFromNamespace("filter_manager_modal_srv", "teal") + fd1 <- teal.slice::init_filtered_data(list(iris = list(dataset = iris))) fd2 <- teal.slice::init_filtered_data( list(iris = list(dataset = iris), mtcars = list(dataset = mtcars)) @@ -48,20 +50,22 @@ filter <- teal_slices( ) ) -app <- shinyApp( - ui = fluidPage( - teal:::filter_manager_modal_ui("manager") - ), - server = function(input, output, session) { - teal:::filter_manager_modal_srv( +ui <- fluidPage( + filter_manager_modal_ui("manager") +) + +server <- function(input, output, session) { + observe({ + filter_manager_modal_srv( "manager", filtered_data_list = list(module1 = fd1, module2 = fd2, module3 = fd3), filter = filter ) - } -) + }) +} + if (interactive()) { - shinyApp(app$ui, app$server) + shinyApp(ui, server) } } diff --git a/man/module_labels.Rd b/man/module_labels.Rd new file mode 100644 index 0000000000..eb17276c6e --- /dev/null +++ b/man/module_labels.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/modules.R +\name{module_labels} +\alias{module_labels} +\title{Retrieve labels from \code{teal_modules}} +\usage{ +module_labels(modules) +} +\arguments{ +\item{modules}{(\code{teal_modules})} +} +\value{ +A \code{list} containing the labels of the modules. If the modules are nested, +the function returns a nested \code{list} of labels. +} +\description{ +Retrieve labels from \code{teal_modules} +} +\keyword{internal} diff --git a/man/module_management.Rd b/man/module_management.Rd index bd9f88db50..66ae01d7a4 100644 --- a/man/module_management.Rd +++ b/man/module_management.Rd @@ -10,13 +10,15 @@ extract_module(modules, class) drop_module(modules, class) } \arguments{ -\item{modules}{\code{teal_modules}} +\item{modules}{(\code{teal_modules})} \item{class}{The class name of \code{teal_module} to be extracted or dropped.} } \value{ -For \code{extract_module}, a \code{teal_module} of class \code{class} or \code{teal_modules} containing modules of class \code{class}. -For \code{drop_module}, the opposite, which is all \code{teal_modules} of class other than \code{class}. +\itemize{ +\item For \code{extract_module}, a \code{teal_module} of class \code{class} or \code{teal_modules} containing modules of class \code{class}. +\item For \code{drop_module}, the opposite, which is all \code{teal_modules} of class other than \code{class}. +} \code{teal_modules} } diff --git a/man/module_nested_tabs.Rd b/man/module_nested_tabs.Rd index e4d2620e15..39711d6e70 100644 --- a/man/module_nested_tabs.Rd +++ b/man/module_nested_tabs.Rd @@ -53,32 +53,37 @@ srv_nested_tabs( ) } \arguments{ -\item{id}{(\code{character(1)})\cr +\item{id}{(\code{character(1)}) module id} -\item{datasets}{(\verb{named list} of \code{FilteredData})\cr +\item{modules}{(\code{teal_modules}) object containing the output modules which +will be displayed in the \code{teal} application. See \code{\link[=modules]{modules()}} and \code{\link[=module]{module()}} for +more details.} + +\item{datasets}{(named \code{list} of \code{FilteredData}) object to store filter state and filtered datasets, shared across modules. For more details see \code{\link[teal.slice:FilteredData]{teal.slice::FilteredData}}. Structure of the list must be the same as structure of the \code{modules} argument and list names must correspond to the labels in \code{modules}. When filter is not module-specific then list contains the same object in all elements.} -\item{depth}{(\code{integer(1)})\cr +\item{depth}{(\code{integer(1)}) number which helps to determine depth of the modules nesting.} -\item{is_module_specific}{(\code{logical(1)})\cr +\item{is_module_specific}{(\code{logical(1)}) flag determining if the filter panel is global or module-specific. When set to \code{TRUE}, a filter panel is called inside of each module tab.} \item{reporter}{(\code{Reporter}) object from \code{teal.reporter}} } \value{ -depending on class of \code{modules}, \code{ui_nested_tabs} returns: +Depending on the class of \code{modules}, \code{ui_nested_tabs} returns: \itemize{ -\item \code{teal_module}: instantiated UI of the module +\item \code{teal_module}: instantiated UI of the module. \item \code{teal_modules}: \code{tabsetPanel} with each tab corresponding to recursively -calling this function on it.\cr -\code{srv_nested_tabs} returns a reactive which returns the active module that corresponds to the selected tab. +calling this function on it. } + +\code{srv_nested_tabs} returns a reactive which returns the active module that corresponds to the selected tab. } \description{ Create a UI of nested tabs of \code{teal_modules} @@ -87,16 +92,15 @@ Create a UI of nested tabs of \code{teal_modules} Each \code{teal_modules} is translated to a \code{tabsetPanel} and each of its children is another tab-module called recursively. The UI of a -\code{teal_module} is obtained by calling the \code{ui} function on it. +\code{teal_module} is obtained by calling its UI function. -The \code{datasets} argument is required to resolve the teal arguments in an -isolated context (with respect to reactivity) +The \code{datasets} argument is required to resolve the \code{teal} arguments in an +isolated context (with respect to reactivity). } \section{\code{srv_nested_tabs}}{ -This module calls recursively all elements of the \code{modules} returns one which -is currently active. +This module recursively calls all elements of \code{modules} and returns currently active one. \itemize{ \item \code{teal_module} returns self as a active module. \item \code{teal_modules} also returns module active within self which is determined by the \code{input$active_tab}. @@ -104,31 +108,67 @@ is currently active. } \examples{ -mods <- teal:::example_modules() -datasets <- teal:::example_datasets() -app <- shinyApp( - ui = function() { - tagList( - teal:::include_teal_css_js(), - textOutput("info"), - fluidPage( # needed for nice tabs - teal:::ui_nested_tabs("dummy", modules = mods, datasets = datasets) - ) - ) - }, - server = function(input, output, session) { - active_module <- teal:::srv_nested_tabs( - "dummy", - datasets = datasets, - modules = mods - ) - output$info <- renderText({ - paste0("The currently active tab name is ", active_module()$label) - }) - } +# use non-exported function from teal +include_teal_css_js <- getFromNamespace("include_teal_css_js", "teal") +teal_data_to_filtered_data <- getFromNamespace("teal_data_to_filtered_data", "teal") +ui_nested_tabs <- getFromNamespace("ui_nested_tabs", "teal") +srv_nested_tabs <- getFromNamespace("srv_nested_tabs", "teal") + +# create `teal_data` +data <- teal_data(iris = iris, mtcars = mtcars) +datanames <- datanames(data) + +# creates a hierarchy of `teal_modules` from which a `teal` app can be created. +mods <- modules( + label = "d1", + modules( + label = "d2", + modules( + label = "d3", + example_module(label = "aaa1", datanames = datanames), + example_module(label = "aaa2", datanames = datanames) + ), + example_module(label = "bbb", datanames = datanames) + ), + example_module(label = "ccc", datanames = datanames) ) + +# creates nested list aligned with the module hierarchy created above, +# each leaf holding the same `FilteredData` object. +datasets <- teal_data_to_filtered_data(data) +datasets <- list( + "d2" = list( + "d3" = list( + "aaa1" = datasets, + "aaa2" = datasets + ), + "bbb" = datasets + ), + "ccc" = datasets +) + +ui <- function() { + tagList( + include_teal_css_js(), + textOutput("info"), + fluidPage( # needed for nice tabs + ui_nested_tabs("dummy", modules = mods, datasets = datasets) + ) + ) +} +server <- function(input, output, session) { + active_module <- srv_nested_tabs( + "dummy", + datasets = datasets, + modules = mods + ) + output$info <- renderText({ + paste0("The currently active tab name is ", active_module()$label) + }) +} if (interactive()) { - shinyApp(app$ui, app$server) + shinyApp(ui, server) } + } \keyword{internal} diff --git a/man/module_tabs_with_filters.Rd b/man/module_tabs_with_filters.Rd index 03163535dc..e0f95b37d0 100644 --- a/man/module_tabs_with_filters.Rd +++ b/man/module_tabs_with_filters.Rd @@ -17,20 +17,26 @@ srv_tabs_with_filters( ) } \arguments{ -\item{id}{(\code{character(1)})\cr +\item{id}{(\code{character(1)}) module id} -\item{datasets}{(\verb{named list} of \code{FilteredData})\cr +\item{modules}{(\code{teal_modules}) object containing the output modules which +will be displayed in the \code{teal} application. See \code{\link[=modules]{modules()}} and \code{\link[=module]{module()}} for +more details.} + +\item{datasets}{(named \code{list} of \code{FilteredData}) object to store filter state and filtered datasets, shared across modules. For more details see \code{\link[teal.slice:FilteredData]{teal.slice::FilteredData}}. Structure of the list must be the same as structure of the \code{modules} argument and list names must correspond to the labels in \code{modules}. When filter is not module-specific then list contains the same object in all elements.} +\item{filter}{(\code{teal_slices}) +Specifies the initial filter using \code{\link[=teal_slices]{teal_slices()}}.} + \item{reporter}{(\code{Reporter}) object from \code{teal.reporter}} } \value{ -A \code{tagList} of The main menu, place holders for filters and -place holders for the teal modules +A \code{shiny.tag.list} containing the main menu, placeholders for filters and placeholders for the \code{teal} modules. } \description{ The \link{ui_nested_tabs} function returns a nested tabbed UI corresponding @@ -45,30 +51,63 @@ This works with nested modules of depth greater than 2, though the filter panel is inserted at the right of the modules at depth 1 and not at the leaves. } \examples{ +# use non-exported function from teal +include_teal_css_js <- getFromNamespace("include_teal_css_js", "teal") +teal_data_to_filtered_data <- getFromNamespace("teal_data_to_filtered_data", "teal") +ui_tabs_with_filters <- getFromNamespace("ui_tabs_with_filters", "teal") +srv_tabs_with_filters <- getFromNamespace("srv_tabs_with_filters", "teal") -mods <- teal:::example_modules() -datasets <- teal:::example_datasets() +# creates `teal_data` +data <- teal_data(iris = iris, mtcars = mtcars) +datanames <- datanames(data) -app <- shinyApp( - ui = function() { - tagList( - teal:::include_teal_css_js(), - textOutput("info"), - fluidPage( # needed for nice tabs - ui_tabs_with_filters("dummy", modules = mods, datasets = datasets) - ) - ) - }, - server = function(input, output, session) { - output$info <- renderText({ - paste0("The currently active tab name is ", active_module()$label) - }) - active_module <- srv_tabs_with_filters(id = "dummy", datasets = datasets, modules = mods) - } +# creates a hierarchy of `teal_modules` from which a `teal` app can be created. +mods <- modules( + label = "d1", + modules( + label = "d2", + modules( + label = "d3", + example_module(label = "aaa1", datanames = datanames), + example_module(label = "aaa2", datanames = datanames) + ), + example_module(label = "bbb", datanames = datanames) + ), + example_module(label = "ccc", datanames = datanames) ) -if (interactive()) { - shinyApp(app$ui, app$server) + +# creates nested list aligned with the module hierarchy created above, +# each leaf holding the same `FilteredData` object. +datasets <- teal_data_to_filtered_data(data) +datasets <- list( + "d2" = list( + "d3" = list( + "aaa1" = datasets, + "aaa2" = datasets + ), + "bbb" = datasets + ), + "ccc" = datasets +) + +ui <- function() { + tagList( + include_teal_css_js(), + textOutput("info"), + fluidPage( # needed for nice tabs + ui_tabs_with_filters("dummy", modules = mods, datasets = datasets) + ) + ) +} +server <- function(input, output, session) { + output$info <- renderText({ + paste0("The currently active tab name is ", active_module()$label) + }) + active_module <- srv_tabs_with_filters(id = "dummy", datasets = datasets, modules = mods) } +if (interactive()) { + shinyApp(ui, server) +} } \keyword{internal} diff --git a/man/module_teal.Rd b/man/module_teal.Rd index 7e0a6f6811..cd5b56413a 100644 --- a/man/module_teal.Rd +++ b/man/module_teal.Rd @@ -4,7 +4,7 @@ \alias{module_teal} \alias{ui_teal} \alias{srv_teal} -\title{teal main app module} +\title{\code{teal} main app module} \usage{ ui_teal( id, @@ -17,69 +17,85 @@ ui_teal( srv_teal(id, modules, teal_data_rv, filter = teal_slices()) } \arguments{ -\item{id}{(\code{character(1)})\cr +\item{id}{(\code{character(1)}) module id} -\item{splash_ui}{(\code{shiny.tag})\cr UI to display initially, -can be a splash screen or a Shiny module UI. For the latter, see +\item{splash_ui}{(\code{shiny.tag}) UI to display initially, +can be a splash screen or a \code{shiny} module UI. For the latter, see \code{\link[=init]{init()}} about how to call the corresponding server function.} -\item{title}{(\code{shiny.tag} or \code{character(1)})\cr +\item{title}{(\code{shiny.tag} or \code{character(1)}) The browser window title. Defaults to a title "teal app" with the icon of NEST. Can be created using the \code{build_app_title()} or by passing a valid \code{shiny.tag} which is a head tag with title and link tag.} -\item{header}{(\code{shiny.tag} or \code{character(1)}) \cr +\item{header}{(\code{shiny.tag} or \code{character(1)}) The header of the app.} -\item{footer}{(\code{shiny.tag} or \code{character(1)})\cr +\item{footer}{(\code{shiny.tag} or \code{character(1)}) The footer of the app.} -\item{teal_data_rv}{(\code{reactive})\cr +\item{modules}{(\code{teal_modules}) object containing the output modules which +will be displayed in the \code{teal} application. See \code{\link[=modules]{modules()}} and \code{\link[=module]{module()}} for +more details.} + +\item{teal_data_rv}{(\code{reactive}) returns the \code{teal_data}, only evaluated once, \code{NULL} value is ignored} + +\item{filter}{(\code{teal_slices}) +Specifies the initial filter using \code{\link[=teal_slices]{teal_slices()}}.} } \value{ -\code{ui_teal} returns \code{HTML} for Shiny module UI. -\code{srv_teal} returns \code{reactive} which returns the currently active module. +Returns a \code{reactive} expression which returns the currently active module. } \description{ -This is the main teal app that puts everything together. +This is the main \code{teal} app that puts everything together. } \details{ It displays the splash UI which is used to fetch the data, possibly prompting for a password input to fetch the data. Once the data is ready, -the splash screen is replaced by the actual teal UI that is tabsetted and +the splash screen is replaced by the actual \code{teal} UI that is tabsetted and has a filter panel with \code{datanames} that are relevant for the current tab. Nested tabs are possible, but we limit it to two nesting levels for reasons of clarity of the UI. The splash screen functionality can also be used for non-delayed data which takes time to load into memory, avoiding -Shiny session timeouts. +\code{shiny} session timeouts. Server evaluates the \code{teal_data_rv} (delayed data mechanism) and creates the \code{datasets} object that is shared across modules. Once it is ready and non-\code{NULL}, the splash screen is replaced by the -main teal UI that depends on the data. +main \code{teal} UI that depends on the data. The currently active tab is tracked and the right filter panel updates the displayed datasets to filter for according to the active \code{datanames} of the tab. -It is written as a Shiny module so it can be added into other apps as well. +It is written as a \code{shiny} module so it can be added into other apps as well. } \examples{ -mods <- teal:::example_modules() -teal_data_rv <- reactive(teal:::example_cdisc_data()) -app <- shinyApp( - ui = function() { - teal:::ui_teal("dummy") - }, - server = function(input, output, session) { - active_module <- teal:::srv_teal(id = "dummy", modules = mods, teal_data_rv = teal_data_rv) - } +# use non-exported function from teal +ui_teal <- getFromNamespace("ui_teal", "teal") +srv_teal <- getFromNamespace("srv_teal", "teal") + +mods <- modules( + label = "example app", + example_module(label = "example dataset", datanames = c("iris", "mtcars")) ) + +teal_data_rv <- reactive(teal_data(iris = iris, mtcars = mtcars)) + +ui <- function() { + ui_teal("dummy") +} + +server <- function(input, output, session) { + active_module <- srv_teal(id = "dummy", modules = mods, teal_data_rv = teal_data_rv) +} + if (interactive()) { - shinyApp(app$ui, app$server) + shinyApp(ui, server) } + } \keyword{internal} diff --git a/man/module_teal_with_splash.Rd b/man/module_teal_with_splash.Rd new file mode 100644 index 0000000000..edbe3746ff --- /dev/null +++ b/man/module_teal_with_splash.Rd @@ -0,0 +1,85 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/module_teal_with_splash.R +\name{module_teal_with_splash} +\alias{module_teal_with_splash} +\alias{ui_teal_with_splash} +\alias{srv_teal_with_splash} +\title{Add splash screen to \code{teal} application.} +\usage{ +ui_teal_with_splash( + id, + data, + title = build_app_title(), + header = tags$p(), + footer = tags$p() +) + +srv_teal_with_splash(id, data, modules, filter = teal_slices()) +} +\arguments{ +\item{id}{(\code{character(1)}) +module id} + +\item{data}{(\code{teal_data} or \code{teal_data_module}) +For constructing the data object, refer to \code{\link[=teal_data]{teal_data()}} and \code{\link[=teal_data_module]{teal_data_module()}}.} + +\item{title}{(\code{shiny.tag} or \code{character(1)}) +The browser window title. Defaults to a title "teal app" with the icon of NEST. +Can be created using the \code{build_app_title()} or +by passing a valid \code{shiny.tag} which is a head tag with title and link tag.} + +\item{header}{(\code{shiny.tag} or \code{character(1)}) +The header of the app.} + +\item{footer}{(\code{shiny.tag} or \code{character(1)}) +The footer of the app.} + +\item{modules}{(\code{teal_modules}) object containing the output modules which +will be displayed in the \code{teal} application. See \code{\link[=modules]{modules()}} and \code{\link[=module]{module()}} for +more details.} + +\item{filter}{(\code{teal_slices}) +Specifies the initial filter using \code{\link[=teal_slices]{teal_slices()}}.} +} +\value{ +Returns a \code{reactive} expression containing a \code{teal_data} object when data is loaded or \code{NULL} when it is not. +} +\description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} + +Displays custom splash screen during initial delayed data loading. +} +\details{ +This module pauses app initialization pending delayed data loading. +This is necessary because the filter panel and modules depend on the data to initialize. + +\code{teal_with_splash} follows the \code{shiny} module convention. +\code{\link[=init]{init()}} is a wrapper around this that assumes that \code{teal} it is +the top-level module and cannot be embedded. + +Note: It is no longer recommended to embed \code{teal} in \code{shiny} apps as a module. +but rather use \code{init} to create a standalone application. +} +\examples{ +teal_modules <- modules(example_module()) +# Shiny app with modular integration of teal +ui <- fluidPage( + ui_teal_with_splash(id = "app1", data = teal_data()) +) + +server <- function(input, output, session) { + srv_teal_with_splash( + id = "app1", + data = teal_data(iris = iris), + modules = teal_modules + ) +} + +if (interactive()) { + shinyApp(ui, server) +} + +} +\seealso{ +\code{\link[=init]{init()}} +} diff --git a/man/modules.Rd b/man/modules.Rd deleted file mode 100644 index dbcc71d7f4..0000000000 --- a/man/modules.Rd +++ /dev/null @@ -1,92 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/modules.R -\name{modules} -\alias{modules} -\alias{toString.teal_modules} -\alias{print.teal_modules} -\title{Creates a \code{teal_modules} object.} -\usage{ -modules(..., label = "root") - -\method{toString}{teal_modules}(x, indent = 0, ...) - -\method{print}{teal_modules}(x, ...) -} -\arguments{ -\item{...}{parameters passed to \code{toString}} - -\item{label}{(\code{character(1)}) label of modules collection (default \code{"root"}). -If using the \code{label} argument then it must be explicitly named. -For example \code{modules("lab", ...)} should be converted to \code{modules(label = "lab", ...)}} - -\item{x}{\code{teal_modules}} - -\item{indent}{(\code{integer}) indent level; -each \code{submodule} is indented one level more} -} -\value{ -object of class \code{teal_modules}. Object contains following fields -\itemize{ -\item \code{label}: taken from the \code{label} argument -\item \code{children}: a list containing objects passed in \code{...}. List elements are named after -their \code{label} attribute converted to a valid \code{shiny} id. -} - -(\code{character}) -} -\description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} -This function collects a list of \code{teal_modules} and \code{teal_module} objects and returns a \code{teal_modules} object -containing the passed objects. - -This function dictates what modules are included in a \code{teal} application. The internal structure of \code{teal_modules} -shapes the navigation panel of a \code{teal} application. -} -\examples{ -library(shiny) - -app <- init( - data = teal_data(iris = iris), - modules = modules( - label = "Modules", - modules( - label = "Module", - module( - label = "Inner module", - server = function(id, data) { - moduleServer( - id, - module = function(input, output, session) { - output$data <- renderDataTable(data[["iris"]]()) - } - ) - }, - ui = function(id) { - ns <- NS(id) - tagList(dataTableOutput(ns("data"))) - }, - datanames = "all" - ) - ), - module( - label = "Another module", - server = function(id) { - moduleServer( - id, - module = function(input, output, session) { - output$text <- renderText("Another module") - } - ) - }, - ui = function(id) { - ns <- NS(id) - tagList(textOutput(ns("text"))) - }, - datanames = NULL - ) - ) -) -if (interactive()) { - shinyApp(app$ui, app$server) -} -} diff --git a/man/modules_depth.Rd b/man/modules_depth.Rd index d945fa439c..98a65537a5 100644 --- a/man/modules_depth.Rd +++ b/man/modules_depth.Rd @@ -7,22 +7,25 @@ modules_depth(modules, depth = 0L) } \arguments{ -\item{modules}{(\code{list}, \code{teal_modules} or \code{teal_module})\cr +\item{modules}{(\code{list} or \code{teal_modules} or \code{teal_module}) nested list of \code{teal_modules} or \code{teal_module} objects or a single \code{teal_modules} or \code{teal_module} object. These are the specific output modules which -will be displayed in the teal application. See \code{\link[=modules]{modules()}} and \code{\link[=module]{module()}} for +will be displayed in the \code{teal} application. See \code{\link[=modules]{modules()}} and \code{\link[=module]{module()}} for more details.} \item{depth}{optional, integer determining current depth level} } \value{ -depth level for given module +Depth level for given module. } \description{ Depth starts at 0, so a single \code{teal.module} has depth 0. Nesting it increases overall depth by 1. } \examples{ +# use non-exported function from teal +modules_depth <- getFromNamespace("modules_depth", "teal") + mods <- modules( label = "d1", modules( @@ -35,7 +38,7 @@ mods <- modules( ), module(label = "ccc") ) -stopifnot(teal:::modules_depth(mods) == 3L) +stopifnot(modules_depth(mods) == 3L) mods <- modules( label = "a", @@ -44,6 +47,6 @@ mods <- modules( ), module(label = "b2") ) -stopifnot(teal:::modules_depth(mods) == 2L) +stopifnot(modules_depth(mods) == 2L) } \keyword{internal} diff --git a/man/report_card_template.Rd b/man/report_card_template.Rd index 1be43cf829..42a2750800 100644 --- a/man/report_card_template.Rd +++ b/man/report_card_template.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/utils.R \name{report_card_template} \alias{report_card_template} -\title{Template Function for \code{TealReportCard} Creation and Customization} +\title{Template function for \code{TealReportCard} creation and customization} \usage{ report_card_template( title, @@ -25,7 +25,7 @@ report_card_template( of the filter state in the report} } \value{ -(\code{TealReportCard}) populated with a title, description and filter state +(\code{TealReportCard}) populated with a title, description and filter state. } \description{ This function generates a report card with a title, diff --git a/man/reporter_previewer_module.Rd b/man/reporter_previewer_module.Rd index 937a3ec9dc..1aa7bb0784 100644 --- a/man/reporter_previewer_module.Rd +++ b/man/reporter_previewer_module.Rd @@ -7,23 +7,23 @@ reporter_previewer_module(label = "Report previewer", server_args = list()) } \arguments{ -\item{label}{(\code{character(1)}) Label shown in the navigation item for the module. Any label possible except -\code{"global_filters"} - read more in \code{mapping} argument of \link{teal_slices}.} +\item{label}{(\code{character(1)}) Label shown in the navigation item for the module or module group. +For \code{modules()} defaults to \code{"root"}. See \code{Details}.} -\item{server_args}{(\verb{named list})\cr +\item{server_args}{(named \code{list}) Arguments passed to \code{\link[teal.reporter:reporter_previewer_srv]{teal.reporter::reporter_previewer_srv()}}.} } \value{ -\code{teal_module} (extended with \code{teal_module_previewer} class) containing the \code{teal.reporter} previewer -functionality. +\code{teal_module} (extended with \code{teal_module_previewer} class) containing the \code{teal.reporter} previewer functionality. } \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} + This function wraps \code{\link[teal.reporter:reporter_previewer_ui]{teal.reporter::reporter_previewer_ui()}} and \code{\link[teal.reporter:reporter_previewer_srv]{teal.reporter::reporter_previewer_srv()}} into a \code{teal_module} to be used in \code{teal} applications. If you are creating a \code{teal} application using \code{\link[=init]{init()}} then this -module will be added to your application automatically if any of your \verb{teal modules} +module will be added to your application automatically if any of your \code{teal_modules} support report generation. } diff --git a/man/resolve_modules_datanames.Rd b/man/resolve_modules_datanames.Rd index 928a381884..3c966c6c11 100644 --- a/man/resolve_modules_datanames.Rd +++ b/man/resolve_modules_datanames.Rd @@ -14,7 +14,7 @@ resolve_modules_datanames(modules, datanames, join_keys) \item{join_keys}{(\code{join_keys}) object} } \value{ -\code{teal_modules} with resolved \code{datanames} +\code{teal_modules} with resolved \code{datanames}. } \description{ Modifies \code{module$datanames} to include names of the parent dataset (taken from \code{join_keys}). diff --git a/man/run_js_files.Rd b/man/run_js_files.Rd index 0168333cc0..e519649216 100644 --- a/man/run_js_files.Rd +++ b/man/run_js_files.Rd @@ -7,7 +7,10 @@ run_js_files(files) } \arguments{ -\item{files}{(\code{character}) vector of filenames} +\item{files}{(\code{character}) vector of filenames.} +} +\value{ +returns \code{NULL}, invisibly. } \description{ This is triggered from the server to execute on the client @@ -18,6 +21,6 @@ the \code{run_js} actually executes \code{JavaScript} functions. \details{ \code{system.file} should not be used to access files in other packages, it does not work with \code{devtools}. Therefore, we redefine this method in each package -as needed. Thus, we do not export this method +as needed. Thus, we do not export this method. } \keyword{internal} diff --git a/man/show_rcode_modal.Rd b/man/show_rcode_modal.Rd index 228aec1036..66874b9690 100644 --- a/man/show_rcode_modal.Rd +++ b/man/show_rcode_modal.Rd @@ -2,23 +2,24 @@ % Please edit documentation in R/show_rcode_modal.R \name{show_rcode_modal} \alias{show_rcode_modal} -\title{Show R Code Modal} +\title{Show \code{R} code modal} \usage{ show_rcode_modal(title = NULL, rcode, session = getDefaultReactiveDomain()) } \arguments{ -\item{title}{(\code{character(1)})\cr -Title of the modal, displayed in the first comment of the R-code.} +\item{title}{(\code{character(1)}) +Title of the modal, displayed in the first comment of the \code{R} code.} -\item{rcode}{(\code{character})\cr -vector with R code to show inside the modal.} +\item{rcode}{(\code{character}) +vector with \code{R} code to show inside the modal.} -\item{session}{(\code{ShinySession} optional)\cr -\code{shiny} Session object, if missing then \code{\link[shiny:domains]{shiny::getDefaultReactiveDomain()}} is used.} +\item{session}{(\code{ShinySession} optional) +\code{shiny} session object, if missing then \code{\link[shiny:domains]{shiny::getDefaultReactiveDomain()}} is used.} } \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} -Use the \code{\link[shiny:showModal]{shiny::showModal()}} function to show the R code inside. + +Use the \code{\link[shiny:showModal]{shiny::showModal()}} function to show the \code{R} code inside. } \references{ \code{\link[shiny:showModal]{shiny::showModal()}} diff --git a/man/slices_restore.Rd b/man/slices_restore.Rd deleted file mode 100644 index c2b2c39192..0000000000 --- a/man/slices_restore.Rd +++ /dev/null @@ -1,27 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/teal_slices-store.R -\name{slices_restore} -\alias{slices_restore} -\title{Restore teal_slices object from a file} -\usage{ -slices_restore(file) -} -\arguments{ -\item{file}{Path to file where \code{teal_slices} is stored. Must have a \code{.json} extension and read access.} -} -\value{ -A \code{teal_slices} object restored from the file. -} -\description{ -This function takes a file path to a \code{JSON} file containing a \code{teal_slices} object -and restores it to its original form. The restored \code{teal_slices} object can be used -to access filter states and their corresponding attributes. -} -\examples{ -if (interactive()) { - # Restore a teal_slices object from a file - tss_restored <- slices_restore("path/to/file.json") -} - -} -\keyword{internal} diff --git a/man/slices_store.Rd b/man/slices_store.Rd index 9ee4a96c5c..f8d9f75f0e 100644 --- a/man/slices_store.Rd +++ b/man/slices_store.Rd @@ -2,43 +2,65 @@ % Please edit documentation in R/teal_slices-store.R \name{slices_store} \alias{slices_store} -\title{Store teal_slices object to a file} +\alias{slices_restore} +\title{Store and restore \code{teal_slices} object} \usage{ slices_store(tss, file) + +slices_restore(file) } \arguments{ \item{tss}{(\code{teal_slices}) object to be stored.} -\item{file}{(\code{character(1)}) The file path where \code{teal_slices} object will be saved. -The file extension should be \code{".json"}.} +\item{file}{(\code{character(1)}) file path where \code{teal_slices} object will be +saved and restored. The file extension should be \code{".json"}.} } \value{ -\code{NULL}, invisibly. +\code{slices_store} returns \code{NULL}, invisibly. + +\code{slices_restore} returns a \code{teal_slices} object restored from the file. } \description{ -This function takes a \code{teal_slices} object and saves it to a file in \code{JSON} format. -The \code{teal_slices} object contains information about filter states and can be used to -create, modify, and delete filter states. The saved file can be later loaded using -the \code{slices_restore} function. +Functions that write a \code{teal_slices} object to a file in the \code{JSON} format, +and also restore the object from disk. } \details{ -\code{Date} class is stored in \code{"ISO8601"} format (\code{YYYY-MM-DD}). \code{POSIX*t} classes are converted to a -character by using \code{format.POSIX*t(usetz = TRUE, tz = "UTC")} (\verb{YYYY-MM-DD \{N\}\{N\}:\{N\}\{N\}:\{N\}\{N\} UTC}, where -\verb{\{N\} = [0-9]} is a number and \code{UTC} is \verb{Coordinated Universal Time} timezone short-code). -This format is assumed during \code{slices_restore}. All \code{POSIX*t} objects in \code{selected} or \code{choices} fields of -\code{teal_slice} objects are always printed in \code{UTC} timezone as well. +Date and date time objects are stored in the following formats: +\itemize{ +\item \code{Date} class is converted to the \code{"ISO8601"} standard (\code{YYYY-MM-DD}). +\item \code{POSIX*t} classes are converted to character by using +\code{format.POSIX*t(usetz = TRUE, tz = "UTC")} (\verb{YYYY-MM-DD HH:MM:SS UTC}, where +\code{UTC} is the \verb{Coordinated Universal Time} timezone short-code). +} + +This format is assumed during \code{slices_restore}. All \code{POSIX*t} objects in +\code{selected} or \code{choices} fields of \code{teal_slice} objects are always printed in +\code{UTC} timezone as well. } \examples{ +# use non-exported function from teal +slices_store <- getFromNamespace("slices_store", "teal") + # Create a teal_slices object tss <- teal_slices( teal_slice(dataname = "data", varname = "var"), teal_slice(dataname = "data", expr = "x > 0", id = "positive_x", title = "Positive x") ) -if (interactive()) { - # Store the teal_slices object to a file - slices_store(tss, "path/to/file.json") -} +slices_path <- tempfile(pattern = "teal_slices", fileext = ".json") +print(slices_path) + +# Store the teal_slices object to a file +slices_store(tss, slices_path) + +# use non-exported function from teal +slices_restore <- getFromNamespace("slices_restore", "teal") +# Restore a teal_slices object from a file +tss_restored <- slices_restore(slices_path) + +} +\seealso{ +\code{\link[=teal_slices]{teal_slices()}} } \keyword{internal} diff --git a/man/snapshot_manager_module.Rd b/man/snapshot_manager_module.Rd index 66e5b8a94d..5bf1a6382f 100644 --- a/man/snapshot_manager_module.Rd +++ b/man/snapshot_manager_module.Rd @@ -22,7 +22,7 @@ containing all \code{teal_slice}s existing in the app, both active and inactive} of the mapping of filter state ids (rows) to modules labels (columns); all columns are \code{logical} vectors} -\item{filtered_data_list}{non-nested (\verb{named list}) that contains \code{FilteredData} objects} +\item{filtered_data_list}{non-nested (named \code{list}) that contains \code{FilteredData} objects} } \value{ Nothing is returned. @@ -72,7 +72,7 @@ Once a name has been accepted, \code{slices_global} is converted to a list of li The snapshot contains the \code{mapping} attribute of the initial application state (or one that has been restored), which may not reflect the current one, so \code{mapping_matrix} is transformed to obtain the current mapping, i.e. a list that, -when passed to the \code{mapping} argument of \code{\link{teal_slices}}, would result in the current mapping. +when passed to the \code{mapping} argument of \code{\link[=teal_slices]{teal_slices()}}, would result in the current mapping. This is substituted as the snapshot's \code{mapping} attribute and the snapshot is added to the snapshot list. To restore app state, a snapshot is retrieved from storage and rebuilt into a \code{teal_slices} object. @@ -81,7 +81,7 @@ and set anew according to the \code{mapping} attribute of the snapshot. The snapshot is then set as the current content of \code{slices_global}. To save a snapshot, the snapshot is retrieved and reassembled just like for restoring, -and then saved to file with \code{\link{slices_store}}. +and then saved to file with \code{\link[=slices_store]{slices_store()}}. When a snapshot is uploaded, it will first be added to storage just like a newly created one, and then used to restore app state much like a snapshot taken from storage. diff --git a/man/srv_teal_with_splash.Rd b/man/srv_teal_with_splash.Rd deleted file mode 100644 index ad1c259cd0..0000000000 --- a/man/srv_teal_with_splash.Rd +++ /dev/null @@ -1,38 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/module_teal_with_splash.R -\name{srv_teal_with_splash} -\alias{srv_teal_with_splash} -\title{Server function that loads the data through reactive loading and then delegates -to \code{\link[=srv_teal]{srv_teal()}}.} -\usage{ -srv_teal_with_splash(id, data, modules, filter = teal_slices()) -} -\arguments{ -\item{id}{(\code{character})\cr -module id to embed it, if provided, -the server function must be called with \code{\link[shiny:moduleServer]{shiny::moduleServer()}}; -See the vignette for an example. However, \code{\link[=ui_teal_with_splash]{ui_teal_with_splash()}} -is then preferred to this function.} - -\item{data}{(\code{teal_data}, \code{teal_data_module}, \verb{named list})\cr -\code{teal_data} object as returned by \code{\link[teal.data:teal_data]{teal.data::teal_data()}} or -\code{teal_data_module} or simply a list of a named list of objects -(\code{data.frame} or \code{MultiAssayExperiment}).} - -\item{modules}{\code{teal_modules} object containing the output modules which -will be displayed in the teal application. See \code{\link[=modules]{modules()}} and \code{\link[=module]{module()}} for -more details.} - -\item{filter}{(\code{teal_slices})\cr -Specification of initial filter. Filters can be specified using \code{\link[=teal_slices]{teal_slices()}}. -Old way of specifying filters through a list is deprecated and will be removed in the -next release. Please fix your applications to use \code{\link[=teal_slices]{teal_slices()}}.} -} -\value{ -\code{reactive} containing \code{teal_data} object when data is loaded. -If data is not loaded yet, \code{reactive} returns \code{NULL}. -} -\description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} -Please also refer to the doc of \code{\link[=init]{init()}}. -} diff --git a/man/tdata.Rd b/man/tdata.Rd index 77da52688a..2cdfb301e2 100644 --- a/man/tdata.Rd +++ b/man/tdata.Rd @@ -3,37 +3,38 @@ \name{tdata} \alias{tdata} \alias{new_tdata} -\title{Create a \code{tdata} Object} +\title{Create a \code{tdata} object} \usage{ new_tdata(data, code = "", join_keys = NULL, metadata = NULL) } \arguments{ -\item{data}{A \verb{named list} of \code{data.frames} (or \code{MultiAssayExperiment}) +\item{data}{(named \code{list}) A list of \code{data.frame} or \code{MultiAssayExperiment} objects, which optionally can be \code{reactive}. Inside this object all of these items will be made \code{reactive}.} -\item{code}{A \code{character} (or \code{reactive} which evaluates to a \code{character}) containing +\item{code}{(\code{character} or \code{reactive} which evaluates to a \code{character}) containing the code used to generate the data. This should be \code{reactive} if the code is changing during a reactive context (e.g. if filtering changes the code). Inside this object \code{code} will be made reactive} -\item{join_keys}{A \code{teal.data::join_keys} object containing relationships between the +\item{join_keys}{(\code{teal.data::join_keys}) object containing relationships between the datasets.} -\item{metadata}{A \verb{named list} each element contains a list of metadata about the named data.frame +\item{metadata}{(named \code{list}) each element contains a list of metadata about the named \code{data.frame} Each element of these list should be atomic and length one.} } \value{ -A \code{tdata} object +A \code{tdata} object. } \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} -Create a new object called \code{tdata} which contains \code{data}, a \code{reactive} list of data.frames + +Create a new object called \code{tdata} which contains \code{data}, a \code{reactive} list of \code{data.frames} (or \code{MultiAssayExperiment}), with attributes: \itemize{ -\item{\code{code} (\code{reactive}) containing code used to generate the data} -\item{join_keys (\code{join_keys}) containing the relationships between the data} -\item{metadata (\verb{named list}) containing any metadata associated with the data frames} +\item \code{code} (\code{reactive}) containing code used to generate the data +\item join_keys (\code{join_keys}) containing the relationships between the data +\item metadata (named \code{list}) containing any metadata associated with the data frames } } \examples{ diff --git a/man/tdata2env.Rd b/man/tdata2env.Rd index eb3f2bec20..61f0576320 100644 --- a/man/tdata2env.Rd +++ b/man/tdata2env.Rd @@ -2,20 +2,18 @@ % Please edit documentation in R/tdata.R \name{tdata2env} \alias{tdata2env} -\title{Function to convert a \code{tdata} object to an \code{environment} -Any \code{reactives} inside \code{tdata} are first evaluated} +\title{Function to convert a \code{tdata} object to an \code{environment}} \usage{ tdata2env(data) } \arguments{ -\item{data}{a \code{tdata} object} +\item{data}{(\code{tdata}) object} } \value{ -an \code{environment} +An \code{environment}. } \description{ -Function to convert a \code{tdata} object to an \code{environment} -Any \code{reactives} inside \code{tdata} are first evaluated +Any \code{reactive} expressions inside \code{tdata} are evaluated first. } \examples{ diff --git a/man/tdata_deprecation.Rd b/man/tdata_deprecation.Rd index 7c9a1a570d..5f0b006de2 100644 --- a/man/tdata_deprecation.Rd +++ b/man/tdata_deprecation.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/tdata.R \name{as_tdata} \alias{as_tdata} -\title{Downgrade \code{teal_data} objects in modules for compatibility.} +\title{Downgrade \code{teal_data} objects in modules for compatibility} \usage{ as_tdata(x) } diff --git a/man/teal-package.Rd b/man/teal-package.Rd index ad4f6bd6b8..027c900c47 100644 --- a/man/teal-package.Rd +++ b/man/teal-package.Rd @@ -4,14 +4,14 @@ \name{teal-package} \alias{teal} \alias{teal-package} -\title{teal: Interactive Exploration of Clinical Trials Data} +\title{\code{teal}: Interactive exploration of clinical trials data} \description{ -The teal package provides a shiny based framework for creating an +The \code{teal} package provides a \code{shiny} based framework for creating an interactive data analysis environment. } \details{ -To learn mode about the package either read the project website at -\url{Project Website} or read the \code{\link{init}} manual pages. +To learn mode about the package, visit the \href{https://insightsengineering.github.io/teal/latest-tag/}{project website} +or read the \code{\link[=init]{init()}} manual page. } \seealso{ Useful links: diff --git a/man/teal_data_module.Rd b/man/teal_data_module.Rd index 043e394547..631c2f2fd1 100644 --- a/man/teal_data_module.Rd +++ b/man/teal_data_module.Rd @@ -9,7 +9,7 @@ \alias{eval_code,teal_data_module,expression-method} \alias{within} \alias{within.teal_data_module} -\title{Data Module for \code{teal} Applications} +\title{Data module for \code{teal} applications} \usage{ teal_data_module(ui, server) @@ -18,11 +18,11 @@ teal_data_module(ui, server) \method{within}{teal_data_module}(data, expr, ...) } \arguments{ -\item{ui}{(\verb{function(id)})\cr -\code{shiny} module \code{ui} function; must only take \code{id} argument} +\item{ui}{(\verb{function(id)}) +\code{shiny} module UI function; must only take \code{id} argument} -\item{server}{(\verb{function(id)})\cr -\code{shiny} module \code{ui} function; must only take \code{id} argument; +\item{server}{(\verb{function(id)}) +\code{shiny} module server function; must only take \code{id} argument; must return reactive expression containing \code{teal_data} object} \item{object}{(\code{teal_data_module})} @@ -51,18 +51,22 @@ Create a \code{teal_data_module} object and evaluate code on it with history tra \code{teal_data_module} creates a \code{shiny} module to supply or modify data in a \code{teal} application. The module allows for running data pre-processing code (creation \emph{and} some modification) after the app starts. The body of the server function will be run in the app rather than in the global environment. -This means it will be run every time the app starts, so use sparingly.\cr +This means it will be run every time the app starts, so use sparingly. + Pass this module instead of a \code{teal_data} object in a call to \code{\link[=init]{init()}}. -Note that the server function must always return a \code{teal_data} object wrapped in a reactive expression.\cr +Note that the server function must always return a \code{teal_data} object wrapped in a reactive expression. + See vignette \code{vignette("data-as-shiny-module", package = "teal")} for more details. \code{eval_code} evaluates given code in the environment of the \code{teal_data} object created by the \code{teal_data_module}. The code is added to the \verb{@code} slot of the \code{teal_data}. \code{within} is a convenience function for evaluating inline code inside the environment of a \code{teal_data_module}. +It accepts only inline expressions (both simple and compound) and allows for injecting values into \code{expr} through +the \code{...} argument: as \code{name:value} pairs are passed to \code{...}, \code{name} in \code{expr} will be replaced with \code{value.} } \examples{ -data <- teal_data_module( +tdm <- teal_data_module( ui = function(id) { ns <- NS(id) actionButton(ns("submit"), label = "Load data") @@ -85,27 +89,14 @@ data <- teal_data_module( } ) -tdm <- teal_data_module( - ui = function(id) div(id = shiny::NS(id)("div_id")), - server = function(id) { - shiny::moduleServer(id, function(input, output, session) { - shiny::reactive(teal_data(IRIS = iris)) - }) - } -) -eval_code(tdm, "IRIS <- subset(IRIS, Species == 'virginica')") +eval_code(tdm, "dataset1 <- subset(dataset1, Species == 'virginica')") -tdm <- teal_data_module( - ui = function(id) div(id = shiny::NS(id)("div_id")), - server = function(id) { - shiny::moduleServer(id, function(input, output, session) { - shiny::reactive(teal_data(IRIS = iris)) - }) - } -) -within(tdm, IRIS <- subset(IRIS, Species == "virginica")) +within(tdm, dataset1 <- subset(dataset1, Species == "virginica")) +# use additional parameter for expression value substitution. +valid_species <- "versicolor" +within(tdm, dataset1 <- subset(dataset1, Species \%in\% species), species = valid_species) } \seealso{ -\code{\linkS4class{teal_data}}, \code{\link[base:with]{base::within()}}, \code{\link[teal.code:qenv]{teal.code::within.qenv()}} +\code{\link[teal.data:teal_data-class]{teal.data::teal_data}}, \code{\link[teal.code:qenv]{teal.code::qenv()}} } diff --git a/man/teal_data_to_filtered_data.Rd b/man/teal_data_to_filtered_data.Rd index f3906674a2..5e7ac25b4f 100644 --- a/man/teal_data_to_filtered_data.Rd +++ b/man/teal_data_to_filtered_data.Rd @@ -12,9 +12,9 @@ teal_data_to_filtered_data(x, datanames = teal_data_datanames(x)) \item{datanames}{(\code{character}) vector of data set names to include; must be subset of \code{datanames(x)}} } \value{ -(\code{FilteredData}) object +A \code{FilteredData} object. } \description{ -Create a \code{FilteredData} object from a \code{teal_data} object +Create a \code{FilteredData} object from a \code{teal_data} object. } \keyword{internal} diff --git a/man/teal_modules.Rd b/man/teal_modules.Rd new file mode 100644 index 0000000000..e8e0f01fa3 --- /dev/null +++ b/man/teal_modules.Rd @@ -0,0 +1,166 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/modules.R +\name{teal_modules} +\alias{teal_modules} +\alias{module} +\alias{teal_module} +\alias{modules} +\alias{format.teal_module} +\alias{print.teal_module} +\alias{format.teal_modules} +\alias{print.teal_modules} +\title{Create \code{teal_module} and \code{teal_modules} objects.} +\usage{ +module( + label = "module", + server = function(id, ...) { + moduleServer(id, function(input, output, session) { + + }) + }, + ui = function(id, ...) { + tags$p(paste0("This module has no UI (id: ", id, " )")) + + }, + filters, + datanames = "all", + server_args = NULL, + ui_args = NULL +) + +modules(..., label = "root") + +\method{format}{teal_module}(x, indent = 0, ...) + +\method{print}{teal_module}(x, ...) + +\method{format}{teal_modules}(x, indent = 0, ...) + +\method{print}{teal_modules}(x, ...) +} +\arguments{ +\item{label}{(\code{character(1)}) Label shown in the navigation item for the module or module group. +For \code{modules()} defaults to \code{"root"}. See \code{Details}.} + +\item{server}{(\code{function}) \code{shiny} module with following arguments: +\itemize{ +\item \code{id} - \code{teal} will set proper \code{shiny} namespace for this module (see \code{\link[shiny:moduleServer]{shiny::moduleServer()}}). +\item \code{input}, \code{output}, \code{session} - (not recommended) then \code{\link[shiny:callModule]{shiny::callModule()}} will be used to call a module. +\item \code{data} (optional) module will receive a \code{teal_data} object, a list of reactive (filtered) data specified in +the \code{filters} argument. +\item \code{datasets} (optional) module will receive \code{FilteredData}. (See \code{\link[teal.slice:FilteredData]{teal.slice::FilteredData}}). +\item \code{reporter} (optional) module will receive \code{Reporter}. (See \code{\link[teal.reporter:Reporter]{teal.reporter::Reporter}}). +\item \code{filter_panel_api} (optional) module will receive \code{FilterPanelAPI}. (See \code{\link[teal.slice:FilterPanelAPI]{teal.slice::FilterPanelAPI}}). +\item \code{...} (optional) \code{server_args} elements will be passed to the module named argument or to the \code{...}. +}} + +\item{ui}{(\code{function}) \code{shiny} UI module function with following arguments: +\itemize{ +\item \code{id} - \code{teal} will set proper \code{shiny} namespace for this module. +\item \code{...} (optional) \code{ui_args} elements will be passed to the module named argument or to the \code{...}. +}} + +\item{filters}{(\code{character}) Deprecated. Use \code{datanames} instead.} + +\item{datanames}{(\code{character}) A vector with \code{datanames} that are relevant for the item. The +filter panel will automatically update the shown filters to include only +filters in the listed datasets. \code{NULL} will hide the filter panel, +and the keyword \code{"all"} will show filters of all datasets. \code{datanames} also determines +a subset of datasets which are appended to the \code{data} argument in server function.} + +\item{server_args}{(named \code{list}) with additional arguments passed on to the server function.} + +\item{ui_args}{(named \code{list}) with additional arguments passed on to the UI function.} + +\item{...}{\itemize{ +\item For \code{modules()}: (\code{teal_module} or \code{teal_modules}) Objects to wrap into a tab. +\item For \code{format()} and \code{print()}: Arguments passed to other methods. +}} + +\item{x}{(\code{teal_module} or \code{teal_modules}) Object to format/print.} + +\item{indent}{(\code{integer(1)}) Indention level; each nested element is indented one level more.} +} +\value{ +\code{module()} returns an object of class \code{teal_module}. + +\code{modules()} returns a \code{teal_modules} object which contains following fields: +\itemize{ +\item \code{label}: taken from the \code{label} argument. +\item \code{children}: a list containing objects passed in \code{...}. List elements are named after +their \code{label} attribute converted to a valid \code{shiny} id. +} +} +\description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} + +Create a nested tab structure to embed modules in a \code{teal} application. +} +\details{ +\code{module()} creates an instance of a \code{teal_module} that can be placed in a \code{teal} application. +\code{modules()} shapes the structure of a the application by organizing \code{teal_module} within the navigation panel. +It wraps \code{teal_module} and \code{teal_modules} objects in a \code{teal_modules} object, +which results in a nested structure corresponding to the nested tabs in the final application. + +Note that for \code{modules()} \code{label} comes after \code{...}, so it must be passed as a named argument, +otherwise it will be captured by \code{...}. + +The labels \code{"global_filters"} and \code{"Report previewer"} are reserved +because they are used by the \code{mapping} argument of \code{\link[=teal_slices]{teal_slices()}} +and the report previewer module \code{\link[=reporter_previewer_module]{reporter_previewer_module()}}, respectively. +} +\examples{ +library(shiny) + +module_1 <- module( + label = "a module", + server = function(id, data) { + moduleServer( + id, + module = function(input, output, session) { + output$data <- renderDataTable(data()[["iris"]]) + } + ) + }, + ui = function(id) { + ns <- NS(id) + tagList(dataTableOutput(ns("data"))) + }, + datanames = "all" +) + +module_2 <- module( + label = "another module", + server = function(id) { + moduleServer( + id, + module = function(input, output, session) { + output$text <- renderText("Another Module") + } + ) + }, + ui = function(id) { + ns <- NS(id) + tagList(textOutput(ns("text"))) + }, + datanames = NULL +) + +modules <- modules( + label = "modules", + modules( + label = "nested modules", + module_1 + ), + module_2 +) + +app <- init( + data = teal_data(iris = iris), + modules = modules +) + +if (interactive()) { + shinyApp(app$ui, app$server) +} +} diff --git a/man/teal_slices.Rd b/man/teal_slices.Rd index d7be32f4ab..bf84b842e2 100644 --- a/man/teal_slices.Rd +++ b/man/teal_slices.Rd @@ -4,7 +4,7 @@ \alias{teal_slices} \alias{as.teal_slices} \alias{c.teal_slices} -\title{Filter settings for teal applications} +\title{Filter settings for \code{teal} applications} \usage{ teal_slices( ..., @@ -25,9 +25,9 @@ as.teal_slices(x) \item{...}{any number of \code{teal_slice} objects. For \code{print} and \code{format}, additional arguments passed to other functions.} -\item{include_varnames, exclude_varnames}{(named \code{list}s of \code{character}) where list names +\item{include_varnames, exclude_varnames}{(\verb{named list}s of \code{character}) where list names match names of data sets and vector elements match variable names in respective data sets; -specify which variables are allowed to be filtered; see \code{Details}} +specify which variables are allowed to be filtered; see \code{Details}.} \item{count_type}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} \emph{This is a new feature. Do kindly share your opinions on @@ -44,7 +44,7 @@ Please make sure that adding new filters doesn't fail on target platform before \item{allow_add}{(\code{logical(1)}) logical flag specifying whether the user will be able to add new filters} -\item{module_specific}{optional (\code{logical(1)})\cr +\item{module_specific}{optional (\code{logical(1)}) \itemize{ \item \code{FALSE} (default) when one filter panel applied to all modules. All filters will be shared by all modules. @@ -52,17 +52,21 @@ All filters will be shared by all modules. Modules can have different set of filters specified - see \code{mapping} argument. }} -\item{mapping}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} \emph{This is a new feature. Do kindly share your opinions.\cr} -(\verb{named list})\cr -Specifies which filters will be active in which modules on app start. -Elements should contain character vector of \code{teal_slice} \code{id}s (see \code{\link[teal.slice:teal_slice]{teal.slice::teal_slice()}}). +\item{mapping}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} +\emph{This is a new feature. Do kindly share your opinions on +\href{https://github.com/insightsengineering/teal/}{\code{teal}'s GitHub repository}.} + +(named \code{list}) specifies which filters will be active in which modules on app start. +Elements should contain character vector of \code{teal_slice} \code{id}s (see \code{\link[teal.slice:teal_slice]{teal.slice::teal_slice}}). Names of the list should correspond to \code{teal_module} \code{label} set in \code{\link[=module]{module()}} function. -\code{id}s listed under \verb{"global_filters} will be active in all modules. -If missing, all filters will be applied to all modules. -If empty list, all filters will be available to all modules but will start inactive. -If \code{module_specific} is \code{FALSE}, only \code{global_filters} will be active on start.} +\itemize{ +\item \code{id}s listed under \verb{"global_filters} will be active in all modules. +\item If missing, all filters will be applied to all modules. +\item If empty list, all filters will be available to all modules but will start inactive. +\item If \code{module_specific} is \code{FALSE}, only \code{global_filters} will be active on start. +}} -\item{app_id}{(\code{character(1)})\cr +\item{app_id}{(\code{character(1)}) For internal use only, do not set manually. Added by \code{init} so that a \code{teal_slices} can be matched to the app in which it was used. Used for verifying snapshots uploaded from file. See \code{snapshot}.} @@ -83,12 +87,12 @@ See argument descriptions for details. } \examples{ filter <- teal_slices( - teal.slice::teal_slice(dataname = "iris", varname = "Species", id = "species"), - teal.slice::teal_slice(dataname = "iris", varname = "Sepal.Length", id = "sepal_length"), - teal.slice::teal_slice( + teal_slice(dataname = "iris", varname = "Species", id = "species"), + teal_slice(dataname = "iris", varname = "Sepal.Length", id = "sepal_length"), + teal_slice( dataname = "iris", id = "long_petals", title = "Long petals", expr = "Petal.Length > 5" ), - teal.slice::teal_slice(dataname = "mtcars", varname = "mpg", id = "mtcars_mpg"), + teal_slice(dataname = "mtcars", varname = "mpg", id = "mtcars_mpg"), mapping = list( module1 = c("species", "sepal_length"), module2 = c("mtcars_mpg"), @@ -96,8 +100,8 @@ filter <- teal_slices( ) ) -app <- teal::init( - data = list(iris = iris, mtcars = mtcars), +app <- init( + data = teal_data(iris = iris, mtcars = mtcars), modules = list( module("module1"), module("module2") @@ -111,6 +115,6 @@ if (interactive()) { } \seealso{ -\code{\link[teal.slice:teal_slices]{teal.slice::teal_slices}}, \code{\link[teal.slice:teal_slice]{teal.slice::teal_slice}}, \code{\link{slices_store}} +\code{\link[teal.slice:teal_slices]{teal.slice::teal_slices}}, \code{\link[teal.slice:teal_slice]{teal.slice::teal_slice}}, \code{\link[=slices_store]{slices_store()}} } \keyword{internal} diff --git a/man/ui_teal_with_splash.Rd b/man/ui_teal_with_splash.Rd deleted file mode 100644 index 90dce7e32d..0000000000 --- a/man/ui_teal_with_splash.Rd +++ /dev/null @@ -1,46 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/module_teal_with_splash.R -\name{ui_teal_with_splash} -\alias{ui_teal_with_splash} -\title{UI to show a splash screen in the beginning, then delegate to \code{\link[=srv_teal]{srv_teal()}}} -\usage{ -ui_teal_with_splash( - id, - data, - title = build_app_title(), - header = tags$p(), - footer = tags$p() -) -} -\arguments{ -\item{id}{(\code{character(1)})\cr -module id} - -\item{data}{(\code{teal_data}, \code{teal_data_module}, \verb{named list})\cr -\code{teal_data} object as returned by \code{\link[teal.data:teal_data]{teal.data::teal_data()}} or -\code{teal_data_module} or simply a list of a named list of objects -(\code{data.frame} or \code{MultiAssayExperiment}).} - -\item{title}{(\code{shiny.tag} or \code{character(1)})\cr -The browser window title. Defaults to a title "teal app" with the icon of NEST. -Can be created using the \code{build_app_title()} or -by passing a valid \code{shiny.tag} which is a head tag with title and link tag.} - -\item{header}{(\code{shiny.tag} or \code{character(1)}) \cr -The header of the app.} - -\item{footer}{(\code{shiny.tag} or \code{character(1)})\cr -The footer of the app.} -} -\description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} -The splash screen could be used to query for a password to fetch the data. -\code{\link[=init]{init()}} is a very thin wrapper around this module useful for end-users which -assumes that it is a top-level module and cannot be embedded. -This function instead adheres to the Shiny module conventions. - -If data is obtained through delayed loading, its splash screen is used. Otherwise, -a default splash screen is shown. - -Please also refer to the doc of \code{\link[=init]{init()}}. -} diff --git a/man/unfold_mapping.Rd b/man/unfold_mapping.Rd index ee6dda2e06..9a12a352d2 100644 --- a/man/unfold_mapping.Rd +++ b/man/unfold_mapping.Rd @@ -7,7 +7,7 @@ unfold_mapping(mapping, module_names) } \arguments{ -\item{mapping}{(\verb{named list}) as stored in mapping parameter of \code{teal_slices}} +\item{mapping}{(named \code{list}) as stored in mapping parameter of \code{teal_slices}} \item{module_names}{(\code{character}) vector containing names of all modules in the app} } diff --git a/man/validate_has_data.Rd b/man/validate_has_data.Rd index cea5906bb3..04b788d84f 100644 --- a/man/validate_has_data.Rd +++ b/man/validate_has_data.Rd @@ -13,16 +13,15 @@ validate_has_data( ) } \arguments{ -\item{x}{a data.frame} +\item{x}{(\code{data.frame})} -\item{min_nrow}{minimum number of rows in \code{x}} +\item{min_nrow}{(\code{numeric(1)}) Minimum allowed number of rows in \code{x}.} -\item{complete}{\code{logical} default \code{FALSE} when set to \code{TRUE} then complete cases are checked.} +\item{complete}{(\code{logical(1)}) Flag specifying whether to check only complete cases. Defaults to \code{FALSE}.} -\item{allow_inf}{\code{logical} default \code{TRUE} when set to \code{FALSE} then error thrown if any values are -infinite.} +\item{allow_inf}{(\code{logical(1)}) Flag specifying whether to allow infinite values. Defaults to \code{TRUE}.} -\item{msg}{(\code{character(1)}) additional message to display alongside the default message.} +\item{msg}{(\code{character(1)}) Additional message to display alongside the default message.} } \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} @@ -41,15 +40,15 @@ ui <- fluidPage( server <- function(input, output) { output$plot <- renderPlot({ - df <- iris[iris$Sepal.Length <= input$len, ] + iris_df <- iris[iris$Sepal.Length <= input$len, ] validate_has_data( - iris_f, + iris_df, min_nrow = 10, complete = FALSE, msg = "Please adjust Max Length of Sepal" ) - hist(iris_f$Sepal.Length, breaks = 5) + hist(iris_df$Sepal.Length, breaks = 5) }) } if (interactive()) { diff --git a/man/validate_has_variable.Rd b/man/validate_has_variable.Rd index e8cb84bfb8..873130c1f8 100644 --- a/man/validate_has_variable.Rd +++ b/man/validate_has_variable.Rd @@ -7,11 +7,11 @@ validate_has_variable(data, varname, msg) } \arguments{ -\item{data}{a data.frame} +\item{data}{(\code{data.frame})} -\item{varname}{name of variable in \code{data}} +\item{varname}{(\code{character(1)}) name of variable to check for in \code{data}} -\item{msg}{message to display if \code{data} does not include \code{varname}} +\item{msg}{(\code{character(1)}) message to display if \code{data} does not include \code{varname}} } \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} diff --git a/man/validate_in.Rd b/man/validate_in.Rd index 7020c29129..b703b5db29 100644 --- a/man/validate_in.Rd +++ b/man/validate_in.Rd @@ -7,11 +7,11 @@ validate_in(x, choices, msg) } \arguments{ -\item{x}{values to test. All must be in \code{choices}} +\item{x}{Vector of values to test.} -\item{choices}{a vector to test for values of \code{x}} +\item{choices}{Vector to test against.} -\item{msg}{warning message to display} +\item{msg}{(\code{character(1)}) Error message to display if some elements of \code{x} are not elements of \code{choices}.} } \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} diff --git a/man/validate_inputs.Rd b/man/validate_inputs.Rd index 3678af1ea7..64d63638fb 100644 --- a/man/validate_inputs.Rd +++ b/man/validate_inputs.Rd @@ -11,7 +11,7 @@ validate_inputs(..., header = "Some inputs require attention") or an optionally named, possibly nested \code{list} of \code{InputValidator} objects, see \code{Details}} -\item{header}{\code{character(1)} generic validation message; set to NULL to omit} +\item{header}{(\code{character(1)}) generic validation message; set to NULL to omit} } \value{ Returns NULL if the final validation call passes and a \code{shiny.silent.error} if it fails. diff --git a/man/validate_no_intersection.Rd b/man/validate_no_intersection.Rd index 1b0507a883..244114ce58 100644 --- a/man/validate_no_intersection.Rd +++ b/man/validate_no_intersection.Rd @@ -11,7 +11,7 @@ validate_no_intersection(x, y, msg) \item{y}{vector} -\item{msg}{message to display if \code{x} and \code{y} intersect} +\item{msg}{(\code{character(1)}) message to display if \code{x} and \code{y} intersect} } \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} diff --git a/man/validate_one_row_per_id.Rd b/man/validate_one_row_per_id.Rd index 054da7b30e..3e883dfee3 100644 --- a/man/validate_one_row_per_id.Rd +++ b/man/validate_one_row_per_id.Rd @@ -7,9 +7,9 @@ validate_one_row_per_id(x, key = c("USUBJID", "STUDYID")) } \arguments{ -\item{x}{a data.frame} +\item{x}{(\code{data.frame})} -\item{key}{a vector of ID variables from \code{x} that identify unique records} +\item{key}{(\code{character}) Vector of ID variables from \code{x} that identify unique records.} } \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} diff --git a/teal.Rproj b/teal.Rproj index 64681752f2..ab99014abb 100644 --- a/teal.Rproj +++ b/teal.Rproj @@ -19,5 +19,5 @@ BuildType: Package PackageUseDevtools: Yes PackageInstallArgs: --no-multiarch --with-keep.source PackageBuildBinaryArgs: --no-build-vignettes -PackageCheckArgs: --no-build-vignettes +PackageCheckArgs: --as-cran PackageRoxygenize: rd,collate,namespace diff --git a/tests/testthat/helper-functions.R b/tests/testthat/helper-functions.R deleted file mode 100644 index 36e1ed008b..0000000000 --- a/tests/testthat/helper-functions.R +++ /dev/null @@ -1,46 +0,0 @@ -#' Load current package from proper folder before running tests for shiny app using \code{shinytest} package -#' -#' @param package_name (\code{character}) name of the package where the test runs ins -#' @return An expression -#' @export -#' -#' @examples -#' if (interactive()) { -#' # start of shinytest script -#' eval(shinytest_load_pckg()) -#' # continue with shinytest script -#' } -#' -shinytest_load_pckg <- function(package_name = NULL) { - pth_source <- file.path("..", "..", "..") - pckg_dir <- file.path(pth_source, "00_pkg_src") - if (dir.exists(pckg_dir)) { - # for devtools::check (i.e. R CMD CHECK) - pckg_path_name <- list.files(pckg_dir, full.names = TRUE) - str <- paste0("\"", pckg_path_name, "\"") - out <- parse( - text = paste0("devtools::load_all(path = ", str, ", export_all = FALSE)"), - keep.source = FALSE - ) - } else if (file.exists(file.path(pth_source, "DESCRIPTION"))) { - # for devtools::test, devtools::test_file, testthat::... - out <- parse( - text = paste0("devtools::load_all(\"", pth_source, "\", export_all = FALSE)"), - keep.source = FALSE - ) - } else { - # for interactive() usage - if (is.null(package_name)) { - out <- parse( - text = "library(devtools::as.package(\".\")$package, character.only = T)", - keep.source = FALSE - ) - } else { - out <- parse( - text = paste0("library(", package_name, ")"), - keep.source = FALSE - ) - } - } - return(out) -} diff --git a/tests/testthat/setup-skip_if_too_deep.R b/tests/testthat/setup-skip_if_too_deep.R index 7e707f6a62..309d6fa627 100644 --- a/tests/testthat/setup-skip_if_too_deep.R +++ b/tests/testthat/setup-skip_if_too_deep.R @@ -23,25 +23,25 @@ testing_depth <- function() { # nolint # nousage } #' Skipping tests in the testthat pipeline under specific scope -#' @description This function should be used per each \code{testthat::test_that} call. +#' +#' @description This function should be used per each `testthat::test_that` call. #' Each of the call should specify an appropriate depth value. #' The depth value will set the appropriate scope so more/less time consuming tests could be recognized. -#' The environment variable \code{TESTING_DEPTH} is used for changing the scope of \code{testthat} pipeline. -#' \code{TESTING_DEPTH} interpretation for each possible value: -#' \itemize{ -#' \item{0}{no tests at all} -#' \item{1}{fast - small scope - executed on every commit} -#' \item{3}{medium - medium scope - daily integration pipeline} -#' \item{5}{slow - all tests - daily package tests} +#' The environment variable `TESTING_DEPTH` is used for changing the scope of `testthat` pipeline. +#' `TESTING_DEPTH` interpretation for each possible value: +#' - no tests at all} +#' - fast - small scope - executed on every commit +#' - medium - medium scope - daily integration pipeline +#' - slow - all tests - daily package tests #' } -#' @param depth \code{numeric} the depth of the testing evaluation, -#' has opposite interpretation to environment variable \code{TESTING_DEPTH}. +#' @param depth `numeric` the depth of the testing evaluation, +#' has opposite interpretation to environment variable `TESTING_DEPTH`. #' So e.g. `0` means run it always and `5` means a heavy test which should be run rarely. -#' If the \code{depth} argument is larger than \code{TESTING_DEPTH} then the test is skipped. +#' If the `depth` argument is larger than `TESTING_DEPTH` then the test is skipped. #' @importFrom testthat skip -#' @return \code{NULL} or invoke an error produced by \code{testthat::skip} -#' @note By default \code{TESTING_DEPTH} is equal to 3 if there is no environment variable for it. -#' By default \code{depth} argument lower or equal to 3 will not be skipped because by default \code{TESTING_DEPTH} +#' @return `NULL` or invoke an error produced by `testthat::skip`. +#' @note By default `TESTING_DEPTH` is equal to 3 if there is no environment variable for it. +#' By default `depth` argument lower or equal to 3 will not be skipped because by default `TESTING_DEPTH` #' is equal to 3. To skip <= 3 depth tests then the environment variable has to be lower than 3 respectively. skip_if_too_deep <- function(depth) { # nolintr checkmate::assert_number(depth, lower = 0, upper = 5) diff --git a/tests/testthat/test-init.R b/tests/testthat/test-init.R index 8a7a56f134..cc3d40e857 100644 --- a/tests/testthat/test-init.R +++ b/tests/testthat/test-init.R @@ -1,108 +1,76 @@ +# data ---- testthat::test_that("init data accepts teal_data object", { testthat::expect_no_error( init( data = teal.data::teal_data(iris = iris), - modules = modules(teal:::example_module()) - ) - ) -}) - -testthat::test_that("init data accepts a single dataframe", { - testthat::expect_no_error( - init(data = list(iris = iris), modules = modules(example_module())) - ) -}) - -testthat::test_that("init data accepts a list of single dataframe without renaming", { - testthat::skip("todo: should we support data as unnamed list in teal?") - testthat::expect_no_error( - init(data = list(iris, mtcars), modules = modules(example_module())) - ) -}) - -testthat::test_that("init data accepts a list of single dataframe with renaming", { - testthat::expect_no_error( - init( - data = list(iris2 = iris), modules = modules(example_module()) ) ) }) -testthat::test_that("init data accepts a single MultiAssayExperiment object", { - utils::data(miniACC, package = "MultiAssayExperiment") - testthat::expect_no_error( - init(data = list(MAE = miniACC), modules = modules(example_module())) - ) -}) - -testthat::test_that("init data accepts a list of a single MultiAssayExperiment object with renaming", { - utils::data(miniACC, package = "MultiAssayExperiment") - testthat::expect_no_error(init(data = list(x = miniACC), modules = modules(example_module()))) -}) - -testthat::test_that("init data acceptsa mixed list of MultiAssayExperiment object and data.frame", { - utils::data(miniACC, package = "MultiAssayExperiment") - testthat::expect_no_error(init(data = list(x = miniACC, y = head(iris)), modules = modules(example_module()))) -}) - testthat::test_that("init data accepts teal_data_module", { testthat::expect_no_error( init( data = teal_data_module(ui = function(id) div(), server = function(id) NULL), - modules = modules(teal:::example_module()) + modules = modules(example_module()) ) ) }) + +# modules ---- testthat::test_that("init modules accepts a teal_modules object", { mods <- modules(example_module(), example_module()) - testthat::expect_no_error(init(data = list(iris = iris), modules = mods)) + testthat::expect_no_error(init(data = teal.data::teal_data(iris = iris), modules = mods)) }) testthat::test_that("init modules accepts a list of teal_module elements", { mods <- list(example_module(), example_module()) - testthat::expect_no_error(init(data = list(iris = iris), modules = mods)) + testthat::expect_no_error(init(data = teal.data::teal_data(iris = iris), modules = mods)) }) testthat::test_that("init modules accepts a teal_module object", { mods <- example_module() - testthat::expect_no_error(init(data = list(iris = iris), modules = mods)) + testthat::expect_no_error(init(data = teal.data::teal_data(iris = iris), modules = mods)) }) +# filter ---- testthat::test_that("init filter accepts `teal_slices`", { fs <- teal.slice::teal_slices( teal.slice::teal_slice(dataname = "iris", varname = "species", selected = "setosa") ) - testthat::expect_no_error(init(data = list(iris = iris), modules = modules(example_module()), filter = fs)) + testthat::expect_no_error( + init(data = teal.data::teal_data(iris = iris), modules = modules(example_module()), filter = fs) + ) testthat::expect_error( - init(data = list(iris = iris), modules = modules(example_module()), filter = unclass(fs)), + init(data = teal.data::teal_data(iris = iris), modules = modules(example_module()), filter = unclass(fs)), "Assertion on 'filter' failed" ) }) -testthat::test_that("init throws when data has no datanames", { +# data + modules ---- +testthat::test_that("init throws when an empty `data` is used", { testthat::expect_error( - init(data = teal_data(), modules = list(example_module())), - "`data` object has no datanames and its environment is empty" + init(data = teal.data::teal_data(), modules = list(example_module())), + "The environment of `data` is empty." ) }) -testthat::test_that("init throws when incompatible module's datanames", { +testthat::test_that("init throws when datanames in modules incompatible w/ datanames in data", { msg <- "Module 'example teal module' uses datanames not available in 'data'" testthat::expect_error( init( - data = teal_data(mtcars = mtcars), + data = teal.data::teal_data(mtcars = mtcars), modules = list(example_module(datanames = "iris")) ), "Module 'example teal module' uses datanames not available in 'data'" ) }) -testthat::test_that("init throws when incompatible filter's datanames", { +testthat::test_that("init throws when dataname in filter incompatible w/ datanames in data", { testthat::expect_warning( init( - data = teal_data(mtcars = mtcars), + data = teal.data::teal_data(mtcars = mtcars), modules = modules(example_module()), filter = teal_slices(teal_slice(dataname = "iris", varname = "Species")) ), diff --git a/tests/testthat/test-module_nested_tabs.R b/tests/testthat/test-module_nested_tabs.R index 3f07fc6a87..3468505249 100644 --- a/tests/testthat/test-module_nested_tabs.R +++ b/tests/testthat/test-module_nested_tabs.R @@ -357,8 +357,8 @@ testthat::test_that("srv_nested_tabs.teal_module passes filter_panel_api to the testthat::test_that(".datasets_to_data returns data which is filtered", { datasets <- get_example_filtered_data() datasets$set_filter_state( - teal.slice:::teal_slices( - teal.slice:::teal_slice(dataname = "d1", varname = "val", selected = c(1, 2)) + teal.slice::teal_slices( + teal.slice::teal_slice(dataname = "d1", varname = "val", selected = c(1, 2)) ) ) module <- test_module_wdata(datanames = c("d1", "d2")) @@ -392,19 +392,22 @@ testthat::test_that(".datasets_to_data returns teal_data object", { ) # code - skip("skipped until we resolve handling code in teal.data:::new_teal_data") testthat::expect_equal( teal.code::get_code(data), - c( - get_rcode_str_install(), - get_rcode_libraries(), - "d1 <- data.frame(id = 1:5, pk = c(2, 3, 2, 1, 4), val = 1:5)\n\n", - "d2 <- data.frame(id = 1:5, value = 1:5)\n\n", - paste0( - "stopifnot(rlang::hash(d1) == \"f6f90d2c133ca4abdeb2f7a7d85b731e\")\n", - "stopifnot(rlang::hash(d2) == \"6e30be195b7d914a1311672c3ebf4e4f\") \n\n" + paste( + c( + get_rcode_str_install(), + get_rcode_libraries(), + "d1 <- data.frame(id = 1:5, pk = c(2, 3, 2, 1, 4), val = 1:5)", + "d2 <- data.frame(id = 1:5, value = 1:5)", + "", + "stopifnot(rlang::hash(d1) == \"f6f90d2c133ca4abdeb2f7a7d85b731e\")", + "stopifnot(rlang::hash(d2) == \"6e30be195b7d914a1311672c3ebf4e4f\")", + "", + "d2 <- dplyr::inner_join(x = d2, y = d1[, c(\"pk\"), drop = FALSE], by = c(id = \"pk\"))", + "" ), - "" + collapse = "\n" ) ) }) @@ -456,9 +459,9 @@ testthat::test_that("calculate_hashes returns the hash of the non Filtered datas list(iris = list(dataset = iris)) ) - fs <- teal.slice:::teal_slices( - teal.slice:::teal_slice(dataname = "iris", varname = "Sepal.Length", selected = c(5.1, 6.4)), - teal.slice:::teal_slice(dataname = "iris", varname = "Species", selected = c("setosa", "versicolor")) + fs <- teal.slice::teal_slices( + teal.slice::teal_slice(dataname = "iris", varname = "Sepal.Length", selected = c(5.1, 6.4)), + teal.slice::teal_slice(dataname = "iris", varname = "Species", selected = c("setosa", "versicolor")) ) shiny::isolate(datasets$set_filter_state(state = fs)) diff --git a/tests/testthat/test-module_teal.R b/tests/testthat/test-module_teal.R index 0850a68aac..e7e6bb3cc6 100644 --- a/tests/testthat/test-module_teal.R +++ b/tests/testthat/test-module_teal.R @@ -4,7 +4,7 @@ testthat::test_that("srv_teal fails when teal_data_rv is not reactive", { app = srv_teal, args = list( id = "test", - teal_data_rv = teal_data(iris = iris), + teal_data_rv = teal.data::teal_data(iris = iris), modules = modules(example_module()) ), expr = NULL @@ -14,7 +14,7 @@ testthat::test_that("srv_teal fails when teal_data_rv is not reactive", { }) testthat::test_that("srv_teal when teal_data_rv changes, datasets_reactive is initialized as list of FilteredData", { - data <- teal_data(iris1 = iris, mtcars1 = mtcars) + data <- teal.data::teal_data(iris1 = iris, mtcars1 = mtcars) shiny::testServer( app = srv_teal, args = list( @@ -33,7 +33,7 @@ testthat::test_that("srv_teal when teal_data_rv changes, datasets_reactive is in }) testthat::test_that("srv_teal initialized datasets_reactive (list) reflects modules structure", { - data <- teal_data(iris1 = iris, mtcars1 = mtcars) + data <- teal.data::teal_data(iris1 = iris, mtcars1 = mtcars) shiny::testServer( app = srv_teal, args = list( @@ -53,7 +53,7 @@ testthat::test_that("srv_teal initialized datasets_reactive (list) reflects modu }) testthat::test_that("srv_teal initialized data containing same FilteredData when the filter is global", { - data <- teal_data(iris1 = iris, mtcars1 = mtcars) + data <- teal.data::teal_data(iris1 = iris, mtcars1 = mtcars) shiny::testServer( app = srv_teal, args = list( @@ -75,7 +75,7 @@ testthat::test_that("srv_teal initialized data containing same FilteredData when }) testthat::test_that("srv_teal initialized data containing different FilteredData when the filter is module_specific", { - data <- teal_data(iris1 = iris, mtcars1 = mtcars) + data <- teal.data::teal_data(iris1 = iris, mtcars1 = mtcars) shiny::testServer( app = srv_teal, args = list( diff --git a/tests/testthat/test-module_teal_with_splash.R b/tests/testthat/test-module_teal_with_splash.R index 8ad4e8ea99..4301e32d68 100644 --- a/tests/testthat/test-module_teal_with_splash.R +++ b/tests/testthat/test-module_teal_with_splash.R @@ -47,7 +47,7 @@ testthat::test_that("srv_teal_with_splash passes teal_data to reactive", { app = srv_teal_with_splash, args = list( id = "test", - data = teal_data(iris = iris), + data = teal.data::teal_data(iris = iris), modules = modules(example_module()) ), expr = { @@ -63,7 +63,7 @@ testthat::test_that("srv_teal_with_splash passes when datanames are empty with w app = srv_teal_with_splash, args = list( id = "test", - data = teal_data(), + data = teal.data::teal_data(), modules = modules(example_module()) ), expr = { @@ -100,7 +100,7 @@ testthat::test_that("srv_teal_with_splash teal_data_rv_validate throws then qenv id = "test", data = teal_data_module( ui = function(id) div(), - server = function(id) reactive(within(teal_data(), stop("not good"))) + server = function(id) reactive(within(teal.data::teal_data(), stop("not good"))) ), modules = modules(example_module()) ), @@ -137,7 +137,7 @@ testthat::test_that("srv_teal_with_splash teal_data_rv_validate throws when inco app = srv_teal_with_splash, args = list( id = "test", - data = teal_data(mtcars = mtcars, iris = iris, npk = npk), + data = teal.data::teal_data(mtcars = mtcars, iris = iris, npk = npk), modules = modules(example_module(datanames = "non-existing")) ), expr = { @@ -155,7 +155,7 @@ testthat::test_that("srv_teal_with_splash teal_data_rv_validate returns teal_dat app = srv_teal_with_splash, args = list( id = "test", - data = teal_data(mtcars = mtcars), + data = teal.data::teal_data(mtcars = mtcars), modules = modules(example_module(datanames = "mtcars")), filter = teal_slices(teal_slice(dataname = "iris", varname = "Species")) ), @@ -175,7 +175,7 @@ testthat::test_that("srv_teal_with_splash gets observe event from srv_teal", { app = srv_teal_with_splash, args = list( id = "test", - data = teal_data(), + data = teal.data::teal_data(), modules = modules(example_module()) ), expr = { diff --git a/tests/testthat/test-modules.R b/tests/testthat/test-modules.R index 547a467967..6dfbcaee13 100644 --- a/tests/testthat/test-modules.R +++ b/tests/testthat/test-modules.R @@ -27,7 +27,7 @@ testthat::test_that("module requires label argument to be a string different tha testthat::test_that("module warns when server contains datasets argument", { testthat::expect_warning( module(server = function(id, datasets) NULL), - "`datasets` argument in the `server` is deprecated" + "`datasets` argument in the server is deprecated" ) }) @@ -66,7 +66,7 @@ testthat::test_that("module expects all server_args being a server arguments or testthat::expect_error( module(server = function(id) NULL, server_args = list(arg1 = NULL)), - "Following `server_args` elements have no equivalent in the formals of the `server`" + "Following `server_args` elements have no equivalent in the formals of the server" ) }) @@ -99,7 +99,7 @@ testthat::test_that("module expects all ui_args being a ui arguments or passed t testthat::expect_error( module(ui = function(id) NULL, ui_args = list(arg1 = NULL)), - "Following `ui_args` elements have no equivalent in the formals of `ui`" + "Following `ui_args` elements have no equivalent in the formals of UI" ) }) @@ -376,12 +376,12 @@ testthat::test_that("modules_depth increases depth by 1 for each teal_modules", # is_arg_used ----- get_srv_and_ui <- function() { - return(list( + list( server_fun = function(id, datasets) {}, # nolint ui_fun = function(id, ...) { tags$p(paste0("id: ", id)) } - )) + ) } testthat::test_that("is_arg_used throws error if object is not teal_module or teal_modules", { @@ -495,3 +495,20 @@ testthat::test_that("append_module produces teal_modules with unique named child mod_names <- names(appended_mods$children) testthat::expect_equal(mod_names, unique(mod_names)) }) + + +# format ---------------------------------------------------------------------------------------------------------- + +testthat::test_that("format.teal_modules returns proper structure", { + mod <- module(label = "a") + mod2 <- module(label = "c") + mods <- modules(label = "c", mod, mod2) + mod3 <- module(label = "c") + + appended_mods <- append_module(mods, mod3) + + testthat::expect_equal( + format(appended_mods), + "+ c\n + a\n + c\n + c\n" + ) +}) diff --git a/tests/testthat/test-rcode_utils.R b/tests/testthat/test-rcode_utils.R index 1add79e18d..2d7cc4b946 100644 --- a/tests/testthat/test-rcode_utils.R +++ b/tests/testthat/test-rcode_utils.R @@ -20,19 +20,23 @@ testthat::test_that("With teal.load_nest_code option set to character get_rcode_ }) -testthat::test_that("With teal.load_nest_code option is not character get_rcode_str_install (silently) returns - default string", { - withr::with_options( - list(teal.load_nest_code = TRUE), - testthat::expect_equal(get_rcode_str_install(), "# Add any code to install/load your NEST environment here\n") - ) - +testthat::test_that( + paste( + "When teal.load_nest_code option is not character", + "get_rcode_str_install (silently) returns default string" + ), + { + withr::with_options( + list(teal.load_nest_code = TRUE), + testthat::expect_equal(get_rcode_str_install(), "# Add any code to install/load your NEST environment here\n") + ) - withr::with_options( - list(teal.load_nest_code = list(x = "boo")), - testthat::expect_equal(get_rcode_str_install(), "# Add any code to install/load your NEST environment here\n") - ) -}) + withr::with_options( + list(teal.load_nest_code = list(x = "boo")), + testthat::expect_equal(get_rcode_str_install(), "# Add any code to install/load your NEST environment here\n") + ) + } +) testthat::test_that("get_rcode_libraries returns current session packages", { diff --git a/tests/testthat/test-tdata.R b/tests/testthat/test-tdata.R index daf6979b19..ada4ebae18 100644 --- a/tests/testthat/test-tdata.R +++ b/tests/testthat/test-tdata.R @@ -218,7 +218,7 @@ testthat::test_that("join_keys returns join_keys object if it exists inside tdat code <- c("iris <- iris", "mtcars <- mtcars") data_tdata <- teal::new_tdata(list(iris = iris, mtcars = mtcars), code) data_teal_data <- teal.data::teal_data(iris = iris, mtcars = mtcars, code = code) -data_reactive <- reactive(teal.data::teal_data(iris = iris, mtcars = mtcars, code = code)) +data_reactive <- shiny::reactive(teal.data::teal_data(iris = iris, mtcars = mtcars, code = code)) testthat::test_that("as_tdata accepts all possible inputs", { testthat::expect_no_error(as_tdata(data_tdata)) @@ -250,9 +250,8 @@ testthat::test_that("datasets are maintained during conversion", { testthat::expect_identical(datasets_teal_data, datasets_tdata) }) -testthat::test_that("code is maintained during conversion", { +testthat::test_that("as_tdata maintains code during conversion", { data_teal_data_downgraded <- as_tdata(data_teal_data) - skip("skipped until we resolve handling code in teal.data:::new_teal_data") testthat::expect_identical( teal.code::get_code(data_teal_data), shiny::isolate(attr(data_teal_data_downgraded, "code")()) diff --git a/tests/testthat/test-teal_data_module-eval_code.R b/tests/testthat/test-teal_data_module-eval_code.R index 9c3fb95551..06f5638110 100644 --- a/tests/testthat/test-teal_data_module-eval_code.R +++ b/tests/testthat/test-teal_data_module-eval_code.R @@ -107,7 +107,7 @@ testthat::test_that("eval_code.teal_data_module propagates qenv error from the o server = function(id) { shiny::moduleServer(id, function(input, output, session) { reactive( - within(teal_data(IRIS = iris), "non_existing_var + 1") + within(teal.data::teal_data(IRIS = iris), "non_existing_var + 1") ) }) } diff --git a/tests/testthat/test-teal_slices-store.R b/tests/testthat/test-teal_slices-store.R index eeb28824ce..8cdd377a8a 100644 --- a/tests/testthat/test-teal_slices-store.R +++ b/tests/testthat/test-teal_slices-store.R @@ -9,7 +9,7 @@ testthat::test_that("teal_slice store/restore supports NULL and character(0) for ) ) slices_store(tss, slices_path) - tss_restored <- teal:::slices_restore(slices_path) + tss_restored <- slices_restore(slices_path) slices2_path <- withr::local_file("slices2.json") tss2 <- teal_slices( diff --git a/tests/testthat/test-teal_slices.R b/tests/testthat/test-teal_slices.R index 1c556d2496..7f0a86e0f8 100644 --- a/tests/testthat/test-teal_slices.R +++ b/tests/testthat/test-teal_slices.R @@ -28,7 +28,6 @@ testthat::test_that("teal_slices fails when inexisting teal_slice id is specifie ) }) - testthat::test_that("teal_slices processes filter mapping", { # if missing, all filters are global tss <- teal_slices( @@ -84,35 +83,33 @@ testthat::test_that("teal_slices drops non-global filters if module_specific = F testthat::expect_true(is.list(attr(tss, "mapping")) && length(attr(tss, "mapping")) == 0L) }) - - - - - - - -testthat::test_that("deep_copy_filters copies teal_slice changes pointer of teal_slice object - but values remain the same", { - tss <- teal_slices( - teal.slice::teal_slice(dataname = "data", varname = "var1", choices = c("a", "b"), selected = "a"), - teal.slice::teal_slice(dataname = "data", varname = "var2", choices = c("A", "B"), selected = "B") - ) - tss_copy <- deep_copy_filter(tss) - - testthat::expect_false(identical(tss, tss_copy)) - testthat::expect_identical( - { - tss_temp <- lapply(tss, as.list) - attributes(tss_temp) <- attributes(tss) - tss_temp - }, - { - tss_copy_temp <- lapply(tss_copy, as.list) - attributes(tss_copy_temp) <- attributes(tss_copy) - tss_copy_temp - } - ) -}) +testthat::test_that( + paste( + "deep_copy_filters copies teal_slice changes", + "pointer of teal_slice object but values remain the same" + ), + { + tss <- teal_slices( + teal.slice::teal_slice(dataname = "data", varname = "var1", choices = c("a", "b"), selected = "a"), + teal.slice::teal_slice(dataname = "data", varname = "var2", choices = c("A", "B"), selected = "B") + ) + tss_copy <- deep_copy_filter(tss) + + testthat::expect_false(identical(tss, tss_copy)) + testthat::expect_identical( + { + tss_temp <- lapply(tss, as.list) + attributes(tss_temp) <- attributes(tss) + tss_temp + }, + { + tss_copy_temp <- lapply(tss_copy, as.list) + attributes(tss_copy_temp) <- attributes(tss_copy) + tss_copy_temp + } + ) + } +) diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index 77561d3ce9..9cef8a8410 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -100,3 +100,98 @@ test_that("build_app_title builts a valid tag", { testthat::expect_silent(validate_app_title_tag(valid_title_local)) testthat::expect_silent(validate_app_title_tag(valid_title_remote)) }) + + +# create_app_id ---- +testthat::test_that("create_app_id: 'data' accepts teal_data or teal_data_module", { + testthat::expect_no_error(create_app_id(teal.data::teal_data(), modules(example_module()))) + + tdm <- teal_data_module( + ui = function(id) div(), + server = function(id) NULL + ) + testthat::expect_no_error(create_app_id(tdm, modules(example_module()))) + + testthat::expect_error( + create_app_id(iris, modules(example_module())), + "Assertion on 'data' failed: Must inherit from class 'teal_data'/'teal_data_module'" + ) +}) + +testthat::test_that("create_app_id: 'modules' accepts modules", { + testthat::expect_no_error(create_app_id(teal.data::teal_data(), modules(example_module()))) + + testthat::expect_error( + create_app_id(teal.data::teal_data(), example_module()), + "Assertion on 'modules' failed: Must inherit from class 'teal_modules'" + ) +}) + +testthat::test_that("create_app_id returns a character string", { + checkmate::expect_string(create_app_id(teal.data::teal_data(), modules(example_module()))) +}) + +testthat::test_that("create_app_id returns different hash for different data", { + hash1 <- create_app_id(teal.data::teal_data(i = iris), modules(example_module())) + hash2 <- create_app_id(teal.data::teal_data(i = mtcars), modules(example_module())) + testthat::expect_failure(testthat::expect_identical(hash1, hash2)) +}) + +testthat::test_that("create_app_id returns different hash for different modules", { + hash1 <- create_app_id(teal.data::teal_data(i = iris), modules(example_module())) + hash2 <- create_app_id(teal.data::teal_data(i = iris), modules(example_module(), example_module())) + testthat::expect_failure(testthat::expect_identical(hash1, hash2)) +}) + +## defunction ---- +testthat::test_that("defunction returns a string when passed a function", { + checkmate::expect_string(defunction(init)) +}) + +testthat::test_that("defunction returns non-function atomic as is", { + testthat::expect_identical( + defunction("character"), + "character" + ) + testthat::expect_identical( + defunction(c(TRUE, FALSE)), + c(TRUE, FALSE) + ) + testthat::expect_identical( + defunction(1:3), + 1:3 + ) + testthat::expect_identical( + defunction(1:3 * 1), + 1:3 * 1 + ) +}) + +testthat::test_that("defunction recursively goes down a list", { + # styler: off + x <- list( + "character" = "character", + "function1" = function(x) return(x), + "list2" = list( + "function2" = function(x) mean(x), + "list3" = list( + "function3" = function(data) summary(data) + ) + ) + ) + # styler: on + y <- list( + "character" = "character", + "function1" = "return(x)", + "list2" = list( + "function2" = "mean(x)", + "list3" = list( + "function3" = "summary(data)" + ) + ) + ) + testthat::expect_identical( + defunction(x), + y + ) +}) diff --git a/vignettes/adding-support-for-reporting.Rmd b/vignettes/adding-support-for-reporting.Rmd index a4d305736c..c08a88ae5b 100644 --- a/vignettes/adding-support-for-reporting.Rmd +++ b/vignettes/adding-support-for-reporting.Rmd @@ -33,22 +33,26 @@ Let us consider an example module, based on the example module from `teal`: library(teal) example_module <- function(label = "example teal module") { module( - label, + label = label, server = function(id, data) { + checkmate::assert_class(data, "reactive") + checkmate::assert_class(isolate(data()), "teal_data") + moduleServer(id, function(input, output, session) { - ns <- session$ns updateSelectInput(session, "dataname", choices = isolate(datanames(data()))) - output$text <- renderPrint(data()[[input$dataname]]) + output$dataset <- renderPrint({ + req(input$dataname) + data()[[input$dataname]] + }) }) }, ui = function(id) { ns <- NS(id) - teal.widgets::standard_layout( - output = verbatimTextOutput(ns("text")), - encoding = selectInput(ns("dataname"), "Choose a dataset", choices = NULL) + sidebarLayout( + sidebarPanel(selectInput(ns("dataname"), "Choose a dataset", choices = NULL)), + mainPanel(verbatimTextOutput(ns("dataset"))) ) - }, - datanames = "all" + } ) } ``` @@ -75,22 +79,23 @@ See below: ```{r} example_module_with_reporting <- function(label = "example teal module") { module( - label, + label = label, server = function(id, data, reporter) { moduleServer(id, function(input, output, session) { - ns <- session$ns updateSelectInput(session, "dataname", choices = isolate(datanames(data()))) - output$text <- renderPrint(data()[[input$dataname]]) + output$dataset <- renderPrint({ + req(input$dataname) + data()[[input$dataname]] + }) }) }, ui = function(id) { ns <- NS(id) - teal.widgets::standard_layout( - output = verbatimTextOutput(ns("text")), - encoding = selectInput(ns("dataname"), "Choose a dataset", choices = NULL) + sidebarLayout( + sidebarPanel(selectInput(ns("dataname"), "Choose a dataset", choices = NULL)), + mainPanel(verbatimTextOutput(ns("dataset"))) ) - }, - datanames = "all" + } ) } ``` @@ -108,7 +113,7 @@ if (interactive()) shinyApp(app$ui, app$server) `teal` adds another tab to the application, titled `Report previewer`. However, there is no visible change in how the module operates and appears and the user cannot add content to the report from this module. -That requires inserting `ui` and `server` elements of the `teal.reporter` module into the module body. +That requires inserting UI and server elements of the `teal.reporter` module into the module body. ### Insert `teal.reporter` module @@ -117,7 +122,7 @@ The UI and the server logic necessary for adding cards from `example_module_with ```{r} example_module_with_reporting <- function(label = "example teal module") { module( - label, + label = label, server = function(id, data, reporter) { moduleServer(id, function(input, output, session) { teal.reporter::simple_reporter_srv( @@ -125,22 +130,23 @@ example_module_with_reporting <- function(label = "example teal module") { reporter = reporter, card_fun = function(card) card ) - ns <- session$ns updateSelectInput(session, "dataname", choices = isolate(datanames(data()))) - output$text <- renderPrint(data()[[input$dataname]]) + output$dataset <- renderPrint({ + req(input$dataname) + data()[[input$dataname]] + }) }) }, ui = function(id) { ns <- NS(id) - teal.widgets::standard_layout( - output = tagList( + sidebarLayout( + sidebarPanel( teal.reporter::simple_reporter_ui(ns("reporter")), - verbatimTextOutput(ns("text")) + selectInput(ns("dataname"), "Choose a dataset", choices = NULL) ), - encoding = selectInput(ns("dataname"), "Choose a dataset", choices = NULL) + mainPanel(verbatimTextOutput(ns("dataset"))) ) - }, - datanames = "all" + } ) } ``` @@ -178,7 +184,7 @@ custom_function <- function(card = teal.reporter::ReportCard$new()) { example_module_with_reporting <- function(label = "example teal module") { module( - label, + label = label, server = function(id, data, reporter) { moduleServer(id, function(input, output, session) { teal.reporter::simple_reporter_srv( @@ -186,22 +192,23 @@ example_module_with_reporting <- function(label = "example teal module") { reporter = reporter, card_fun = custom_function ) - ns <- session$ns updateSelectInput(session, "dataname", choices = isolate(datanames(data()))) - output$text <- renderPrint(data()[[input$dataname]]) + output$dataset <- renderPrint({ + req(input$dataname) + data()[[input$dataname]] + }) }) }, ui = function(id) { ns <- NS(id) - teal.widgets::standard_layout( - output = tagList( + sidebarLayout( + sidebarPanel( teal.reporter::simple_reporter_ui(ns("reporter")), - verbatimTextOutput(ns("text")) + selectInput(ns("dataname"), "Choose a dataset", choices = NULL) ), - encoding = selectInput(ns("dataname"), "Choose a dataset", choices = NULL) + mainPanel(verbatimTextOutput(ns("dataset"))) ) - }, - datanames = "all" + } ) } ``` @@ -241,10 +248,10 @@ Without this definition, the API of `TealReportCard` will not be available withi ## Example In conclusion, we have demonstrated how to build a standard `teal` app with code reproducibility and reporter functionalities. -Note that the `server` function requires the `filter_panel_api` argument so that the filter panel state can be added to the report. +Note that the server function requires the `filter_panel_api` argument so that the filter panel state can be added to the report. In the final example, we have incorporated `teal.code` snippets. -`teal.code` is an R library that offers utilities for storing code and associating it with an execution environment. +`teal.code` is an `R` library that offers utilities for storing code and associating it with an execution environment. This allows `ReporterCard` to store the code necessary to generate the table along with the table itself. To learn more about `teal.code` see the vignette _`qenv`_ in `teal.code`. @@ -254,11 +261,10 @@ library(teal.reporter) example_reporter_module <- function(label = "Example") { module( - label, + label = label, server = function(id, data, reporter, filter_panel_api) { with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelApi") moduleServer(id, function(input, output, session) { - ns <- session$ns updateSelectInput(session, "dataname", choices = isolate(datanames(data()))) dat <- reactive(data()[[input$dataname]]) observe({ @@ -318,9 +324,17 @@ example_reporter_module <- function(label = "Example") { }, ui = function(id) { ns <- NS(id) - teal.widgets::standard_layout( - output = tableOutput(ns("table")), - encoding = tagList( + + sidebarLayout( + sidebarPanel(selectInput(ns("dataname"), "Choose a dataset", choices = NULL)), + mainPanel( + teal.reporter::simple_reporter_ui(ns("reporter")), + verbatimTextOutput(ns("dataset")) + ) + ) + + sidebarLayout( + sidebarPanel( div( teal.reporter::add_card_button_ui(ns("addReportCard")), teal.reporter::download_report_button_ui(ns("downloadButton")), @@ -328,10 +342,10 @@ example_reporter_module <- function(label = "Example") { ), selectInput(ns("dataname"), "Choose a dataset", choices = NULL), sliderInput(ns("nrow"), "Number of rows", min = 1, max = 1, value = 1, step = 1) - ) + ), + mainPanel(tableOutput(ns("table"))) ) - }, - datanames = "all" + } ) } diff --git a/vignettes/blueprint/actors.Rmd b/vignettes/blueprint/actors.Rmd index 35bcce157b..380ad23408 100644 --- a/vignettes/blueprint/actors.Rmd +++ b/vignettes/blueprint/actors.Rmd @@ -36,13 +36,13 @@ style D fill:pink The primary responsibility of a `teal` app developer is to leverage the available building blocks of the `teal` framework to create a functional `teal` app that analyses the data. -To expedite the app creation process, developers can take advantage of pre-existing `teal` modules found in R packages like `teal.modules.general` and `teal.modules.clinical`. +To expedite the app creation process, developers can take advantage of pre-existing `teal` modules found in `R` packages like `teal.modules.general` and `teal.modules.clinical`. These modules are designed with a focus on standardization and versatility, making them suitable for a wide range of use cases. When developing a `teal` app, the developer will select the most appropriate `teal` modules and integrate them into the app's interface to ensure seamless usability for end-users. -To learn more about the existing modules, visit [`teal.gallery`](https://insightsengineering.github.io/teal.gallery/) that contains several demo applications and their source code. +To learn more about the existing modules, visit [`teal.gallery`](https://insightsengineering.github.io/teal.gallery/), which contains several demo applications and their source code. ## `teal` module developer @@ -73,7 +73,7 @@ However, developers have the freedom to create a `teal` module that is customize Ultimately, one or more `teal` modules are employed to construct a `teal` app. -To learn more about creating custom modules follow the [Tutorial on Creating a Custom Module](https://insightsengineering.github.io/teal/latest-tag/articles/creating-custom-modules.html). +To learn more about creating custom modules follow the [Tutorial on Creating a Custom Module](creating-custom-modules.html). ## Workflow in a clinical trial study @@ -113,7 +113,7 @@ style C2 fill:gold In a clinical trial study setting, a unique **study `teal` app developer** is assigned to each study team and is accountable for developing a tailored `teal` app for their respective study. -The **study `teal` app developer** will initially leverage existing `teal` modules from R packages created by **`teal` module developers**. +The **study `teal` app developer** will initially leverage existing `teal` modules from `R` packages created by **`teal` module developers**. In cases where there is a need to create new modules tailored to the study, a **study `teal` module developer** will need to be involved. diff --git a/vignettes/blueprint/dataflow.Rmd b/vignettes/blueprint/dataflow.Rmd index 70eead1701..c0b68ec1a1 100644 --- a/vignettes/blueprint/dataflow.Rmd +++ b/vignettes/blueprint/dataflow.Rmd @@ -29,7 +29,7 @@ sequenceDiagram; ) ``` -The sequence diagram above illustrates the different stages that data goes through within the `teal` framework, supported by the `teal.slice` R package: +The sequence diagram above illustrates the different stages that data goes through within the `teal` framework, supported by the `teal.slice` package: 1. Data is created and loaded into `teal` app; - Data sets are wrapped in a `teal_data` before being passed to the app; diff --git a/vignettes/blueprint/filter_panel.Rmd b/vignettes/blueprint/filter_panel.Rmd index 47a4126ef7..435cca3a20 100644 --- a/vignettes/blueprint/filter_panel.Rmd +++ b/vignettes/blueprint/filter_panel.Rmd @@ -49,7 +49,7 @@ The filter panel creates subsets of data. Subsetting is achieved by creating fil The process is entirely interactive. Filter states can be created, removed, and changed at will, however, the app developer may choose to constrain or even restrict them. -When a filter state is created, the filter panel generates a _filter card_ with `shiny` inputs appropriate to the type of the variable, e.g. range selectors for numeric columns and sets of checkboxes or dropdown menus for categorical ones. +When a filter state is created, the filter panel generates a _filter card_ with `shiny` inputs appropriate to the type of the variable, e.g. range selectors for numeric columns and sets of checkboxes or drop-down menus for categorical ones. As users interact with the filter cards, the subsetting complete expression is updated and filtered data is recomputed. diff --git a/vignettes/blueprint/in_app_data.Rmd b/vignettes/blueprint/in_app_data.Rmd index d6f6dcfab0..c41e1e9cc5 100644 --- a/vignettes/blueprint/in_app_data.Rmd +++ b/vignettes/blueprint/in_app_data.Rmd @@ -23,4 +23,4 @@ This allows the app developer to include user actions data creation, fetching, a ## Further reading -A complete explanation of using the `teal_data_module` can be found in [this `teal` vignette](https://insightsengineering.github.io/teal/latest-tag/articles/data-as-shiny-module.html) +A complete explanation of using the `teal_data_module` can be found in [this `teal` vignette](data-as-shiny-module.html) diff --git a/vignettes/blueprint/input_data.Rmd b/vignettes/blueprint/input_data.Rmd index cf602c4980..0b138becbf 100644 --- a/vignettes/blueprint/input_data.Rmd +++ b/vignettes/blueprint/input_data.Rmd @@ -23,7 +23,7 @@ The `teal_data` class, which serves as the primary data interface for `teal` app ## Preparing data for a `teal` application All `teal` applications run on data provided in a `teal_data` object. -Data objects are stored and modified within the environment of the `teal_data` object and all R code used is tracked, which allows for the code to be evaluated and executed in the `teal` application, and reproduced outside the `teal` application. +Data objects are stored and modified within the environment of the `teal_data` object and all `R` code used is tracked, which allows for the code to be evaluated and executed in the `teal` application, and reproduced outside the `teal` application. This includes data loading, preprocessing, filtering, transformations, and plotting, etc. The `teal_data` object makes it easy for users to reproduce and validate the results of their analyses. @@ -48,8 +48,8 @@ Learn more about the use of `teal_data` in the [`teal.data` package vignettes](h ## `Show R Code` and `Reporter` -In both the `teal.modules.clinical` and `teal.modules.general` R packages, you'll find that most modules include a convenient `Show R Code` button. -When this button is clicked, a modal window appears, revealing the R code responsible for generating the module's output. +In both the `teal.modules.clinical` and `teal.modules.general` packages, you'll find that most modules include a convenient `Show R Code` button. +When this button is clicked, a modal window appears, revealing the `R` code responsible for generating the module's output. This functionality is achieved by inspecting the `teal_data` object and retrieving code from it. With the `Show R Code` button, users can easily copy and independently run the code to reproduce the analysis presented in the teal module. @@ -60,6 +60,6 @@ Much like the `Show R Code` mechanism, the code displayed in a Reporter Card is ![Reporter](../../man/figures/reporter.jpg){width=50%} -To learn more about the `Reporter` feature, please visit the [teal.reporter documentation](https://insightsengineering.github.io/teal.reporter/latest-tag/index.html). +To learn more about the `Reporter` feature, please visit the [`teal.reporter` documentation](https://insightsengineering.github.io/teal.reporter/latest-tag/index.html). -Overall, `qenv` from `teal.code` and its child class, `teal_data`, are powerful tools for ensuring code reproducibility and promoting high-quality research in the R programming language. +Overall, `qenv` from `teal.code` and its child class, `teal_data`, are powerful tools for ensuring code reproducibility and promoting high-quality research in the `R` programming language. diff --git a/vignettes/blueprint/intro.Rmd b/vignettes/blueprint/intro.Rmd index 0537ce52e5..deed98a6d3 100644 --- a/vignettes/blueprint/intro.Rmd +++ b/vignettes/blueprint/intro.Rmd @@ -34,4 +34,4 @@ The `teal` framework's functionality draws heavily from the following packages: Although these packages are mentioned in the material, we strongly recommend visiting their vignettes to learn more about them. -Learn on how to make your first `teal` application [here](https://insightsengineering.github.io/teal/main/articles/teal.html)! +Learn on how to make your first `teal` application [here](getting-started-with-teal.html)! diff --git a/vignettes/blueprint/module_encapsulation.Rmd b/vignettes/blueprint/module_encapsulation.Rmd index fd15e4ca31..fe7ec07c8e 100644 --- a/vignettes/blueprint/module_encapsulation.Rmd +++ b/vignettes/blueprint/module_encapsulation.Rmd @@ -15,16 +15,16 @@ The `teal` framework leverages the [`shiny` module concept](https://rstudio.gith ## Benefits -By implementing the modular app technique from the shiny module into the creation of the teal module, several benefits are realized: +By implementing the modular app technique from the shiny module into the creation of the `teal` module, several benefits are realized: 1. Streamlined maintenance - The development of the teal module becomes more manageable, as it can function independently from the teal framework. - This separation allows developers to maintain the module with ease. This approach has been successfully applied in R packages dedicated to teal module development, such as `teal.modules.general` and `teal.modules.clinical`. + The development of the `teal` module becomes more manageable, as it can function independently from the `teal` framework. + This separation allows developers to maintain the module with ease. This approach has been successfully applied in `R` packages dedicated to `teal` module development, such as `teal.modules.general` and `teal.modules.clinical`. 1. Enhanced focus on output - `teal` module developers can concentrate solely on refining parameters or encoding, and output aspects (such as data summarization and visualization) without the need to concern themselves with the intricacies of the teal framework. + `teal` module developers can concentrate solely on refining parameters or encoding, and output aspects (such as data summarization and visualization) without the need to concern themselves with the intricacies of the `teal` framework. When developed correctly, the module seamlessly integrates with `teal`. 1. Facilitated collaboration `teal` module development becomes an accessible entry point for developers interested in collaborating. - This approach encourages user collaboration for the improvement of teal modules, as developers gain a deeper understanding of the mechanics of the teal framework. + This approach encourages user collaboration for the improvement of `teal` modules, as developers gain a deeper understanding of the mechanics of the `teal` framework. diff --git a/vignettes/blueprint/product_map.Rmd b/vignettes/blueprint/product_map.Rmd index 3949c3d8ae..1a523d3706 100644 --- a/vignettes/blueprint/product_map.Rmd +++ b/vignettes/blueprint/product_map.Rmd @@ -62,21 +62,18 @@ style modules fill:pink ) ``` -`teal` is a modular framework that relies on a suite of related R packages, as illustrated in the above diagram, to provide a wide range of functionalities. +`teal` is a modular framework that relies on a suite of related packages, as illustrated in the above diagram, to provide a wide range of functionalities. -`teal`'s primary function is to create web app for analysing clinical trial data but it **has** a multitude of features distributed across various R packages. +`teal`'s primary function is to create web app for analyzing clinical trial data but it **has** a multitude of features distributed across various packages. -Developers can selectively leverage these R packages, such as `teal.widgets`, `teal.code`, and `teal.logger`, to **build** `teal` modules for a `teal` app. This approach gives the developers the tools that speed up their work and avoid re-implementing existing logic and UI elements. +Developers can selectively leverage these packages, such as `teal.widgets`, `teal.code`, and `teal.logger`, to **build** `teal` modules for a `teal` app. +This approach gives the developers the tools that speed up their work and avoid re-implementing existing logic and UI elements. -The `teal` modules utilize various R packages such as `tern`, `osprey`, and `goshawk` to perform calculations and analyses. -These R packages provide **support** to the `teal` modules by performing all computations while the modules only have to focus on how to wrap the input options and the output. +The `teal` modules utilize various packages such as `tern`, `osprey`, and `goshawk` to perform calculations and analyses. +These packages provide **support** to the `teal` modules by performing all computations while the modules only have to focus on how to wrap the input options and the output. Once developed, new and existing modules can be integrated into `teal` to **create** a functional `teal` app. -## Why so many R packages? +## Why so many packages? -By breaking down `teal` features, modules, and calculations into dedicated R packages, maintenance is made significantly more manageable. - -Additionally, this separation allows for a clear distinction between the various roles and actors involved in the development of `teal`. - -To learn different roles/actors in `teal` development, visit [Actors](actors.html). +By breaking down `teal` features, modules, and calculations into dedicated packages, maintenance is made significantly more manageable. diff --git a/vignettes/bootstrap-themes-in-teal.Rmd b/vignettes/bootstrap-themes-in-teal.Rmd index 117677bfab..9ee5fee52c 100644 --- a/vignettes/bootstrap-themes-in-teal.Rmd +++ b/vignettes/bootstrap-themes-in-teal.Rmd @@ -10,15 +10,15 @@ vignette: > ## Introduction -We offer an easy application of a custom Bootstrap theme in a `teal` app. `teal` uses the `bslib` R package which provides tools for customizing Bootstrap themes, including those of `shiny` apps. +We offer an easy application of a custom Bootstrap theme in a `teal` app. `teal` uses the `bslib` `R` package which provides tools for customizing Bootstrap themes, including those of `shiny` apps. ## Usage -`teal` app developers can specify custom Bootstrap themes by setting the `teal.bs_theme` R option, which has to be set to `bslib::bs_theme` object. The `bslib::bs_theme(...)` function creates a Bootstrap theme object, where one specifies the (major) Bootstrap version (default or one of 3, 4 or 5). Optionally one can choose a **`bootswatch` theme** and **customize the app CSS** with functions like `bslib::bs_add_rules`. Please read more about custom themes in [the `bslib` getting started vignette](https://rstudio.github.io/bslib/articles/bslib.html). The `teal.bs_theme` R option has to be specified at the top of the code script. +`teal` app developers can specify custom Bootstrap themes by setting the `teal.bs_theme` `R` option, which has to be set to `bslib::bs_theme` object. The `bslib::bs_theme(...)` function creates a Bootstrap theme object, where one specifies the (major) Bootstrap version (default or one of 3, 4, or 5). Optionally one can choose a **`bootswatch` theme** and **customize the app CSS** with functions like `bslib::bs_add_rules`. Please read more about custom themes in [the `bslib` getting started vignette](https://rstudio.github.io/bslib/articles/bslib.html). The `teal.bs_theme` `R` option has to be specified at the top of the code script. Please install `bslib` package before you run the code below. -### `teal.bs_theme` R option +### `teal.bs_theme` `R` option ``` options("teal.bs_theme" = bslib::bs_theme("Custom Options")) @@ -44,7 +44,7 @@ options("teal.bs_theme" = bslib::bs_theme_update(bslib::bs_theme(version = "5"), ### Default Bootstrap theme -When using the default `bslib` theme for any version (3, 4 or 5), its styling might not be as expected. Please run the interactive `themer` (recommended) or apply a custom theme to explore all the theme options. **In many scenarios updating only the theme might not be enough and e.g. font color and other specifications should be updated too.** +When using the default `bslib` theme for any version (3, 4 or 5), its styling might not be as expected. Please run the interactive themer (recommended) or apply a custom theme to explore all the theme options. **In many scenarios updating only the theme might not be enough and e.g. font color and other specifications should be updated too.** ``` # instead of @@ -60,7 +60,7 @@ Please use the `options("teal.bs_theme" = NULL)` call to return to the default ` ### Theme not updated -One reason the theme is not updated could be that the web browser caches the previous one, especially different themes are run one after another. Please, use the `Cmd+Shift+R` (Mac) or `Ctrl+F5` (Windows) to hard refresh the webpage. +One reason the theme is not updated could be that the web browser caches the previous one, especially when different themes are run one after another. Please, use the `Cmd+Shift+R` (Mac) or `Ctrl+F5` (Windows) to hard refresh the webpage. ### Custom `teal` `CSS` @@ -118,7 +118,7 @@ Instead of starting from scratch, we want to start with a [`Bootswatch`](https:/ -`bslib` has updated our `CSS` styles to use our new theme, including the `customizer` theme. Additionally if we look at our R console, we will see +`bslib` has updated our `CSS` styles to use our new theme, including the `customizer` theme. Additionally, if we look at our `R` console, we will see ```{r eval = FALSE} #### Update your bs_theme() R code with: ##### @@ -129,7 +129,7 @@ This is a helpful guide that provides code to update our theme. For `teal` appli #### Customize a `bootswatch` theme -Our base theme (Minty) is close to what we want but let's make a few modifications. To start, we will increase the base font size. To do this, we choose the "Fonts" section of the `customizer` theme and then set a value in the "Base font size" input. We use 1.25 here, which means all our fonts will be increased by a factor of 1.25. If we check the R console, we will see `bslib` has printed `bs_theme_update(theme, font_scale = 1.25, bootswatch = "minty")`, which now includes our font size adjustment. +Our base theme (Minty) is close to what we want but let's make a few modifications. To start, we will increase the base font size. To do this, we choose the "Fonts" section of the `customizer` theme and then set a value in the "Base font size" input. We use 1.25 here, which means all our fonts will be increased by a factor of 1.25. If we check the `R` console, we will see `bslib` has printed `bs_theme_update(theme, font_scale = 1.25, bootswatch = "minty")`, which now includes our font size adjustment. @@ -137,11 +137,11 @@ Finally, suppose we do not want borders to be rounded. In our `customizer` theme -As expected, our corners are no longer rounded. If we look at our R console, we will now see `bs_theme_update(theme, font_scale = 1.25, `enable-rounded` = FALSE, bootswatch = "minty")`. +As expected, our corners are no longer rounded. If we look at our `R` console, we will now see `bs_theme_update(theme, font_scale = 1.25, `enable-rounded` = FALSE, bootswatch = "minty")`. #### Apply the customized theme -Once our customization is complete, we will apply the changes to our application. To do this, we use the option `teal.bs_theme` like before but this time we will expand on our `bslib::bs_theme` call to include our changes. Luckily, the arguments that were printed to the R console when running our app in the themer can be plugged right in. +Once our customization is complete, we will apply the changes to our application. To do this, we use the option `teal.bs_theme` like before but this time we will expand on our `bslib::bs_theme` call to include our changes. Luckily, the arguments that were printed to the `R` console when running our app in the themer can be plugged right in. ```{r eval = FALSE} options( @@ -168,7 +168,7 @@ Now the application has our custom theme applied. -Please note the interactive themer only contains the mostly commonly applied options. +Please note the interactive themer only contains the most commonly applied options. For more customization options, review the `bslib` documentation. diff --git a/vignettes/creating-custom-modules.Rmd b/vignettes/creating-custom-modules.Rmd index 26102d6d32..6353512e57 100644 --- a/vignettes/creating-custom-modules.Rmd +++ b/vignettes/creating-custom-modules.Rmd @@ -1,7 +1,9 @@ --- title: "Creating Custom Modules" author: "NEST CoreDev" -output: rmarkdown::html_vignette +output: + rmarkdown::html_vignette: + toc: true vignette: > %\VignetteIndexEntry{Creating Custom Modules} %\VignetteEngine{knitr::rmarkdown} @@ -10,61 +12,25 @@ vignette: > ## Introduction -The `teal` framework provides a large number of analysis modules to be incorporated into `teal` applications. +The `teal` framework provides a large catalog of plug-in-ready analysis modules to be incorporated into `teal` applications. However, it is also possible to create your own modules using the `module` function. -Here is an implementation of a simple module: - -```{r, message=FALSE} -library(teal) -example_module <- function(label = "example teal module") { - checkmate::assert_string(label) - module( - label, - server = function(id, data) { - checkmate::assert_class(data, "reactive") - checkmate::assert_class(isolate(data()), "teal_data") - moduleServer(id, function(input, output, session) { - ns <- session$ns - updateSelectInput(session, "dataname", choices = isolate(datanames(data()))) - output$text <- renderPrint(data()[[input$dataname]]) - }) - }, - ui = function(id) { - ns <- NS(id) - teal.widgets::standard_layout( - output = verbatimTextOutput(ns("text")), - encoding = selectInput(ns("dataname"), "Choose a dataset", choices = NULL) - ) - }, - datanames = "all" - ) -} -``` - -which can be added into `teal` apps using `example_module(label = "Label for tab")`. - ## Components of a module ### UI function This function contains the UI required for the module. -It should be a function with at least the arguments `id`. -It can also contain the argument `data` for access to the application data. +It should be a function with at least the argument `id`. See the server section below for more details. -The UI function can contain standard UI components alongside additional widgets provided by the `teal.widgets` package. -In the example above we are using the `standard_layout` function of `teal.widgets` which generates a layout -including an encoding panel on the left and main output covering the rest of the module's UI. - ### Server function -This function contains the shiny server logic for the module and should be of the form: +This function contains the `shiny` server logic for the module and should be of the form: ```{r, eval=FALSE} function( id, - data, # optional; use if module needs access application data + data, # optional; use if module needs access to application data filter_panel_api, # optional; use if module needs access to filter panel; see teal.slice reporter, # optional; use if module supports reporting; see reporting vignette ...) { @@ -74,18 +40,54 @@ function( } ``` -When used inside a `teal` application called with `init`, the `data` argument is a named list of reactive `data.frame`s containing the data after having been filtered through the filter panel. -It is of the `tdata` type and can be created using the `new_tdata` function. +The data that arrives in the module is a `teal_data` object, the data container used throughout the `teal` application. +`teal_data` is passed to the `init` function when building the application and, after filtering by the filter panel, it is passed to modules, wrapped in a reactive expression. +The `teal_data` class allows modules to track the `R` code that they execute so that module outputs can be reproduced. +See the `teal.data` package for a [detailed explanation](https://insightsengineering.github.io/teal.data/latest-tag/articles/teal-data-reproducibility.html). -## A more complicated example +## Example modules -The `teal` framework also provides: +### Viewing data -- A way to create modules which then generate the R code needed to reproduce their outputs; these modules use the [`teal.code`](https://insightsengineering.github.io/teal.code/) package. +Here is a minimal module that allows the user to select and view one dataset at a time. +By default, filtering is enabled for all datasets. +Note that dataset choices are specified by the `datanames` property of the `teal_data` container. -The annotated example below demonstrates these features within a simple histogram module, allowing app developers to choose the data and columns that their app users can select for display in a histogram. +```{r, message=FALSE} +library(teal) -See the package and function documentation for further details. +example_module <- function(label = "example teal module") { + checkmate::assert_string(label) + + module( + label = label, + server = function(id, data) { + checkmate::assert_class(data, "reactive") + checkmate::assert_class(isolate(data()), "teal_data") + + moduleServer(id, function(input, output, session) { + updateSelectInput(session, "dataname", choices = isolate(datanames(data()))) + output$dataset <- renderPrint({ + req(input$dataname) + data()[[input$dataname]] + }) + }) + }, + ui = function(id) { + ns <- NS(id) + sidebarLayout( + sidebarPanel(selectInput(ns("dataname"), "Choose a dataset", choices = NULL)), + mainPanel(verbatimTextOutput(ns("dataset"))) + ) + } + ) +} +``` + +### Interacting with data and viewing code + +The example below allows the user to interact with the data to create a simple visualization. +In addition, it prints the code that can be used to reproduce that visualization. ```{r} library(teal) @@ -94,35 +96,34 @@ library(teal) # allows for selecting dataset and one of its numeric variables ui_histogram_example <- function(id) { ns <- NS(id) - teal.widgets::standard_layout( - output = plotOutput(ns("plot")), - encoding = div( + sidebarLayout( + sidebarPanel( selectInput(ns("datasets"), "select dataset", choices = NULL), selectInput(ns("numerics"), "select numeric variable", choices = NULL) ), - # we have a show R code button to show the code needed - # to generate the histogram - forms = teal.widgets::verbatim_popup_ui(ns("rcode"), "Show R code") + mainPanel( + plotOutput(ns("plot")), + verbatimTextOutput(ns("code")) + ), ) } # server function for the module # presents datasets and numeric variables for selection # displays a histogram of the selected variable +# displays code to reproduce the histogram srv_histogram_example <- function(id, data) { checkmate::assert_class(data, "reactive") checkmate::assert_class(isolate(data()), "teal_data") moduleServer(id, function(input, output, session) { - ns <- session$ns - # update dataset and variable choices # each selection stored in separate reactive expression - updateSelectInput(session, "datasets", "select dataset", choices = isolate(datanames(data()))) + updateSelectInput(inputId = "datasets", choices = isolate(datanames(data()))) observe({ req(dataset()) nums <- vapply(data()[[dataset()]], is.numeric, logical(1L)) - updateSelectInput(session, "numerics", "select numeric variable", choices = names(nums[nums])) + updateSelectInput(inputId = "numerics", choices = names(nums[nums])) }) dataset <- reactive(input$datasets) selected <- reactive(input$numerics) @@ -147,12 +148,12 @@ srv_histogram_example <- function(id, data) { plot_code_q()[["p"]] }) - # code upon clicking 'Show R Code' button - teal.widgets::verbatim_popup_srv( - id = "rcode", - verbatim_content = reactive(teal.code::get_code(plot_code_q())), - title = "R Code" - ) + # view code + output$code <- renderPrint({ + plot_code_q() %>% + get_code() %>% + cat() + }) }) } @@ -167,14 +168,9 @@ tm_histogram_example <- function(label) { } ``` -An example `teal` application using this module is shown below: - -Teal Duck - +This module is ready to be used in a `teal` app. ```{r} -library(teal) - app <- init( data = teal_data(IRIS = iris, NPK = npk), modules = tm_histogram_example(label = "Histogram Module"), @@ -186,13 +182,11 @@ if (interactive()) { } ``` -## `shiny` input cycle - -When `teal` modules are run inside the `init` the initial shiny input cycle is empty for each of them. -In practice, this means that some inputs might be initialized with `NULL` value, unnecessary triggering some observers. -A developer has to be aware of this situation as often it will require `shiny::req` or `ignoreInit` argument in observers or `reactive` expressions. -This side effect is caused by the `shiny::insertUI` function. -We are aware of this inconvenience and have already started to look for a solution. +Teal Duck ## Adding reporting to a module -Refer to `vignette("adding_support_for_reporting")` to read about adding support for reporting in your `teal` module. +Refer to [this vignette](adding-support-for-reporting.html) to read about adding support for reporting in your `teal` module. + +## Using standard widgets in your custom module + +The [`teal.widgets`](https://insightsengineering.github.io/teal.widgets/latest-tag/) package provides various widgets which can be leveraged to quickly create standard elements in your custom module. diff --git a/vignettes/data-as-shiny-module.Rmd b/vignettes/data-as-shiny-module.Rmd index b7d08bfe69..d64dfa5f0c 100644 --- a/vignettes/data-as-shiny-module.Rmd +++ b/vignettes/data-as-shiny-module.Rmd @@ -12,24 +12,24 @@ vignette: > ## Introduction -Proper functioning of any `teal` application requires presence of a `teal_data` object. +Proper functioning of any `teal` application requires the presence of a `teal_data` object. Typically, a `teal_data` object created in the global environment will be passed to the `data` argument in `init`. This `teal_data` object should contain all elements necessary for successful execution of the application's modules. -In some scenarios, however, application developers may opt to postpone some data operations until the application run time. +In some scenarios, however, application developers may opt to postpone some data operations until the application runtime. This can be done by passing a special _`shiny` module_ to the `data` argument. The `teal_data_module` function is used to build such a module from the following components: -- a `ui` function; accepts only one argument, `id`; defines user interface elements for the data module -- a `server` function: accepts only one argument, `id`; defines server logic for the data module, including data creation; must return a reactive expression containing a `teal_data` object +- a UI function; accepts only one argument, `id`; defines user interface elements for the data module +- a server function: accepts only one argument, `id`; defines server logic for the data module, including data creation; must return a reactive expression containing a `teal_data` object `teal` will run this module when the application starts and the resulting `teal_data` object that will be used throughout all `teal` (analytic) modules. ## Creating data in-app -One case for postponing data operations are data sets that are dynamic, frequently updated. +One case for postponing data operations is datasets that are dynamic, frequently updated. Such data cannot be created once and kept in the global environment. -Using `teal_data_module` allows to create a data set from scratch every time the user starts the application. +Using `teal_data_module` enables creating a dataset from scratch every time the user starts the application. ```{r, message = FALSE, warning = FALSE} library(teal) @@ -37,7 +37,7 @@ library(teal) ```{r} -data_mod <- teal_data_module( +data_module <- teal_data_module( ui = function(id) div(), server = function(id) { moduleServer(id, function(input, output, session) { @@ -49,7 +49,7 @@ data_mod <- teal_data_module( dataset2 <- mtcars } ) - datanames(data) <- c("dataset1", "dataset2") + datanames(data) <- c("dataset1", "dataset2") # optional data }) }) @@ -58,8 +58,8 @@ data_mod <- teal_data_module( app <- init( - data = data_mod, - module = example_module() + data = data_module, + modules = example_module() ) if (interactive()) { @@ -67,10 +67,11 @@ if (interactive()) { } ``` +_See `?qenv` for a detailed explanation of how to use the `within` method._ ## Modification of data in-app -Another reason to postpone data operations is to allow the application user to act the preprocessing stage. +Another reason to postpone data operations is to involve the application user in the preprocessing stage. An initial, constant form of the data can be created in the global environment and then modified once the app starts. The following example illustrates how `teal_data_module` can be utilized to subset data based on the user inputs: @@ -82,11 +83,11 @@ data <- within(teal_data(), { }) datanames(data) <- c("dataset1", "dataset2") -data_mod <- teal_data_module( +data_module <- teal_data_module( ui = function(id) { ns <- NS(id) div( - selectInput(ns("species"), "Select species to filter", + selectInput(ns("species"), "Select species to keep", choices = unique(iris$Species), multiple = TRUE ), actionButton(ns("submit"), "Submit") @@ -107,8 +108,8 @@ data_mod <- teal_data_module( ) app <- init( - data = data_mod, - module = example_module() + data = data_module, + modules = example_module() ) if (interactive()) { @@ -116,26 +117,25 @@ if (interactive()) { } ``` -_See `?within.qenv` for a detailed explanation of how to use the `within` method._ - Note that running preprocessing code in a module as opposed to the global environment will increase app loading times. It is recommended to keep the constant code in the global environment and to move only the dynamic parts to a data module. ###### WARNING -When using `teal_data_module` to modify a pre-existing `teal_data` object it is crucial that the server function and the data object are defined in the same environment as otherwise the server function will not be able to access the data object. +When using `teal_data_module` to modify a pre-existing `teal_data` object, it is crucial that the server function and the data object are defined in the same environment, otherwise the server function will not be able to access the data object. This means server functions defined in packages cannot be used. ### Extending existing `teal_data_modules` -The `teal_data_module` can be further modified outside of the initial `shiny` module and processed after user inputs. -The `within` function allows to process at runtime the data in the `teal_data` object contained in `teal_data_module`. +The server logic of a `teal_data_module` can be modified before it is used in an app, using the `within` function. +This allows the `teal_data` object that is created in the `teal_data_module` to be processed further. -Building on the previous example, the `data_mod` is handled as a generic connector and here new columns are added once the data is retrieved. +In the previous example, `data_module` takes a predefined `teal_data` object and allows the app user to select a subset. +The following example modifies `data_module` so that new columns are added once the data is retrieved. ```{r} -data_mod_2 <- within( - data_mod, +data_module_2 <- within( + data_module, { # Create new column with Ratio of Sepal.Width and Petal.Width dataset1$Ratio.Sepal.Petal.Width <- round(dataset1$Sepal.Width / dataset1$Petal.Width, digits = 2L) @@ -145,8 +145,8 @@ data_mod_2 <- within( ) app <- init( - data = data_mod_2, - module = example_module() + data = data_module_2, + modules = example_module() ) if (interactive()) { diff --git a/vignettes/filter-panel.Rmd b/vignettes/filter-panel.Rmd index 97261eb7f9..d6528d63af 100644 --- a/vignettes/filter-panel.Rmd +++ b/vignettes/filter-panel.Rmd @@ -15,7 +15,7 @@ Based on the selections made in the filter panel, filter expressions are execute The technical details of the filter panel are extensively described in [`teal.slice` documentation](https://insightsengineering.github.io/teal.slice/latest-tag/). By default, `init` initializes the filter panel without any active filters but allows the user to add filters on any column. -To start a `teal` application with predefined filters, one must to specify the `filter` argument. +To start a `teal` application with predefined filters, one must specify the `filter` argument. In the following example four filters are specified using the `teal_slice` function and wrapped together with `teal_slices`. ```r @@ -64,7 +64,7 @@ if (interactive()) { ### Global and module specific filter panel `teal` contains the `teal_slices` function that extends the original `teal_slices` found in `teal.slice` by adding two arguments: `module_specific` and `mapping`. -By default `init` initializes app with a "global" filter panel, where all modules use the same filters. +By default `init` initializes the app with a "global" filter panel, where all modules use the same filters. Setting `module_specific = TRUE` switches to a "module-specific" filter panel, where each module can have a different set of filters active at any time. It is still possible to set global filters that will be shared among modules. @@ -76,12 +76,15 @@ One possible scenario is depicted in the figure below: - `filter 4` is used only by `module 1` - `filter 5` and `filter 6` are not active in any of the modules -![](./images/filters_mapping.jpg) + +![](./images/filters-mapping.svg) To achieve the described setup, one must set the `module_specific` argument to `TRUE` and use the `mapping` argument to match filters to modules. `mapping` takes a named list where element names correspond to module labels, and elements are vectors of `teal_slice` `id`s applied to that module at startup. `teal_slice`s listed the element called `"global_filters"` will be applied to all modules. +For a detailed explanation about the filter states, see [this `teal.slice` vignette](https://insightsengineering.github.io/teal.slice/latest-tag/articles/filter-panel-for-developers.html). + ```r library(teal) diff --git a/vignettes/getting-started-with-teal.Rmd b/vignettes/getting-started-with-teal.Rmd index 3c918f3160..f1947a53fb 100644 --- a/vignettes/getting-started-with-teal.Rmd +++ b/vignettes/getting-started-with-teal.Rmd @@ -10,7 +10,7 @@ vignette: > ## Introduction -`teal` is a shiny-based interactive exploration framework for analyzing data, with particular emphasis on `CDISC` clinical trial data. +`teal` is a shiny-based interactive exploration framework for analyzing data, with particular emphasis on CDISC clinical trial data. `teal` applications allow their users to: * "Pull" in data from external data sources @@ -24,7 +24,6 @@ In addition, the `teal` framework provides application developers with: * A logging framework to facilitate debugging of applications More advanced users of the framework can also create new analysis modules which can be added into any `teal` applications. -See the _Creating Custom Modules_ vignette for a brief introduction to creating modules. ## Your first `teal` application: @@ -35,75 +34,93 @@ library(teal) app <- init( data = teal_data(IRIS = iris, MTCARS = mtcars), - modules = example_module(), - header = "My first teal application" + modules = modules( + example_module("Module 1"), + example_module("Module 2") + ), + filter = teal_slices( + teal_slice(dataname = "IRIS", varname = "Species", selected = "setosa") + ), + title = build_app_title(title = "My first teal app"), + header = h3("My first teal application"), + footer = div(a("Powered by teal", href = "https://insightsengineering.github.io/teal/latest-tag/")) ) if (interactive()) { shinyApp(app$ui, app$server) } ``` -Example application -As shown in the image above, this application consists of several distinct areas: - -* Application Header (title at the top): is the title of the application. -* Teal Modules (bar under the title): in this case a simple module named "example teal module". -* Encoding Panel (panel on the left hand side): Module specific UI components, in this case a drop-down to select a dataset name. -* Main Output Panel (panel on the middle): The outputs of the module, for this example module the chosen dataset is displayed. -* Filter Panel (panel on the right hand side): for filtering the data to be passed into all `teal` modules. - -### Encoding panel - -The left hand side of the application is (usually) dedicated to module specific controls. -In modules which include reproducibility functionality it often contains a _Show R Code_ button that, when clicked, will display the code required to re-generate the output, including any filtering added by the filter panel and `library` calls to attach required packages. - -### Filter panel - -The filter panel allows app developers to select the datasets they wish to make available in the modules and define filters for those datasets. -The top section shows the number of records remaining in each dataset after filtering. -The middle section lists all currently defined filters. Typically these can be modified by the user. -The bottom section allows the user to add new filters. - -In the example below: - -* For the `IRIS` dataset, only rows satisfying the conditions `Petal.Length >= 3.4` and `Species %in% c("setosa", "virginica")` are included, thereby keeping 50 rows. -* For the `MTCARS` dataset, only rows satisfying the condition `cyl %in% c(4, 6)` are included, thereby keeping 18 rows. - -Example filter panel + + +_Hovering_ the image shows the `teal` application generated by this code. + + + + Example application + Example application (hover) + + +Every `teal` application is composed of the following elements, all of which can be controlled by the app developer by passing arguments to the `init` function: + +* Application Title _(browser's tab title)_: is the title of the application. +* Application Header and Footer _(the top and the bottom of the app)_: any content to be placed at the top and bottom of the application. +* Teal Modules _(tabs under the header)_: tab for each module included in the application. + * In the example code: there are two modules named "Module 1" and "Module 2". +* Module Content _(panel on the middle)_: the outputs of the currently active module. +* Filter Panel _(panel on the right hand side)_: for filtering the data to be passed into all `teal` modules. + * In the example code: the filter panel is being initialized with a filter for the `Species` variable in the `iris` dataset. ## Creating your own applications -The key function to use to create your `teal` application is `init`, which requires two arguments: `data` and `modules`. +The key function to use to create your `teal` application is `init`, which requires two mandatory arguments: `data` and `modules`. There are other optional arguments for `init`, which can be used to customize the application. Please refer to the documentation for `init` for further details. ### Application data -The `data` argument to the `init` function specifies the data used by your application. -In the example above this is data that exists in the global environment. -We call `teal_data` to bind all datasets into one R object of class `teal_data`. -We could also specify relationships between the datasets using the `join_keys` argument but in this case the datasets are not related. +The `data` argument in the `init` function specifies the data used in your application. All datasets which are about to be used in `teal` application must be passed through `teal_data` object. +It is also possible to specify relationships between the datasets using the `join_keys` argument but in this case the datasets are not related. See [this vignette](including-data-in-teal-applications.html) for details. If data is not available and has to be pulled from a remote source, `init` must receive a `teal_data_module` that specifies how to obtain the desired datasets and put them into a `teal_data` object. See [this vignette](data-as-shiny-module.html) for details. -In order to use `CDISC` clinical trial data in a `teal` application the `cdisc_data` function is used instead. +In order to use CDISC clinical trial data in a `teal` application the `cdisc_data` function is used instead. +Custom `SDTM` standards can be handled with `teal_data` and `join_keys`. -For further details we recommend exploring the [`teal.data`](https://insightsengineering.github.io/teal.data/) package documentation. +For further details, we recommend exploring the [`teal.data`](https://insightsengineering.github.io/teal.data/) package documentation. ### Modules The `modules` argument to `init` consists of a list of `teal` modules (which can be wrapped together using the function `modules`). -We recommend creating applications using predefined `teal` modules. -See the references below for links to these modules. +Core `teal` developers have created several universal `teal` modules that can be useful in any `teal` application. +To learn how to create your own modules, please explore [Creating Custom Modules vignette](creating-custom-modules.html). +To use our predefined modules, see the references below for links to these modules. ### Defining filters The optional `filter` argument in `init` allows you to initialize the application with predefined filters. -See the documentation for `init` for further details. +For further details see [Filter Panel vignette](filter-panel.html) . ### Reporting If any of the `modules` in your `teal` application support reporting (see [`teal.reporter`](https://insightsengineering.github.io/teal.reporter/) for more details), users of your application can add the outputs of the modules to a report. -This report can then be downloaded and a special _Report Previewer_ module will be added to your application as an additional tab, where users can view and configure their reports before downloading them. +This report can then be downloaded and a special _Report Previewer_ module will be added to your application as an additional tab, where users can view and configure their reports before downloading them. See more details in [this vignette](adding-support-for-reporting.html). + + +### Reproducible code + +`teal` hands over data with reproducible code to every module included in the application. +Note that `teal` does not display the code, that is the modules' responsibility. +For example, the `example_module` function used above shows the code in the main panel together with other outputs. +For more details see [this vignette](reproducible-code.html). ## Where to go next @@ -115,6 +132,12 @@ For example see: * [clinical trial reporting modules](https://insightsengineering.github.io/teal.modules.clinical/) * [modules for analyzing `MultiAssayExperiment` objects](https://insightsengineering.github.io/teal.modules.hermes/) +For a demo of `teal` apps see: + +* [The gallery of sample apps based on teal](https://insightsengineering.github.io/teal.gallery/) +* [A catalog of Tables, Listings and Graphs](https://insightsengineering.github.io/tlg-catalog/) +* [A catalog of Biomarker Analysis Templates of Tables And Graphs](https://insightsengineering.github.io/biomarker-catalog/) + The `teal` framework relies on a set of supporting packages whose documentation provides more in-depth information. The packages which are of most interest when defining `teal`applications are: diff --git a/vignettes/images/custom_app.png b/vignettes/images/custom_app.png index d6ef818e0e..24a8bad884 100644 Binary files a/vignettes/images/custom_app.png and b/vignettes/images/custom_app.png differ diff --git a/vignettes/images/example_app.png b/vignettes/images/example_app.png deleted file mode 100644 index 8c8469c7df..0000000000 Binary files a/vignettes/images/example_app.png and /dev/null differ diff --git a/vignettes/images/filter_panel.png b/vignettes/images/filter_panel.png deleted file mode 100644 index d7adcd60cd..0000000000 Binary files a/vignettes/images/filter_panel.png and /dev/null differ diff --git a/vignettes/images/filters-mapping.svg b/vignettes/images/filters-mapping.svg new file mode 100644 index 0000000000..e86542b02e --- /dev/null +++ b/vignettes/images/filters-mapping.svg @@ -0,0 +1 @@ +
Predefined filters
Predefined filte...
module 2
module 2
module 1
module 1
module 3
module 3
module 4
module 4
filter 1
filter 1
filter 2
filter 2
filter 3
filter 3
filter 4
filter 4
filter 5
filter 5
filter 6
filter 6
filter 1
filter 1
filter 1
filter 1
filter 1
filter 1
filter 1
filter 1
filter 2
filter 2
filter 2
filter 2
filter 3
filter 3
filter 4
filter 4
Text is not SVG - cannot display
\ No newline at end of file diff --git a/vignettes/images/filters_mapping.jpg b/vignettes/images/filters_mapping.jpg deleted file mode 100644 index 270f59b6f3..0000000000 Binary files a/vignettes/images/filters_mapping.jpg and /dev/null differ diff --git a/vignettes/images/show_code_prepro_missing.png b/vignettes/images/show_code_prepro_missing.png new file mode 100644 index 0000000000..7fbd2ae81f Binary files /dev/null and b/vignettes/images/show_code_prepro_missing.png differ diff --git a/vignettes/images/show_code_prepro_present.png b/vignettes/images/show_code_prepro_present.png new file mode 100644 index 0000000000..03f0046313 Binary files /dev/null and b/vignettes/images/show_code_prepro_present.png differ diff --git a/vignettes/images/simple_module.png b/vignettes/images/simple_module.png deleted file mode 100644 index 8c87ec12a3..0000000000 Binary files a/vignettes/images/simple_module.png and /dev/null differ diff --git a/vignettes/images/teal-app-components-hover.svg b/vignettes/images/teal-app-components-hover.svg new file mode 100644 index 0000000000..84d9efa291 --- /dev/null +++ b/vignettes/images/teal-app-components-hover.svg @@ -0,0 +1 @@ +
My first teal app
My first teal...
127.0.0.1:3838
127.0.0.1:3838
Module 1
Module 1
Module 2
Module 2
My first teal application
My first teal application
Choose a dataset 
Choose a dataset 
IRIS
IRIS
Show R code
Show R code
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 5.1 3.5 1.4 0.2 setosa
2 4.9 3.0 1.4 0.2 setosa
3 4.7 3.2 1.3 0.2 setosa
4 4.6 3.1 1.5 0.2 setosa
5 5.0 3.6 1.4 0.2 setosa
6 5.4 3.9 1.7 0.4 setosa
7 4.6 3.4 1.4 0.3 setosa
8 5.0 3.4 1.5 0.2 setosa
9 4.4 2.9 1.4 0.2 setosa
10 4.9 3.1 1.5 0.1 setosa
11 5.4 3.7 1.5 0.2 setosa
12 4.8 3.4 1.6 0.2 setosa
13 4.8 3.0 1.4 0.1 setosa
14 4.3 3.0 1.1 0.1 setosa
15 5.8 4.0 1.2 0.2 setosa
16 5.7 4.4 1.5 0.4 setosa
17 5.4 3.9 1.3 0.4 setosa
Sepal.Length Sepal.Width Petal.Length Petal.Width Species...
Active Filter Summary
Active Filter Summary
dataname
dataname
MTCARS
MTCARS
IRIS
IRIS
Obs
Obs
32/32
32/32
50/150
50/150
Active Filter Variables
Active Filter Variables
IRIS
IRIS
Species
Species
setosa
setosa
Add Filter Variables
Add Filter Variables
IRIS
IRIS
Add
Add
filter
filter
Select variable to filter
Select variable to fi...
MTCARS
MTCARS
MTCARS
MTCA...
Add
Add
filter
filter
Select variable to filter
Select variable to fil...
Powered by teal
Powered by te...
Session Info
Session In...
Pid:33902 Token:ed1901df
Pid:33902 Token:ed19...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/vignettes/images/teal-app-components.svg b/vignettes/images/teal-app-components.svg new file mode 100644 index 0000000000..675339679a --- /dev/null +++ b/vignettes/images/teal-app-components.svg @@ -0,0 +1 @@ +
Title
Title
127.0.0.1:3838
127.0.0.1:3838
Header
Header
Footer
Footer
Active Module Content
Active Module Content
Module 1
Module 1
Module 2
Module 2
Module 3
Module 3
Filter Panel
Filter Panel
Text is not SVG - cannot display
\ No newline at end of file diff --git a/vignettes/including-data-in-teal-applications.Rmd b/vignettes/including-data-in-teal-applications.Rmd index 0e317438cf..723210cc07 100644 --- a/vignettes/including-data-in-teal-applications.Rmd +++ b/vignettes/including-data-in-teal-applications.Rmd @@ -21,8 +21,12 @@ knitr::opts_chunk$set( ## Data in `teal` Applications -While the `teal` framework is mainly geared towards clinical data that conforms to the `ADaM` standard, general, non-relational data is handled just as well and the mechanism of passing data to applications is virtually the same. -Also, modules in `teal.modules.general` have been designed to work with general data. +The `teal` framework readily accepts general, non-relational data. +Modules defined in the `teal.modules.general` package are designed to work well with that kind of data. +Relational data is handled just as well and the mechanism of passing data to applications is virtually the same. +This includes clinical data that conforms to the `ADaM` standard. +We are working on making the framework extendable so that support for other data structures can be added with relative ease. +Currently some support is offered for the `MultiAssayExperiment` class. All applications use the `teal_data` class as a data container. `teal_data` objects are passed to `init` to build the application, where they are modified by the filter panel (if applicable) and passed on to modules. @@ -56,37 +60,53 @@ shinyApp(app$ui, app$server) ### Reproducible data -A `teal_data` stores data in a separate environment. -Therefore, modifying the stored datasets requires that code be evaluated in that environment. +A `teal_data` object stores data in a separate environment. +Therefore, modifying the stored datasets requires that processing code be evaluated in that environment. Following that logic, one can create an empty `teal_data` object and populate it by evaluating code. This can be done using the `eval_code` function or, more conveniently, using the `within` function. ```{r} # create empty object -data0 <- teal_data() +data_empty <- teal_data() # run code in the object -data1 <- teal.code::eval_code(data0, code = "iris <- iris - cars <- mtcars") +data_populated_1 <- eval_code(data_empty, code = "iris <- iris + cars <- mtcars") # alternative -data2 <- within(data0, { +data_populated_2 <- within(data_empty, { iris <- iris cars <- mtcars }) ``` The key difference between `eval_code` and `within` is that the former accepts code as character vector or language objects (calls and expressions), while `within` accepts _only_ inline code. -See `?eval_code` for more details. +See `?qenv` for more details. -Note that with `data` the code that created the data objects is unknown so the process cannot be reproduced. -The necessary code can be added with the `code` argument of the `teal_data` function but in that case the code may not reproduce the environment. +Note that in the first example `data` was created by passing data objects, so the code that was used to create the data objects is unknown and therefore the process cannot be reproduced. +Inspecting code the in the app created above reveals a note that the preprocessing code is absent. + + + +The necessary code can be supplied to the `code` argument of the `teal_data` function. + +```{r} +data_with_code <- teal_data( + iris = iris, cars = mtcars, + code = "iris <- iris + cars <- mtcars" +) +``` + + + +Keep in mind this code is not executed in the `teal_data`'s environment, so it may not reproduce the environment. Such an object is considered _unverified_ (see [`verified` property](#verified)). -If reproducibility is required, we recommend creating `teal_data` empty and evaluating code. +If reproducibility is required, we recommend creating an empty `teal_data` object and then evaluating code. -###### code from file +#### code from file -The ability to pass code as character vector to `eval_code` opens the door to using code stored in a file. +The ability to pass code as a character vector to `eval_code` opens the door to using code stored in a file. ```{r, eval=FALSE} # not run data_from_file <- teal_data() @@ -107,9 +127,9 @@ Currently `teal` supports two specialized data formats. ### `ADaM` data -The `ADaM` data model specifies relationships between `CDISC` datasets. -The `cdisc_data` function takes advantage of that fact to to automatically set default joining keys (see [`join_keys` property](#join_keys)). -In the example below, two standard `CDISC` datasets (`ADSL` and `ADTTE`) are passed to `cdisc_data`. +The `ADaM` data model, defined in CDISC standards, specifies relationships between the subject-level parent dataset and observation-level child datasets. +The `cdisc_data` function takes advantage of that fact to automatically set default joining keys (see [`join_keys` property](#join_keys)). +In the example below, two standard `ADaM` datasets (`ADSL` and `ADTTE`) are passed to `cdisc_data`. ```{r} # create cdisc_data @@ -130,7 +150,7 @@ shinyApp(app$ui, app$server) ### `MultiAssayExperiment` data -The `MultiAssayExperiment` package offers a data structure for representing and analyzing multi-omics experiments: a biological analysis approach utilizing multiple types of observations, such as DNA mutations and abundance of RNA and proteins, in the same biological specimens. +The `MultiAssayExperiment` package offers a data structure for representing and analyzing multi-omics experiments that involve multi-modal, high-dimensionality data, such as DNA mutations, protein or RNA abundance, chromatin occupancy, etc., in the same biological specimens. The `MultiAssayExperiment` class is described in detail [here](https://www.bioconductor.org/packages/release/bioc/vignettes/MultiAssayExperiment/inst/doc/MultiAssayExperiment.html). @@ -149,7 +169,7 @@ app <- init( shinyApp(app$ui, app$server) ``` -Note that due to the unique structure of a MAE `teal` requires special considerations when building `teal` modules. +Due to the unique structure of a MAE, `teal` requires special considerations when building `teal` modules. Therefore, we cannot guarantee that all modules will work properly with MAEs. The package [`teal.modules.hermes`](https://insightsengineering.github.io/teal.modules.hermes/latest-tag/) has been developed specifically with MAE in mind and will be more reliable. @@ -161,10 +181,11 @@ The filter panel supports MAEs out of the box. ##### `datanames` -The `datanames` property lists the objects stored in the `teal_data` that constitute datasets of interest. +The `datanames` property lists the objects stored in the `teal_data` environment that constitute datasets of interest. Objects passed to `teal_data` become automatically listed in the `datanames` property of the resulting object. Objects created in `teal_data` by evaluating code need not be data objects of interest and as such they are not automatically added to `datanames`. -Use the `datanames` function to modify the `datanames` property. +For convenience, an empty `datanames` property is considered to mean "all objects in the container". +`datanames` can be read or modified with the `datanames` function. ```{r} data_with_objects <- teal_data(iris = iris, cars = mtcars) @@ -180,8 +201,12 @@ datanames(data_with_code) <- c("iris", "cars") datanames(data_with_code) ``` -All `teal` modules take a `datanames` argument that determines which datasets they are to have access to. -Only objects enumerated in the `datanames` property of the `teal_data` object can be used. +The `datanames` property serves as a communication bridge between the data container and modules in a `teal` application. +In `teal` all modules are called with a `datanames` argument that determines which of the variables in the `teal_data` object they are to access. +Only variables enumerated in the `datanames` property are eligible for use in modules. + +Note that specifying `datanames` in `teal_data` is optional; if the property is empty, all objects are considered eligible. +Likewise, the `datanames` argument in the module call defaults to `"all"`, which means that module will attempt to access all eligible variables in the `teal_data` object. For a detailed explanation of `datanames`, see [this `teal.data` vignette](https://insightsengineering.github.io/teal.data/latest-tag/articles/teal-data.html). @@ -219,10 +244,10 @@ For a detailed explanation of join keys, see [this `teal.data` vignette](https:/ ##### `verified` -`teal_data` allows for tracking code from data creation through filtering through analysis so that the whole process can be reproduced. +`teal_data` allows for tracking code from data creation through data filtering through data analysis so that the whole process can be reproduced. The `verified` property designates whether or not reproducibility has been confirmed. -`teal_data` objects that are created empty and are only modified by evaluating code in them are verified by default. -Ones created with data objects alone or with data objects and code are not verified by default but can become verified by running the `verify` function. +`teal_data` objects that are created empty and only modified by evaluating code within them are considered verified by default. +Those created with data objects alone or with data objects and code are not verified by default, but can become verified by running the `verify` function. ```{r} data_with_code