diff --git a/.gitignore b/.gitignore index b2a7690edf..a78ab40e4c 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ tmp.* vignettes/*.R vignettes/*.html vignettes/*.md +inst/doc diff --git a/DESCRIPTION b/DESCRIPTION index 049ca349a9..0eac74ef04 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -31,9 +31,9 @@ BugReports: https://github.com/insightsengineering/teal/issues Depends: R (>= 4.0), shiny (>= 1.7.0), - teal.data (>= 0.3.0.9011), - teal.slice (>= 0.4.0.9023), - teal.transform (>= 0.4.0.9007) + teal.data (>= 0.3.0.9017), + teal.slice (>= 0.4.0.9027), + teal.transform (>= 0.4.0.9010) Imports: checkmate (>= 2.1.0), jsonlite, @@ -51,8 +51,6 @@ Imports: utils Suggests: bslib, - covr, - dplyr (>= 1.0.5), knitr (>= 1.42), MultiAssayExperiment, R6, @@ -70,8 +68,8 @@ Config/Needs/verdepcheck: rstudio/shiny, insightsengineering/teal.data, mllg/checkmate, jeroen/jsonlite, r-lib/lifecycle, daroczig/logger, tidyverse/magrittr, r-lib/rlang, daattali/shinyjs, insightsengineering/teal.logger, insightsengineering/teal.reporter, - insightsengineering/teal.widgets, rstudio/bslib, r-lib/covr, - tidyverse/dplyr, yihui/knitr, bioc::MultiAssayExperiment, r-lib/R6, + insightsengineering/teal.widgets, rstudio/bslib, + yihui/knitr, bioc::MultiAssayExperiment, r-lib/R6, rstudio/rmarkdown, rstudio/shinyvalidate, insightsengineering/teal.code, r-lib/testthat, r-lib/withr, yaml=vubiostat/r-yaml diff --git a/NAMESPACE b/NAMESPACE index 237124e2d1..341b7e34e1 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,7 +1,6 @@ # Generated by roxygen2: do not edit by hand S3method(c,teal_slices) -S3method(get_code,tdata) S3method(get_metadata,default) S3method(get_metadata,tdata) S3method(join_keys,tdata) @@ -19,6 +18,7 @@ S3method(within,teal_data_module) export("%>%") export(TealReportCard) export(as.teal_slices) +export(as_tdata) export(example_module) export(get_code_tdata) export(get_metadata) @@ -48,7 +48,7 @@ import(shiny) import(teal.data) import(teal.slice) import(teal.transform) -importFrom(lifecycle,deprecate_soft) +importFrom(lifecycle,badge) importFrom(magrittr,"%>%") importFrom(methods,setMethod) importFrom(stats,setNames) diff --git a/NEWS.md b/NEWS.md index 3aa9328d0b..0aae1ce015 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,13 @@ * `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. + +### Breaking changes + +* `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. ### Miscellaneous diff --git a/R/dummy_functions.R b/R/dummy_functions.R index 608c1a3241..8f1f2d3d5d 100644 --- a/R/dummy_functions.R +++ b/R/dummy_functions.R @@ -78,10 +78,7 @@ example_datasets <- function() { # nolint #' @return A `teal` module which can be included in the `modules` argument to [teal::init()]. #' @examples #' app <- init( -#' data = teal_data( -#' dataset("IRIS", iris), -#' dataset("MTCARS", mtcars) -#' ), +#' data = teal_data(IRIS = iris, MTCARS = mtcars), #' modules = example_module() #' ) #' if (interactive()) { @@ -93,22 +90,27 @@ example_module <- function(label = "example teal module", datanames = "all") { module( label, server = function(id, data) { - checkmate::assert_class(data, "tdata") + checkmate::assert_class(data(), "teal_data") moduleServer(id, function(input, output, session) { - output$text <- renderPrint(data[[input$dataname]]()) + ns <- session$ns + updateSelectInput(session, "dataname", choices = isolate(teal.data::datanames(data()))) + output$text <- renderPrint({ + req(input$dataname) + data()[[input$dataname]] + }) teal.widgets::verbatim_popup_srv( id = "rcode", - verbatim_content = attr(data, "code")(), + verbatim_content = reactive(teal.code::get_code(data())), title = "Association Plot" ) }) }, - ui = function(id, data) { + ui = function(id) { ns <- NS(id) teal.widgets::standard_layout( output = verbatimTextOutput(ns("text")), encoding = div( - selectInput(ns("dataname"), "Choose a dataset", choices = names(data)), + selectInput(ns("dataname"), "Choose a dataset", choices = NULL), teal.widgets::verbatim_popup_ui(ns("rcode"), "Show R code") ) ) diff --git a/R/get_rcode_utils.R b/R/get_rcode_utils.R index e53bb9918a..5954a78c40 100644 --- a/R/get_rcode_utils.R +++ b/R/get_rcode_utils.R @@ -38,39 +38,38 @@ get_rcode_str_install <- function() { #' @param datasets (`FilteredData`) object #' @param hashes named (`list`) of hashes per dataset #' -#' @return `character(3)` containing the following elements: +#' @return Character string concatenated from the following elements: #' - data pre-processing code (from `data` argument in `init`) #' - hash check of loaded objects -#' - filter code +#' - filter code (if any) #' #' @keywords internal get_datasets_code <- function(datanames, datasets, hashes) { + # preprocessing code str_prepro <- teal.data:::get_code_dependency(attr(datasets, "preprocessing_code"), names = datanames) if (length(str_prepro) == 0) { str_prepro <- "message('Preprocessing is empty')" - } else if (length(str_prepro) > 0) { - str_prepro <- paste0(str_prepro, "\n\n") + } else { + str_prepro <- paste(str_prepro, collapse = "\n") } - str_hash <- paste( - paste0( - vapply( - datanames, - function(dataname) { - sprintf( - "stopifnot(%s == %s)", - deparse1(bquote(rlang::hash(.(as.name(dataname))))), - deparse1(hashes[[dataname]]) - ) - }, - character(1) - ), - collapse = "\n" - ), - "\n\n" - ) + # hash checks + str_hash <- vapply(datanames, function(dataname) { + sprintf( + "stopifnot(%s == %s)", + deparse1(bquote(rlang::hash(.(as.name(dataname))))), + deparse1(hashes[[dataname]]) + ) + }, character(1)) + str_hash <- paste(str_hash, collapse = "\n") + # filter expressions str_filter <- teal.slice::get_filter_expr(datasets, datanames) + if (str_filter == "") { + str_filter <- character(0) + } - c(str_prepro, str_hash, str_filter) + # concatenate all code + str_code <- paste(c(str_prepro, str_hash, str_filter), collapse = "\n\n") + sprintf("%s\n", str_code) } diff --git a/R/init.R b/R/init.R index 77e668019b..bf7b82dc8a 100644 --- a/R/init.R +++ b/R/init.R @@ -10,16 +10,10 @@ #' End-users: This is the most important function for you to start a #' teal app that is composed out of teal modules. #' -#' @param data (`TealData` or `TealDataset` or `TealDatasetConnector` or `list` or `data.frame` -#' or `MultiAssayExperiment`, `teal_data`, `teal_data_module`)\cr -#' `R6` object as returned by [teal.data::cdisc_data()], [teal.data::teal_data()], -#' [teal.data::cdisc_dataset()], [teal.data::dataset()], [teal.data::dataset_connector()] or -#' [teal.data::cdisc_dataset_connector()] or [teal_data_module()] or a single `data.frame` or -#' a `MultiAssayExperiment` -#' or a list of the previous objects or function returning a named list. -#' NOTE: teal does not guarantee reproducibility of the code when names of the list elements -#' do not match the original object names. To ensure reproducibility please use [teal.data::teal_data()] -#' or [teal.data::cdisc_data()] with `check = TRUE` enabled. +#' @param data (`teal_data`, `teal_data_module`, `named list`)\cr +#' `teal_data` object as returned by [teal.data::teal_data()] or +#' `teal_data_modules` or simply a list of a named list of objects +#' (`data.frame` or `MultiAssayExperiment`). #' @param modules (`list`, `teal_modules` or `teal_module`)\cr #' 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 @@ -50,13 +44,10 @@ #' @include modules.R #' #' @examples -#' new_iris <- transform(iris, id = seq_len(nrow(iris))) -#' new_mtcars <- transform(mtcars, id = seq_len(nrow(mtcars))) -#' #' app <- init( #' data = teal_data( -#' dataset("new_iris", new_iris), -#' dataset("new_mtcars", new_mtcars), +#' new_iris = transform(iris, id = seq_len(nrow(iris))), +#' new_mtcars = transform(mtcars, id = seq_len(nrow(mtcars))), #' code = " #' new_iris <- transform(iris, id = seq_len(nrow(iris))) #' new_mtcars <- transform(mtcars, id = seq_len(nrow(mtcars))) @@ -74,7 +65,7 @@ #' "Iris Sepal.Length histogram", #' server = function(input, output, session, data) { #' output$hist <- renderPlot( -#' hist(data[["new_iris"]]()$Sepal.Length) +#' hist(data()[["new_iris"]]$Sepal.Length) #' ) #' }, #' ui = function(id, ...) { @@ -111,11 +102,22 @@ init <- function(data, footer = tags$p(), id = character(0)) { logger::log_trace("init initializing teal app with: data ({ class(data)[1] }).") - if (!inherits(data, c("TealData", "teal_data", "teal_data_module"))) { - data <- teal.data::to_relational_data(data = data) + if (is.list(data) && !inherits(data, "teal_data_module")) { + checkmate::assert_list(data, names = "named") + data <- do.call(teal.data::teal_data, data) + } + if (inherits(data, "TealData")) { + lifecycle::deprecate_stop( + when = "0.99.0", + what = "init(data)", + paste( + "TealData is no longer supported. Use teal_data() instead.", + "Please follow migration instructions https://github.com/insightsengineering/teal/discussions/988." + ) + ) } - checkmate::assert_multi_class(data, c("TealData", "teal_data", "teal_data_module")) + checkmate::assert_multi_class(data, c("teal_data", "teal_data_module")) checkmate::assert_multi_class(modules, c("teal_module", "list", "teal_modules")) checkmate::assert_string(title, null.ok = TRUE) checkmate::assert( @@ -147,12 +149,6 @@ init <- function(data, as.list(hashables$data@env) } else if (inherits(data, "teal_data_module")) { body(data$server) - } else if (hashables$data$is_pulled()) { - sapply(get_dataname(hashables$data), simplify = FALSE, function(dn) { - hashables$data$get_dataset(dn)$get_raw_data() - }) - } else { - hashables$data$get_code() } attr(filter, "app_id") <- rlang::hash(hashables) @@ -218,10 +214,6 @@ init <- function(data, landing_module <- landing[[1L]] do.call(landing_module$server, c(list(id = "landing_module_shiny_id"), landing_module$server_args)) } - if (inherits(data, "TealDataAbstract")) { - # copy TealData so that load won't be shared between the session - data <- data$copy(deep = TRUE) - } filter <- deep_copy_filter(filter) srv_teal_with_splash(id = id, data = data, modules = modules, filter = filter) } diff --git a/R/module_nested_tabs.R b/R/module_nested_tabs.R index 73e8e1b2a8..7a0e4a9c93 100644 --- a/R/module_nested_tabs.R +++ b/R/module_nested_tabs.R @@ -115,15 +115,6 @@ ui_nested_tabs.teal_module <- function(id, modules, datasets, depth = 0L, is_mod args <- isolate(teal.transform::resolve_delayed(modules$ui_args, datasets)) args <- c(list(id = ns("module")), args) - if (is_arg_used(modules$ui, "datasets")) { - args <- c(args, datasets = datasets) - } - - if (is_arg_used(modules$ui, "data")) { - data <- .datasets_to_data(modules, datasets) - args <- c(args, data = list(data)) - } - teal_ui <- tags$div( id = id, class = "teal_module", @@ -243,7 +234,7 @@ srv_nested_tabs.teal_module <- function(id, datasets, modules, is_module_specifi } if (is_arg_used(modules$server, "data")) { - data <- .datasets_to_data(modules, datasets, trigger_data) + data <- eventReactive(trigger_data(), .datasets_to_data(modules, datasets)) args <- c(args, data = list(data)) } @@ -252,13 +243,6 @@ srv_nested_tabs.teal_module <- function(id, datasets, modules, is_module_specifi args <- c(args, filter_panel_api = filter_panel_api) } - if (is_arg_used(modules$server, "datasets") && is_arg_used(modules$server, "data")) { - warning( - "Module '", modules$label, "' has `data` and `datasets` arguments in the formals.", - "\nIt's recommended to use `data` to work with filtered objects." - ) - } - # observe the trigger_module above to induce the module once the renderUI is triggered observeEvent( ignoreNULL = TRUE, @@ -277,25 +261,20 @@ srv_nested_tabs.teal_module <- function(id, datasets, modules, is_module_specifi }) } -#' Convert `FilteredData` to reactive list of datasets of the `tdata` type. +#' Convert `FilteredData` to reactive list of datasets of the `teal_data` type. #' -#' Converts `FilteredData` object to `tdata` object containing datasets needed for a specific module. +#' 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. #' A hash per `dataset` is calculated internally and returned in the code. #' #' @param module (`teal_module`) module where needed filters are taken from #' @param datasets (`FilteredData`) object where needed data are taken from -#' @param trigger_data (`reactiveVal`) to trigger getting the filtered data -#' @return list of reactive datasets with following attributes: -#' - `code` (`character`) containing datasets reproducible code. -#' - `join_keys` (`join_keys`) containing relationships between datasets. -#' - `metadata` (`list`) containing metadata of datasets. +#' @return A `teal_data` object. #' #' @keywords internal -.datasets_to_data <- function(module, datasets, trigger_data = reactiveVal(1L)) { +.datasets_to_data <- function(module, datasets) { checkmate::assert_class(module, "teal_module") checkmate::assert_class(datasets, "FilteredData") - checkmate::assert_class(trigger_data, "reactiveVal") datanames <- if (is.null(module$datanames) || identical(module$datanames, "all")) { datasets$datanames() @@ -304,29 +283,19 @@ srv_nested_tabs.teal_module <- function(id, datasets, modules, is_module_specifi } # list of reactive filtered data - data <- sapply( - datanames, - function(x) eventReactive(trigger_data(), datasets$get_data(x, filtered = TRUE)), - simplify = FALSE - ) + data <- sapply(datanames, function(x) datasets$get_data(x, filtered = TRUE), simplify = FALSE) hashes <- calculate_hashes(datanames, datasets) - metadata <- sapply(datanames, datasets$get_metadata, simplify = FALSE) - new_tdata( - data, - eventReactive( - trigger_data(), - { - c( - get_rcode_str_install(), - get_rcode_libraries(), - get_datasets_code(datanames, datasets, hashes) - ) - } - ), - datasets$get_join_keys(), - metadata + code <- c( + get_rcode_str_install(), + get_rcode_libraries(), + get_datasets_code(datanames, datasets, hashes) + ) + + do.call( + teal.data::teal_data, + args = c(data, code = list(code), join_keys = list(datasets$get_join_keys()[datanames])) ) } diff --git a/R/module_teal_with_splash.R b/R/module_teal_with_splash.R index 8d4afc59ae..a15e69d507 100644 --- a/R/module_teal_with_splash.R +++ b/R/module_teal_with_splash.R @@ -22,23 +22,17 @@ ui_teal_with_splash <- function(id, title, header = tags$p("Add Title Here"), footer = tags$p("Add Footer Here")) { - checkmate::assert_multi_class(data, c("TealData", "teal_data", "teal_data_module")) + checkmate::assert_multi_class(data, c("teal_data", "teal_data_module")) ns <- NS(id) # Startup splash screen for delayed loading # We use delayed loading in all cases, even when the data does not need to be fetched. # This has the benefit that when filtering the data takes a lot of time initially, the # Shiny app does not time out. - splash_ui <- if (inherits(data, "teal_data_module")) { data$ui(ns("teal_data_module")) } else if (inherits(data, "teal_data")) { div() - } else if (inherits(data, "TealDataAbstract") && teal.data::is_pulled(data)) { - div() - } else { - message("App was initialized with delayed data loading.") - data$get_ui(ns("startapp_module")) } ui_teal( id = ns("teal"), @@ -64,7 +58,7 @@ ui_teal_with_splash <- function(id, #' If data is not loaded yet, `reactive` returns `NULL`. #' @export srv_teal_with_splash <- function(id, data, modules, filter = teal_slices()) { - checkmate::check_multi_class(data, c("TealData", "teal_data", "teal_data_module")) + checkmate::check_multi_class(data, c("teal_data", "teal_data_module")) moduleServer(id, function(input, output, session) { logger::log_trace("srv_teal_with_splash initializing module with data.") @@ -83,41 +77,13 @@ srv_teal_with_splash <- function(id, data, modules, filter = teal_slices()) { data } else if (inherits(data, "teal_data")) { reactiveVal(data) - } else if (inherits(data, "TealDataAbstract") && teal.data::is_pulled(data)) { - new_data <- do.call( - teal.data::teal_data, - c( - lapply(data$get_datasets(), function(x) x$get_raw_data()), - list(code = data$get_code()), - list(join_keys = teal.data::join_keys(data)) - ) - ) - reactiveVal(new_data) # will trigger by setting it - } else { - raw_data_old <- data$get_server()(id = "startapp_module") - raw_data <- reactive({ - data <- raw_data_old() - if (!is.null(data)) { - # raw_data is a reactive which returns data only when submit button clicked - # otherwise it returns NULL - do.call( - teal.data::teal_data, - c( - lapply(data$get_datasets(), function(x) x$get_raw_data()), - list(code = data$get_code()), - list(join_keys = teal.data::join_keys(data)) - ) - ) - } - }) - raw_data } teal_data_rv_validate <- reactive({ # custom module can return error data <- tryCatch(teal_data_rv(), error = function(e) e) - # there is an empty reactive event on init! + # there is an empty reactive cycle on init! if (inherits(data, "shiny.silent.error") && identical(data$message, "")) { return(NULL) } @@ -185,7 +151,6 @@ 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) diff --git a/R/modules.R b/R/modules.R index 3f4d191437..f243bb6120 100644 --- a/R/modules.R +++ b/R/modules.R @@ -22,7 +22,7 @@ #' library(shiny) #' #' app <- init( -#' data = teal_data(dataset("iris", iris)), +#' data = teal_data(iris = iris), #' modules = modules( #' label = "Modules", #' modules( @@ -171,7 +171,7 @@ is_arg_used <- function(modules, arg) { #' @param server (`function`) `shiny` module with following arguments: #' - `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 `tdata` object, a list of reactive (filtered) data specified in +#' - `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]). @@ -179,8 +179,6 @@ is_arg_used <- function(modules, arg) { #' - `...` (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. -#' - `data` (optional) module will receive list of reactive (filtered) data specified in the `filters` argument. -#' - `datasets` (optional) module will receive `FilteredData`. (See `[teal.slice::FilteredData]`). #' - `...` (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 @@ -199,7 +197,7 @@ is_arg_used <- function(modules, arg) { #' library(shiny) #' #' app <- init( -#' data = teal_data(dataset("iris", iris)), +#' data = teal_data(iris = iris), #' modules = list( #' module( #' label = "Module", @@ -284,6 +282,14 @@ module <- function(label = "module", message(sprintf("module \"%s\" server function takes no data so \"datanames\" will be ignored", label)) datanames <- NULL } + 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. ", + "Please use `data` instead.", + call. = FALSE + ) + } srv_extra_args <- setdiff(names(server_args), server_formals) if (length(srv_extra_args) > 0 && !"..." %in% server_formals) { @@ -299,13 +305,20 @@ module <- function(label = "module", stop( "\nmodule() `ui` argument requires a function with following arguments:", "\n - id - teal will set proper shiny namespace for this module.", - "\n\nFollowing arguments can be used optionaly:", - "\n - `data` - module will receive list of reactive (filtered) data specied in the `filters` argument", - "\n - `datasets` - module will receive `FilteredData`. See `help(teal.slice::FilteredData)`", + "\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 `...`" ) } + 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 ", + "Possible solutions are renderUI() or updateXyzInput() functions." + ) + } + ui_extra_args <- setdiff(names(ui_args), ui_formals) if (length(ui_extra_args) > 0 && !"..." %in% ui_formals) { stop( diff --git a/R/modules_debugging.R b/R/modules_debugging.R index 66be4e121b..bb7167c5d3 100644 --- a/R/modules_debugging.R +++ b/R/modules_debugging.R @@ -14,7 +14,7 @@ #' #' @examples #' app <- init( -#' data = list(iris = iris, mtcars = mtcars), +#' data = teal_data(iris = iris, mtcars = mtcars), #' modules = teal:::filter_calls_module(), #' header = "Simple teal app" #' ) @@ -27,10 +27,11 @@ filter_calls_module <- function(label = "Filter Calls Module") { # nolint module( label = label, server = function(input, output, session, data) { - checkmate::assert_class(data, "tdata") + checkmate::assert_class(data, "reactive") + checkmate::assert_class(isolate(data()), "teal_data") output$filter_calls <- renderText({ - get_code_tdata(data) + teal.data::get_code(data()) }) }, ui = function(id, ...) { diff --git a/R/tdata.R b/R/tdata.R index 1806b68d23..391df963c6 100644 --- a/R/tdata.R +++ b/R/tdata.R @@ -1,5 +1,6 @@ #' Create a `tdata` Object #' +#' @description `r lifecycle::badge("deprecated")` #' Create a new object called `tdata` which contains `data`, a `reactive` list of data.frames #' (or `MultiAssayExperiment`), with attributes: #' \itemize{ @@ -20,6 +21,9 @@ #' @param metadata A `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 +#' +#' @seealso `as_tdata` +#' #' @examples #' #' data <- new_tdata( @@ -34,13 +38,21 @@ #' isolate(data[["iris"]]()) #' #' # Get code -#' isolate(get_code(data)) +#' isolate(get_code_tdata(data)) #' #' # Get metadata #' get_metadata(data, "iris") #' #' @export new_tdata <- function(data, code = "", join_keys = NULL, metadata = NULL) { + lifecycle::deprecate_soft( + when = "0.99.0", + what = "tdata()", + details = paste( + "tdata is deprecated and will be removed in the next release. Use `teal_data` instead.\n", + "Please follow migration instructions https://github.com/insightsengineering/teal/discussions/987." + ) + ) checkmate::assert_list( data, any.missing = FALSE, names = "unique", @@ -51,7 +63,6 @@ new_tdata <- function(data, code = "", join_keys = NULL, metadata = NULL) { checkmate::assert_list(metadata, names = "unique", null.ok = TRUE) checkmate::assert_subset(names(metadata), names(data)) - for (m in metadata) teal.data::validate_metadata(m) if (is.reactive(code)) { isolate(checkmate::assert_class(code(), "character", .var.name = "code")) @@ -101,15 +112,6 @@ tdata2env <- function(data) { # nolint list2env(lapply(data, function(x) if (is.reactive(x)) x() else x)) } -#' @rdname tdata -#' @param x a `tdata` object -#' @param ... additional arguments for the generic -#' @export -get_code.tdata <- function(x, ...) { # nolint - # note teal.data which teal depends on defines the get_code method - attr(x, "code")() -} - #' Wrapper for `get_code.tdata` #' This wrapper is to be used by downstream packages to extract the code of a `tdata` object @@ -120,7 +122,7 @@ get_code.tdata <- function(x, ...) { # nolint #' @export get_code_tdata <- function(data) { checkmate::assert_class(data, "tdata") - get_code(data) + attr(data, "code")() } #' Extract `join_keys` from `tdata` @@ -157,3 +159,48 @@ get_metadata.tdata <- function(data, dataname) { get_metadata.default <- function(data, dataname) { stop("get_metadata function not implemented for this object") } + + +#' Downgrade `teal_data` objects in modules for compatibility. +#' +#' Convert `teal_data` to `tdata` in `teal` modules. +#' +#' Recent changes in `teal` cause modules to fail because modules expect a `tdata` object +#' to be passed to the `data` argument but instead they receive a `teal_data` object, +#' which is additionally wrapped in a reactive expression in the server functions. +#' In order to easily adapt such modules without a proper refactor, +#' use this function to downgrade the `data` argument. +#' +#' @param x data object, either `tdata` or `teal_data`, the latter possibly in a reactive expression +#' +#' @return Object of class `tdata`. +#' +#' @examples +#' td <- teal_data() +#' td <- within(td, iris <- iris) %>% within(mtcars <- mtcars) +#' td +#' as_tdata(td) +#' as_tdata(reactive(td)) +#' +#' @export +#' @rdname tdata_deprecation +#' +as_tdata <- function(x) { + if (inherits(x, "tdata")) { + return(x) + } + if (is.reactive(x)) { + checkmate::assert_class(isolate(x()), "teal_data") + datanames <- isolate(teal.data::datanames(x())) + datasets <- sapply(datanames, function(dataname) reactive(x()[[dataname]]), simplify = FALSE) + code <- reactive(teal.code::get_code(x())) + join_keys <- isolate(teal.data::join_keys(x())) + } else if (inherits(x, "teal_data")) { + datanames <- teal.data::datanames(x) + datasets <- sapply(datanames, function(dataname) reactive(x[[dataname]]), simplify = FALSE) + code <- reactive(teal.code::get_code(x)) + join_keys <- isolate(teal.data::join_keys(x)) + } + + new_tdata(data = datasets, code = code, join_keys = join_keys) +} diff --git a/R/teal.R b/R/teal.R index 05de50ebbb..6ec6e6cee4 100644 --- a/R/teal.R +++ b/R/teal.R @@ -19,5 +19,5 @@ magrittr::`%>%` NULL # Fix R CMD check notes -#' @importFrom lifecycle deprecate_soft -lifecycle::deprecate_soft +#' @importFrom lifecycle badge +lifecycle::badge diff --git a/R/teal_data_module.R b/R/teal_data_module.R index 465e240eb2..75a5557e56 100644 --- a/R/teal_data_module.R +++ b/R/teal_data_module.R @@ -1,5 +1,6 @@ #' Data module for `teal` applications #' +#' @description `r lifecycle::badge("experimental")` #' Creates `teal_data_module` object - a `shiny` module to supply or modify data in a `teal` application. #' #' This function creates a `shiny` module that allows for running data pre-processing code after the app starts. diff --git a/R/teal_slices.R b/R/teal_slices.R index 7beae7023d..077d94d8c8 100644 --- a/R/teal_slices.R +++ b/R/teal_slices.R @@ -51,11 +51,11 @@ #' ) #' #' app <- teal::init( +#' data = list(iris = iris, mtcars = mtcars), #' modules = list( #' module("module1"), #' module("module2") #' ), -#' data = list(iris, mtcars), #' filter = filter #' ) #' diff --git a/R/zzz.R b/R/zzz.R index fe6f22205c..a85f3f1437 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -23,9 +23,6 @@ ) } -# Use non-exported function(s) from teal.slice. -# This is a temporary measure and will be removed two release cycles from now (now meaning 0.13.0). -list_to_teal_slices <- getFromNamespace("list_to_teal_slices", "teal.slice") # This one is here because setdiff_teal_slice should not be exported from teal.slice. setdiff_teal_slices <- getFromNamespace("setdiff_teal_slices", "teal.slice") # This one is here because it is needed by c.teal_slices but we don't want it exported from teal.slice. diff --git a/_pkgdown.yml b/_pkgdown.yml index 0ebf24ff57..bbdbe9cc2e 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -28,15 +28,15 @@ navbar: - text: Data Flow href: articles/blueprint/dataflow.html - text: Product Map - href: articles/blueprint/products_map.html + href: articles/blueprint/product_map.html - text: Features navbar: Features - - text: "`qenv`" - href: articles/blueprint/qenv.html + - text: Input Data + href: articles/blueprint/input_data.html + - text: In-App Data + href: articles/blueprint/in_app_data.html - text: Filter Panel href: articles/blueprint/filter_panel.html - - text: Delayed Data Loading (DDL) - href: articles/blueprint/ddl.html - text: Module and Encapsulation href: articles/blueprint/module_encapsulation.html @@ -48,20 +48,17 @@ articles: - title: Get Started navbar: ~ contents: - - teal + - getting-started-with-teal - title: Using teal navbar: Using teal contents: - filter-panel - teal-options - - teal-bs-themes + - bootstrap-themes-in-teal - title: Data in teal Apps navbar: Data in teal Apps contents: - - including-adam-data-in-teal - - including-general-data-in-teal - - including-mae-data-in-teal - - preprocessing-data + - including-data-in-teal-applications - data-as-shiny-module - title: Extending teal navbar: Extending teal @@ -78,14 +75,14 @@ articles: - blueprint/intro - blueprint/actors - blueprint/dataflow - - blueprint/products_map + - blueprint/product_map - title: "" desc: > Features contents: - - blueprint/qenv + - blueprint/input_data + - blueprint/in_app_data - blueprint/filter_panel - - blueprint/ddl - blueprint/module_encapsulation @@ -114,6 +111,7 @@ reference: - landing_popup_module - title: Functions for Module Developers contents: + - as_tdata - tdata - get_code_tdata - get_metadata diff --git a/inst/WORDLIST b/inst/WORDLIST index a460fdae39..199ce67108 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -1,24 +1,18 @@ ADaM -CDISC -DDL -Forkers -Hoffmann -MultiAssayExperiment -Pre -Reproducibility -TLG -Theming -UI -UIs -UX analysing +CDISC cloneable customizable +DDL dropdown +Forkers funder +Hoffmann +MAE omics pharmaverse pre +Pre preprocessed programmatically qenv @@ -27,4 +21,8 @@ reproducibility summarization tabsetted themer +theming +TLG +UI uncheck +UX diff --git a/man/dot-datasets_to_data.Rd b/man/dot-datasets_to_data.Rd index c484adeb75..1789e9bd71 100644 --- a/man/dot-datasets_to_data.Rd +++ b/man/dot-datasets_to_data.Rd @@ -2,27 +2,20 @@ % Please edit documentation in R/module_nested_tabs.R \name{.datasets_to_data} \alias{.datasets_to_data} -\title{Convert \code{FilteredData} to reactive list of datasets of the \code{tdata} type.} +\title{Convert \code{FilteredData} to reactive list of datasets of the \code{teal_data} type.} \usage{ -.datasets_to_data(module, datasets, trigger_data = reactiveVal(1L)) +.datasets_to_data(module, datasets) } \arguments{ \item{module}{(\code{teal_module}) module where needed filters are taken from} \item{datasets}{(\code{FilteredData}) object where needed data are taken from} - -\item{trigger_data}{(\code{reactiveVal}) to trigger getting the filtered data} } \value{ -list of reactive datasets with following attributes: -\itemize{ -\item \code{code} (\code{character}) containing datasets reproducible code. -\item \code{join_keys} (\code{join_keys}) containing relationships between datasets. -\item \code{metadata} (\code{list}) containing metadata of datasets. -} +A \code{teal_data} object. } \description{ -Converts \code{FilteredData} object to \code{tdata} object containing datasets needed for a specific module. +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. A hash per \code{dataset} is calculated internally and returned in the code. } diff --git a/man/eval_code.Rd b/man/eval_code.Rd index 22a67e4570..fd2ab5ffe3 100644 --- a/man/eval_code.Rd +++ b/man/eval_code.Rd @@ -16,7 +16,7 @@ \arguments{ \item{object}{(\code{teal_data_module})} -\item{code}{(\code{character} or \code{language}) code to evaluate. Also accepts and stores comments} +\item{code}{(\code{character} or \code{language}) code to evaluate. If \code{character}, comments are retained.} } \value{ Returns a \code{teal_data_module} object. diff --git a/man/example_module.Rd b/man/example_module.Rd index 9a0c88862b..130acb0f11 100644 --- a/man/example_module.Rd +++ b/man/example_module.Rd @@ -24,10 +24,7 @@ A \code{teal} module which can be included in the \code{modules} argument to \co } \examples{ app <- init( - data = teal_data( - dataset("IRIS", iris), - dataset("MTCARS", mtcars) - ), + data = teal_data(IRIS = iris, MTCARS = mtcars), modules = example_module() ) if (interactive()) { diff --git a/man/filter_calls_module.Rd b/man/filter_calls_module.Rd index f7c7cde320..53bf4b6faf 100644 --- a/man/filter_calls_module.Rd +++ b/man/filter_calls_module.Rd @@ -16,7 +16,7 @@ and avoids session restarts! } \examples{ app <- init( - data = list(iris = iris, mtcars = mtcars), + data = teal_data(iris = iris, mtcars = mtcars), modules = teal:::filter_calls_module(), header = "Simple teal app" ) diff --git a/man/get_datasets_code.Rd b/man/get_datasets_code.Rd index f7256b8340..b68839cef8 100644 --- a/man/get_datasets_code.Rd +++ b/man/get_datasets_code.Rd @@ -14,11 +14,11 @@ get_datasets_code(datanames, datasets, hashes) \item{hashes}{named (\code{list}) of hashes per dataset} } \value{ -\code{character(3)} containing the following elements: +Character string concatenated from the following elements: \itemize{ \item data pre-processing code (from \code{data} argument in \code{init}) \item hash check of loaded objects -\item filter code +\item filter code (if any) } } \description{ diff --git a/man/init.Rd b/man/init.Rd index 0e7ef57199..308af0d5dc 100644 --- a/man/init.Rd +++ b/man/init.Rd @@ -15,16 +15,10 @@ init( ) } \arguments{ -\item{data}{(\code{TealData} or \code{TealDataset} or \code{TealDatasetConnector} or \code{list} or \code{data.frame} -or \code{MultiAssayExperiment}, \code{teal_data}, \code{teal_data_module})\cr -\code{R6} object as returned by \code{\link[teal.data:cdisc_data]{teal.data::cdisc_data()}}, \code{\link[teal.data:teal_data]{teal.data::teal_data()}}, -\code{\link[teal.data:cdisc_dataset]{teal.data::cdisc_dataset()}}, \code{\link[teal.data:dataset]{teal.data::dataset()}}, \code{\link[teal.data:dataset_connector]{teal.data::dataset_connector()}} or -\code{\link[teal.data:cdisc_dataset_connector]{teal.data::cdisc_dataset_connector()}} or \code{\link[=teal_data_module]{teal_data_module()}} or a single \code{data.frame} or -a \code{MultiAssayExperiment} -or a list of the previous objects or function returning a named list. -NOTE: teal does not guarantee reproducibility of the code when names of the list elements -do not match the original object names. To ensure reproducibility please use \code{\link[teal.data:teal_data]{teal.data::teal_data()}} -or \code{\link[teal.data:cdisc_data]{teal.data::cdisc_data()}} with \code{check = TRUE} enabled.} +\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_modules} or simply a list of a named list of objects +(\code{data.frame} or \code{MultiAssayExperiment}).} \item{modules}{(\code{list}, \code{teal_modules} or \code{teal_module})\cr nested list of \code{teal_modules} or \code{teal_module} objects or a single @@ -63,13 +57,10 @@ End-users: This is the most important function for you to start a teal app that is composed out of teal modules. } \examples{ -new_iris <- transform(iris, id = seq_len(nrow(iris))) -new_mtcars <- transform(mtcars, id = seq_len(nrow(mtcars))) - app <- init( data = teal_data( - dataset("new_iris", new_iris), - dataset("new_mtcars", new_mtcars), + new_iris = transform(iris, id = seq_len(nrow(iris))), + new_mtcars = transform(mtcars, id = seq_len(nrow(mtcars))), code = " new_iris <- transform(iris, id = seq_len(nrow(iris))) new_mtcars <- transform(mtcars, id = seq_len(nrow(mtcars))) @@ -87,7 +78,7 @@ app <- init( "Iris Sepal.Length histogram", server = function(input, output, session, data) { output$hist <- renderPlot( - hist(data[["new_iris"]]()$Sepal.Length) + hist(data()[["new_iris"]]$Sepal.Length) ) }, ui = function(id, ...) { diff --git a/man/module.Rd b/man/module.Rd index 9a81569787..e40022d00e 100644 --- a/man/module.Rd +++ b/man/module.Rd @@ -35,7 +35,7 @@ module( \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{tdata} object, a list of reactive (filtered) data specified in +\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}). @@ -45,8 +45,6 @@ the \code{filters} argument. \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{data} (optional) module will receive 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{...} (optional) \code{ui_args} elements will be passed to the module named argument or to the \code{...}. }} @@ -82,7 +80,7 @@ This function embeds a \code{shiny} module inside a \code{teal} application. One library(shiny) app <- init( - data = teal_data(dataset("iris", iris)), + data = teal_data(iris = iris), modules = list( module( label = "Module", diff --git a/man/modules.Rd b/man/modules.Rd index 996f468fd9..dbcc71d7f4 100644 --- a/man/modules.Rd +++ b/man/modules.Rd @@ -46,7 +46,7 @@ shapes the navigation panel of a \code{teal} application. library(shiny) app <- init( - data = teal_data(dataset("iris", iris)), + data = teal_data(iris = iris), modules = modules( label = "Modules", modules( diff --git a/man/reexports.Rd b/man/reexports.Rd index 71c397008b..cebb6d4583 100644 --- a/man/reexports.Rd +++ b/man/reexports.Rd @@ -4,7 +4,7 @@ \name{reexports} \alias{reexports} \alias{\%>\%} -\alias{deprecate_soft} +\alias{badge} \title{Objects exported from other packages} \keyword{internal} \description{ @@ -12,7 +12,7 @@ These objects are imported from other packages. Follow the links below to see their documentation. \describe{ - \item{lifecycle}{\code{\link[lifecycle]{deprecate_soft}}} + \item{lifecycle}{\code{\link[lifecycle]{badge}}} \item{magrittr}{\code{\link[magrittr:pipe]{\%>\%}}} }} diff --git a/man/srv_teal_with_splash.Rd b/man/srv_teal_with_splash.Rd index 9b1d4312d1..decc5b42ee 100644 --- a/man/srv_teal_with_splash.Rd +++ b/man/srv_teal_with_splash.Rd @@ -14,16 +14,10 @@ the server function must be called with \code{\link[shiny:moduleServer]{shiny::m 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{TealData} or \code{TealDataset} or \code{TealDatasetConnector} or \code{list} or \code{data.frame} -or \code{MultiAssayExperiment}, \code{teal_data}, \code{teal_data_module})\cr -\code{R6} object as returned by \code{\link[teal.data:cdisc_data]{teal.data::cdisc_data()}}, \code{\link[teal.data:teal_data]{teal.data::teal_data()}}, -\code{\link[teal.data:cdisc_dataset]{teal.data::cdisc_dataset()}}, \code{\link[teal.data:dataset]{teal.data::dataset()}}, \code{\link[teal.data:dataset_connector]{teal.data::dataset_connector()}} or -\code{\link[teal.data:cdisc_dataset_connector]{teal.data::cdisc_dataset_connector()}} or \code{\link[=teal_data_module]{teal_data_module()}} or a single \code{data.frame} or -a \code{MultiAssayExperiment} -or a list of the previous objects or function returning a named list. -NOTE: teal does not guarantee reproducibility of the code when names of the list elements -do not match the original object names. To ensure reproducibility please use \code{\link[teal.data:teal_data]{teal.data::teal_data()}} -or \code{\link[teal.data:cdisc_data]{teal.data::cdisc_data()}} with \code{check = TRUE} enabled.} +\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_modules} 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 diff --git a/man/tdata.Rd b/man/tdata.Rd index d686b0f621..77da52688a 100644 --- a/man/tdata.Rd +++ b/man/tdata.Rd @@ -3,12 +3,9 @@ \name{tdata} \alias{tdata} \alias{new_tdata} -\alias{get_code.tdata} \title{Create a \code{tdata} Object} \usage{ new_tdata(data, code = "", join_keys = NULL, metadata = NULL) - -\method{get_code}{tdata}(x, ...) } \arguments{ \item{data}{A \verb{named list} of \code{data.frames} (or \code{MultiAssayExperiment}) @@ -25,15 +22,12 @@ datasets.} \item{metadata}{A \verb{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.} - -\item{x}{a \code{tdata} object} - -\item{...}{additional arguments for the generic} } \value{ 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 (or \code{MultiAssayExperiment}), with attributes: \itemize{ @@ -56,9 +50,12 @@ data <- new_tdata( isolate(data[["iris"]]()) # Get code -isolate(get_code(data)) +isolate(get_code_tdata(data)) # Get metadata get_metadata(data, "iris") } +\seealso{ +\code{as_tdata} +} diff --git a/man/tdata_deprecation.Rd b/man/tdata_deprecation.Rd new file mode 100644 index 0000000000..7c9a1a570d --- /dev/null +++ b/man/tdata_deprecation.Rd @@ -0,0 +1,32 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/tdata.R +\name{as_tdata} +\alias{as_tdata} +\title{Downgrade \code{teal_data} objects in modules for compatibility.} +\usage{ +as_tdata(x) +} +\arguments{ +\item{x}{data object, either \code{tdata} or \code{teal_data}, the latter possibly in a reactive expression} +} +\value{ +Object of class \code{tdata}. +} +\description{ +Convert \code{teal_data} to \code{tdata} in \code{teal} modules. +} +\details{ +Recent changes in \code{teal} cause modules to fail because modules expect a \code{tdata} object +to be passed to the \code{data} argument but instead they receive a \code{teal_data} object, +which is additionally wrapped in a reactive expression in the server functions. +In order to easily adapt such modules without a proper refactor, +use this function to downgrade the \code{data} argument. +} +\examples{ +td <- teal_data() +td <- within(td, iris <- iris) \%>\% within(mtcars <- mtcars) +td +as_tdata(td) +as_tdata(reactive(td)) + +} diff --git a/man/teal_data_module.Rd b/man/teal_data_module.Rd index 6bf39fb807..ba5aec55da 100644 --- a/man/teal_data_module.Rd +++ b/man/teal_data_module.Rd @@ -18,9 +18,9 @@ must return reactive expression containing \code{teal_data} object} Object of class \code{teal_data_module}. } \description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} Creates \code{teal_data_module} object - a \code{shiny} module to supply or modify data in a \code{teal} application. -} -\details{ + This function creates a \code{shiny} module that allows for running data pre-processing code 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. diff --git a/man/teal_slices.Rd b/man/teal_slices.Rd index 7894d29c2b..486b969ce2 100644 --- a/man/teal_slices.Rd +++ b/man/teal_slices.Rd @@ -94,11 +94,11 @@ filter <- teal_slices( ) app <- teal::init( + data = list(iris = iris, mtcars = mtcars), modules = list( module("module1"), module("module2") ), - data = list(iris, mtcars), filter = filter ) diff --git a/man/ui_teal_with_splash.Rd b/man/ui_teal_with_splash.Rd index 29396ed74c..35fcf371fd 100644 --- a/man/ui_teal_with_splash.Rd +++ b/man/ui_teal_with_splash.Rd @@ -16,16 +16,10 @@ ui_teal_with_splash( \item{id}{(\code{character(1)})\cr module id} -\item{data}{(\code{TealData} or \code{TealDataset} or \code{TealDatasetConnector} or \code{list} or \code{data.frame} -or \code{MultiAssayExperiment}, \code{teal_data}, \code{teal_data_module})\cr -\code{R6} object as returned by \code{\link[teal.data:cdisc_data]{teal.data::cdisc_data()}}, \code{\link[teal.data:teal_data]{teal.data::teal_data()}}, -\code{\link[teal.data:cdisc_dataset]{teal.data::cdisc_dataset()}}, \code{\link[teal.data:dataset]{teal.data::dataset()}}, \code{\link[teal.data:dataset_connector]{teal.data::dataset_connector()}} or -\code{\link[teal.data:cdisc_dataset_connector]{teal.data::cdisc_dataset_connector()}} or \code{\link[=teal_data_module]{teal_data_module()}} or a single \code{data.frame} or -a \code{MultiAssayExperiment} -or a list of the previous objects or function returning a named list. -NOTE: teal does not guarantee reproducibility of the code when names of the list elements -do not match the original object names. To ensure reproducibility please use \code{\link[teal.data:teal_data]{teal.data::teal_data()}} -or \code{\link[teal.data:cdisc_data]{teal.data::cdisc_data()}} with \code{check = TRUE} enabled.} +\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_modules} or simply a list of a named list of objects +(\code{data.frame} or \code{MultiAssayExperiment}).} \item{title}{(\code{NULL} or \code{character})\cr The browser window title (defaults to the host URL of the page).} diff --git a/man/within.teal_data_module.Rd b/man/within.teal_data_module.Rd index 8b39b09457..98e7a4087f 100644 --- a/man/within.teal_data_module.Rd +++ b/man/within.teal_data_module.Rd @@ -2,16 +2,16 @@ % Please edit documentation in R/teal_data_module-eval_code.R \name{within.teal_data_module} \alias{within.teal_data_module} -\title{Evaluate expression in \code{qenv} object.} +\title{Code Tracking With \code{qenv} Object} \usage{ \method{within}{teal_data_module}(data, expr, ...) } \arguments{ \item{data}{(\code{teal_data_module}) object} -\item{expr}{\code{expression} to evaluate} +\item{expr}{(\code{expression}) to evaluate. Must be inline code, see \verb{Using language objects...}} -\item{...}{\code{name:value} pairs to inject values into \code{expr}} +\item{...}{see \code{Details}} } \value{ Returns a \code{teal_data_module} object with a delayed evaluation of \code{expr} @@ -22,10 +22,22 @@ Convenience function for evaluating inline code inside the environment of a \code{teal_data_module} } \details{ -This is a wrapper for \code{eval_code} that provides a simplified way of passing code for evaluation. -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}. +\code{qenv()} instantiates a \code{qenv} with an empty environment. +Any changes must be made by evaluating code in it with \code{eval_code} or \code{within}, thereby ensuring reproducibility. + +\code{new_qenv()} (\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} and not recommended) +can instantiate a \code{qenv} object with data in the environment and code registered. + +\code{eval_code} evaluates given code in the \code{qenv} environment and appends it to the \code{code} slot. +Thus, if the \code{qenv} had been instantiated empty, contents of the environment are always a result of the stored code. + +\code{get_code} retrieves the code stored in the \code{qenv}. \code{...} passes arguments to methods. + +\code{within} is a convenience function for evaluating inline code inside the environment of a \code{qenv}. +It is a method for the \code{base} generic that wraps \code{eval_code} to provide a simplified way of passing code. +\code{within} 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{ tdm <- teal_data_module( diff --git a/tests/testthat/test-init.R b/tests/testthat/test-init.R index b19df7aad7..53bd16aada 100644 --- a/tests/testthat/test-init.R +++ b/tests/testthat/test-init.R @@ -1,19 +1,3 @@ -testthat::test_that("init data accepts TealData object", { - lifecycle::expect_deprecated( - init( - data = teal.data::cdisc_data( - teal.data::cdisc_dataset( - "ADSL", - as.data.frame(as.list(setNames(nm = teal.data::get_cdisc_keys("ADSL")))), - parent = character(0), - keys = teal.data::get_cdisc_keys("ADSL") - ) - ), - modules = teal:::example_modules(datanames = "ADSL") - ) - ) -}) - testthat::test_that("init data accepts teal_data object", { testthat::expect_no_error( init( @@ -23,39 +7,6 @@ testthat::test_that("init data accepts teal_data object", { ) }) -testthat::test_that("init data throws an error with input other than TealData, teal_data and list(ui, server)", { - character_vector <- c("a", "b", "c") - numeric_vector <- c(1, 2, 3) - matrix_d <- as.matrix(c(1, 2, 3)) - teal_data_list <- list(teal.data::teal_data(teal.data::dataset("iris", iris))) - testthat::expect_error(init(data = character_vector, modules = modules(example_module()))) - testthat::expect_error(init(data = numeric_vector, modules = modules(example_module()))) - testthat::expect_error(init(data = numeric_vector, modules = modules(example_module()))) - testthat::expect_error(init(data = matrix_d, modules = modules(example_module()))) - testthat::expect_error(init(data = teal_data_list, modules = modules(example_module()))) -}) - -testthat::test_that("init data accepts a single TealDataset", { - testthat::expect_no_error( - init( - data = teal.data::dataset("ADSL", head(iris)), - modules = teal:::example_modules(datanames = "ADSL") - ) - ) -}) - -testthat::test_that("init data accepts a list of single TealDataset without renaming", { - testthat::expect_no_error( - init( - data = list( - teal.data::dataset("ADSL", head(iris)), - teal.data::dataset("ADTTE", head(iris)) - ), - modules = teal:::example_modules() - ) - ) -}) - testthat::test_that("init data accepts a single dataframe", { testthat::expect_no_error( init(data = list(iris = iris), modules = modules(example_module())) @@ -63,6 +14,7 @@ testthat::test_that("init data accepts a single dataframe", { }) 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())) ) @@ -77,15 +29,6 @@ testthat::test_that("init data accepts a list of single dataframe with renaming" ) }) -testthat::test_that("init data accepts a list of a TealDataset and a dataframe without renaming", { - testthat::expect_no_error( - init( - data = list(teal.data::dataset("ADSL", head(iris)), iris), - modules = modules(example_module()) - ) - ) -}) - testthat::test_that("init data accepts a single MultiAssayExperiment object", { utils::data(miniACC, package = "MultiAssayExperiment") testthat::expect_no_error( @@ -93,11 +36,6 @@ testthat::test_that("init data accepts a single MultiAssayExperiment object", { ) }) -testthat::test_that("init data accepts a list of a single MultiAssayExperiment object without renaming", { - utils::data(miniACC, package = "MultiAssayExperiment") - testthat::expect_no_error(init(data = list(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()))) @@ -108,38 +46,6 @@ testthat::test_that("init data acceptsa mixed list of MultiAssayExperiment objec testthat::expect_no_error(init(data = list(x = miniACC, y = head(iris)), modules = modules(example_module()))) }) -testthat::test_that("init data accepts a list of a TealDataset and a dataframe with renaming", { - testthat::expect_no_error(init( - data = list( - data1 = teal.data::dataset("iris", head(iris)), - data2 = as.data.frame(as.list(setNames(nm = teal.data::get_cdisc_keys("ADSL")))) - ), - modules = modules(example_module()) - )) -}) - -testthat::test_that("init data accepts a list of mixed TealDataset and dataframe with mixed renaming", { - testthat::expect_no_error( - init( - data = list( - data1 = teal.data::dataset("iris", head(iris)), - iris2 = iris - ), - modules = modules(example_module()) - ) - ) -}) - -testthat::test_that("init data accepts TealDatasetConnector object", { - dsc1 <- teal.data::dataset_connector("iris", teal.data::callable_function(function() head(iris))) - testthat::expect_no_error(init(data = dsc1, modules = modules(example_module()))) -}) - -testthat::test_that("init data accepts a list of TealDatasetConnector object", { - dsc1 <- list(teal.data::dataset_connector("iris", teal.data::callable_function(function() head(iris)))) - testthat::expect_no_error(init(data = dsc1, modules = modules(example_module()))) -}) - testthat::test_that("init data accepts teal_data_module", { testthat::expect_no_error( init( @@ -151,26 +57,26 @@ testthat::test_that("init data accepts teal_data_module", { testthat::test_that("init modules accepts a teal_modules object", { mods <- modules(example_module(), example_module()) - testthat::expect_no_error(init(data = iris, modules = mods)) + testthat::expect_no_error(init(data = list(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 = iris, modules = mods)) + testthat::expect_no_error(init(data = list(iris = iris), modules = mods)) }) testthat::test_that("init modules accepts a teal_module object", { mods <- example_module() - testthat::expect_no_error(init(data = iris, modules = mods)) + testthat::expect_no_error(init(data = list(iris = iris), modules = mods)) }) 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), modules = modules(example_module()), filter = fs)) + testthat::expect_no_error(init(data = list(iris = iris), modules = modules(example_module()), filter = fs)) testthat::expect_error( - init(data = list(iris), modules = modules(example_module()), filter = unclass(fs)), + init(data = list(iris = iris), modules = modules(example_module()), filter = unclass(fs)), "Assertion failed" ) }) diff --git a/tests/testthat/test-module_nested_tabs.R b/tests/testthat/test-module_nested_tabs.R index f040e14be9..6ef8a8c3ad 100644 --- a/tests/testthat/test-module_nested_tabs.R +++ b/tests/testthat/test-module_nested_tabs.R @@ -205,7 +205,10 @@ testthat::test_that("srv_nested_tabs.teal_module does not pass data if not in th testthat::test_that("srv_nested_tabs.teal_module does pass data if in the args explicitly", { module <- module( server = function(id, data, ...) { - moduleServer(id, function(input, output, session) checkmate::assert_class(data, "tdata")) + moduleServer(id, function(input, output, session) { + checkmate::assert_class(data, "reactive") + checkmate::assert_class(data(), "teal_data") + }) }, datanames = NULL ) @@ -245,10 +248,12 @@ testthat::test_that("srv_nested_tabs.teal_module passes data to the server modul ) }) -testthat::test_that("srv_nested_tabs.teal_module passes datasets to the server module", { - module <- module(server = function(id, datasets) { - moduleServer(id, function(input, output, session) checkmate::assert_class(datasets, "FilteredData")) - }) +testthat::test_that("srv_nested_tabs.teal_module passes (deprecated) datasets to the server module", { + module <- lifecycle::expect_deprecated( + module(server = function(id, datasets) { + moduleServer(id, function(input, output, session) checkmate::assert_class(datasets, "FilteredData")) + }) + ) testthat::expect_error( shiny::testServer( @@ -286,26 +291,6 @@ testthat::test_that("srv_nested_tabs.teal_module passes server_args to the ...", ) }) -testthat::test_that("srv_nested_tabs.teal_module warns if both data and datasets are passed", { - module <- module(datanames = NULL, label = "test module", server = function(id, datasets, data) { - moduleServer(id, function(input, output, session) NULL) - }) - - testthat::expect_warning( - shiny::testServer( - app = srv_nested_tabs, - args = list( - id = "test", - datasets = list(`test module` = filtered_data), - modules = modules(module), - reporter = teal.reporter::Reporter$new() - ), - expr = NULL - ), - "Module 'test module' has `data` and `datasets` arguments in the formals" - ) -}) - fp_api <- teal.slice:::FilterPanelAPI$new(filtered_data) testthat::test_that("srv_nested_tabs.teal_module doesn't pass filter_panel_api if not in the args explicitly", { module <- module(server = function(id, ...) { @@ -379,33 +364,6 @@ testthat::test_that("srv_nested_tabs.teal_module passes filter_panel_api to the }) -testthat::test_that(".datasets_to_data accepts a reactiveVal as trigger_data input", { - 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)) - ) - ) - module <- test_module_wdata(datanames = c("d1", "d2")) - trigger_data <- reactiveVal(1L) - testthat::expect_silent(shiny::isolate(.datasets_to_data(module, datasets, trigger_data))) -}) - -testthat::test_that(".datasets_to_data throws error if trigger_data is not a reactiveVal function", { - 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)) - ) - ) - module <- test_module_wdata(datanames = "all") - trigger_data <- 1 - testthat::expect_error( - shiny::isolate(.datasets_to_data(module, datasets, trigger_data)), - "Must inherit from class 'reactiveVal', but has class 'numeric'." - ) -}) - testthat::test_that(".datasets_to_data returns data which is filtered", { datasets <- get_example_filtered_data() datasets$set_filter_state( @@ -414,12 +372,11 @@ testthat::test_that(".datasets_to_data returns data which is filtered", { ) ) module <- test_module_wdata(datanames = c("d1", "d2")) - trigger_data <- reactiveVal(1L) - data <- shiny::isolate(.datasets_to_data(module, datasets, trigger_data)) + data <- shiny::isolate(.datasets_to_data(module, datasets)) - d1_filtered <- shiny::isolate(data[["d1"]]()) + d1_filtered <- data[["d1"]] testthat::expect_equal(d1_filtered, data.frame(id = 1:2, pk = 2:3, val = 1:2)) - d2_filtered <- shiny::isolate(data[["d2"]]()) + d2_filtered <- data[["d2"]] testthat::expect_equal(d2_filtered, data.frame(id = 1:5, value = 1:5)) }) @@ -427,18 +384,16 @@ testthat::test_that(".datasets_to_data returns data which is filtered", { testthat::test_that(".datasets_to_data returns only data requested by modules$datanames", { datasets <- get_example_filtered_data() module <- test_module_wdata(datanames = "d1") - trigger_data <- reactiveVal(1L) - data <- .datasets_to_data(module, datasets, trigger_data) - testthat::expect_equal(shiny::isolate(names(data)), "d1") + data <- shiny::isolate(.datasets_to_data(module, datasets)) + testthat::expect_equal(datanames(data), "d1") }) -testthat::test_that(".datasets_to_data returns tdata object", { +testthat::test_that(".datasets_to_data returns teal_data object", { datasets <- get_example_filtered_data() module <- test_module_wdata(datanames = c("d1", "d2")) - trigger_data <- reactiveVal(1L) - data <- .datasets_to_data(module, datasets, trigger_data) + data <- shiny::isolate(.datasets_to_data(module, datasets)) - testthat::expect_s3_class(data, "tdata") + testthat::expect_s4_class(data, "teal_data") # join_keys testthat::expect_equal( @@ -447,8 +402,9 @@ testthat::test_that(".datasets_to_data returns tdata object", { ) # code + skip("skipped until we resolve handling code in teal.data:::new_teal_data") testthat::expect_equal( - shiny::isolate(get_code(data)), + teal.code::get_code(data), c( get_rcode_str_install(), get_rcode_libraries(), diff --git a/tests/testthat/test-module_teal_with_splash.R b/tests/testthat/test-module_teal_with_splash.R index adb33fb716..e5b1b7a02c 100644 --- a/tests/testthat/test-module_teal_with_splash.R +++ b/tests/testthat/test-module_teal_with_splash.R @@ -128,41 +128,6 @@ testthat::test_that( } ) - -testthat::test_that("srv_teal_with_splash creates raw_data based on DDL returns NULL before loading", { - x <- dataset_connector(dataname = "test_dataset", pull_callable = callable_code("iris")) - delayed_data <- teal_data(x) - shiny::testServer( - app = srv_teal_with_splash, - args = list( - id = "test", - data = delayed_data, - modules = modules(example_module()) - ), - expr = testthat::expect_null(raw_data()) - ) -}) - -testthat::test_that("srv_teal_with_splash creates raw_data based on DDL returns pulled data when loaded", { - teal.logger::suppress_logs() - x <- dataset_connector(dataname = "iris", pull_callable = callable_code("iris")) - delayed_data <- teal_data(x) - shiny::testServer( - app = srv_teal_with_splash, - args = list( - id = "test", - data = delayed_data, - modules = modules(example_module()) - ), - expr = { - testthat::expect_null(raw_data()) - session$setInputs(`startapp_module-submit` = TRUE) # DDL has independent session id (without ns) - testthat::expect_is(raw_data(), "teal_data") - testthat::expect_identical(raw_data()[["iris"]], iris) - } - ) -}) - testthat::test_that("srv_teal_with_splash teal_data_rv_validate throws when incompatible module's datanames", { shiny::testServer( app = srv_teal_with_splash, diff --git a/tests/testthat/test-modules.R b/tests/testthat/test-modules.R index dc2fd11c49..7dc93f2941 100644 --- a/tests/testthat/test-modules.R +++ b/tests/testthat/test-modules.R @@ -1,24 +1,13 @@ -dataset_1 <- teal.data::dataset("iris", head(iris)) -adsl_df <- as.data.frame(as.list(setNames(nm = teal.data::get_cdisc_keys("ADSL")))) -adsl_dataset <- teal.data::cdisc_dataset( - "ADSL", adsl_df, - parent = character(0), keys = teal.data::get_cdisc_keys("ADSL") -) - -call_module_server_fun <- function(input, output, session, data, datasets) { +call_module_server_fun <- function(input, output, session, data) { } -module_server_fun <- function(id, datasets) { +module_server_fun <- function(id, data) { } ui_fun1 <- function(id, ...) { tags$p(paste0("id: ", id)) } -ui_fun2 <- function(id, datasets) { - tags$p(paste0("id: ", id)) -} - testthat::test_that("Calling module() does not throw", { testthat::expect_no_error(suppressMessages(module())) }) @@ -35,6 +24,14 @@ testthat::test_that("module requires label argument to be a string different tha testthat::expect_error(module(label = "global_filters"), "is reserved in teal") }) +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" + ) +}) + + testthat::test_that("module expects server being a shiny server module with any argument", { testthat::expect_no_error(module(server = function(id) NULL)) @@ -81,6 +78,11 @@ testthat::test_that("module requires ui_args argument to be a list", { testthat::expect_error(module(ui_args = list(1, 2, 3)), "Must have names") }) +testthat::test_that("module throws when ui has data or datasets argument", { + testthat::expect_error(module(ui = function(id, data) NULL)) + testthat::expect_error(module(ui = function(id, datasets) NULL)) +}) + testthat::test_that("module expects ui being a shiny ui module with any argument", { testthat::expect_no_error(module(ui = function(id) NULL)) testthat::expect_no_error(module(ui = function(id, any_argument) NULL)) @@ -388,7 +390,7 @@ testthat::test_that("is_arg_used throws error if object is not teal_module or te }) testthat::test_that("is_arg_used returns true if teal_module has given `arg` in server function args", { - testthat::expect_true(is_arg_used(module(server = function(id, datasets, reporter) NULL), "reporter")) + testthat::expect_true(is_arg_used(module(server = function(id, data, reporter) NULL), "reporter")) }) testthat::test_that("is_arg_used returns false if teal_module does not have reporter in server function args", { @@ -406,7 +408,7 @@ testthat::test_that("is_arg_used returns false if teal_modules has no children u }) testthat::test_that("is_arg_used returns true if teal_modules has at least one child using given `arg`", { - server_fun_with_reporter <- function(id, datasets, reporter) NULL + server_fun_with_reporter <- function(id, data, reporter) NULL mod <- module() mod_with_reporter <- module(server = server_fun_with_reporter) diff --git a/tests/testthat/test-tdata.R b/tests/testthat/test-tdata.R index 6511a35bc6..4ed51fa4f0 100644 --- a/tests/testthat/test-tdata.R +++ b/tests/testthat/test-tdata.R @@ -1,3 +1,5 @@ +withr::local_options(lifecycle_verbosity = "quiet") + # ---- constructor ---- testthat::test_that("new_tdata accepts reactive and not reactive MAE and data.frames", { utils::data(miniACC, package = "MultiAssayExperiment") @@ -138,7 +140,7 @@ testthat::test_that("get_metadata returns NULL if dataset doesn't exist", { # ---- get_code ---- testthat::test_that("get_code returns empty character if tdata object has no code", { my_tdata <- new_tdata(data = list(iris = iris, mtcars = mtcars)) - testthat::expect_equal("", isolate(get_code(my_tdata))) + testthat::expect_equal("", isolate(get_code_tdata(my_tdata))) }) testthat::test_that("get_code returns character of code if tdata object has code", { @@ -149,14 +151,14 @@ testthat::test_that("get_code returns character of code if tdata object has code data = list(x = iris, mtcars = head(mtcars)), code = reactive(code_string) ) - testthat::expect_equal(isolate(get_code(my_tdata)), code_string) + testthat::expect_equal(isolate(get_code_tdata(my_tdata)), code_string) # not reactive case (for constructor) my_tdata <- new_tdata( data = list(x = iris, mtcars = head(mtcars)), code = code_string ) - testthat::expect_equal(isolate(get_code(my_tdata)), code_string) + testthat::expect_equal(isolate(get_code_tdata(my_tdata)), code_string) }) # ---- get_code wrapper ---- @@ -199,7 +201,7 @@ testthat::test_that("tdata2env throws error if argument is not tdata", { testthat::expect_error(tdata2env(iris), "Must inherit from class 'tdata'") }) -# ---- get_join_keys ---- +# ---- join_keys ---- testthat::test_that("join_keys returns NULL if no join_keys object exists inside tdata", { my_tdata <- new_tdata(data = list(iris = iris, mae = reactive(miniACC))) testthat::expect_null(join_keys(my_tdata)) @@ -218,3 +220,49 @@ testthat::test_that("join_keys returns join_keys object if it exists inside tdat testthat::expect_equal(join_keys(my_tdata), jk) }) + + +# as_tdata ---- +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)) + +testthat::test_that("as_tdata accepts all possible inputs", { + testthat::expect_no_error(as_tdata(data_tdata)) + testthat::expect_no_error(as_tdata(data_teal_data)) + testthat::expect_no_error(as_tdata(data_reactive)) +}) + +testthat::test_that("as_tdata always returns tdata object", { + data_tdata_downgraded <- as_tdata(data_tdata) + data_teal_data_downgraded <- as_tdata(data_teal_data) + data_reactive_downgraded <- as_tdata(data_teal_data) + + testthat::expect_s3_class(data_tdata_downgraded, "tdata") + testthat::expect_s3_class(data_teal_data_downgraded, "tdata") + testthat::expect_s3_class(data_reactive_downgraded, "tdata") +}) + +testthat::test_that("datasets are maintained during conversion", { + data_tdata_downgraded <- as_tdata(data_teal_data) + + datanames_teal_data <- sort(teal.data::datanames(data_teal_data)) + datanames_tdata <- sort(names(data_tdata_downgraded)) + + testthat::expect_identical(datanames_teal_data, datanames_tdata) + + datasets_teal_data <- sapply(datanames_teal_data, function(x) teal.code::get_var(data_teal_data, x)) + datasets_tdata <- sapply(datanames_tdata, function(x) shiny::isolate(data_tdata_downgraded[[x]]())) + + testthat::expect_identical(datasets_teal_data, datasets_tdata) +}) + +testthat::test_that("code is maintained 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/vignettes/adding-support-for-reporting.Rmd b/vignettes/adding-support-for-reporting.Rmd index 6d4a8001e9..a4d305736c 100644 --- a/vignettes/adding-support-for-reporting.Rmd +++ b/vignettes/adding-support-for-reporting.Rmd @@ -12,9 +12,12 @@ vignette: > ## Introduction -The `teal` package offers an integrated reporting feature utilizing the `teal.reporter` package. For a comprehensive explanation of the reporting functionality itself, please refer to the documentation therein. +The `teal` package offers an integrated reporting feature utilizing the `teal.reporter` package. +For a comprehensive explanation of the reporting functionality itself, please refer to the documentation therein. -This article is intended for module developers and aims to provide guidance on enhancing a custom `teal` module with an automatic reporting feature. This enhancement enables users to incorporate snapshots of the module outputs into a report which can then be reviewed in another module automatically provided by `teal`. Thus the app user can interact with the report. +This article is intended for module developers and aims to provide guidance on enhancing a custom `teal` module with an automatic reporting feature. +This enhancement enables users to incorporate snapshots of the module outputs into a report which can then be reviewed in another module automatically provided by `teal`. +Thus the app user can interact with the report. The responsibilities of a module developer include: @@ -25,24 +28,24 @@ The entire life cycle of objects involved in creating the report and configuring ## Custom module -Let us consider the example module from `teal`: +Let us consider an example module, based on the example module from `teal`: ```{r, message=FALSE} library(teal) -teal_example_module <- function(label = "example teal module") { - checkmate::assert_string(label) +example_module <- function(label = "example teal module") { module( label, server = function(id, data) { - checkmate::assert_class(data, "tdata") moduleServer(id, function(input, output, session) { - output$text <- renderPrint(data[[input$dataname]]()) + ns <- session$ns + updateSelectInput(session, "dataname", choices = isolate(datanames(data()))) + output$text <- renderPrint(data()[[input$dataname]]) }) }, - ui = function(id, data) { + ui = function(id) { ns <- NS(id) teal.widgets::standard_layout( output = verbatimTextOutput(ns("text")), - encoding = selectInput(ns("dataname"), "Choose a dataset", choices = names(data)) + encoding = selectInput(ns("dataname"), "Choose a dataset", choices = NULL) ) }, datanames = "all" @@ -55,34 +58,36 @@ Using `teal`, you can launch this example module with the following: ```{r, eval = FALSE} app <- init( data = teal_data(IRIS = iris, MTCARS = mtcars), - modules = teal_example_module() + modules = example_module() ) if (interactive()) shinyApp(app$ui, app$server) ``` -## Add support for Reporting +## Add support for reporting ### Modify the declaration of the server function -The first step is to add an additional argument to the server function declaration - `reporter`. This informs `teal` that the module requires `reporter`, and it will be included when the module is called. See below: +The first step is to add an additional argument to the server function declaration - `reporter`. +This informs `teal` that the module requires `reporter`, and it will be included when the module is called. +See below: ```{r} example_module_with_reporting <- function(label = "example teal module") { - checkmate::assert_string(label) module( label, server = function(id, data, reporter) { - checkmate::assert_class(data, "tdata") moduleServer(id, function(input, output, session) { - output$text <- renderPrint(data[[input$dataname]]()) + ns <- session$ns + updateSelectInput(session, "dataname", choices = isolate(datanames(data()))) + output$text <- renderPrint(data()[[input$dataname]]) }) }, - ui = function(id, data) { + ui = function(id) { ns <- NS(id) teal.widgets::standard_layout( output = verbatimTextOutput(ns("text")), - encoding = selectInput(ns("dataname"), "Choose a dataset", choices = names(data)) + encoding = selectInput(ns("dataname"), "Choose a dataset", choices = NULL) ) }, datanames = "all" @@ -101,36 +106,38 @@ app <- init( 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 `teal.reporter` `ui` and `server` elements into the module body. +`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. -### Insert `UI` and supporting `shiny` modules for adding report cards +### Insert `teal.reporter` module The UI and the server logic necessary for adding cards from `example_module_with_reporting` to the report are provided by `teal.reporter::simple_reporter_ui` and `teal.reporter::simple_reporter_srv`. ```{r} example_module_with_reporting <- function(label = "example teal module") { - checkmate::assert_string(label) module( label, server = function(id, data, reporter) { - checkmate::assert_class(data, "tdata") moduleServer(id, function(input, output, session) { teal.reporter::simple_reporter_srv( id = "reporter", reporter = reporter, card_fun = function(card) card ) - output$text <- renderPrint(data[[input$dataname]]()) + ns <- session$ns + updateSelectInput(session, "dataname", choices = isolate(datanames(data()))) + output$text <- renderPrint(data()[[input$dataname]]) }) }, - ui = function(id, data) { + ui = function(id) { ns <- NS(id) teal.widgets::standard_layout( output = tagList( teal.reporter::simple_reporter_ui(ns("reporter")), verbatimTextOutput(ns("text")) ), - encoding = selectInput(ns("dataname"), "Choose a dataset", choices = names(data)) + encoding = selectInput(ns("dataname"), "Choose a dataset", choices = NULL) ) }, datanames = "all" @@ -149,11 +156,16 @@ app <- init( if (interactive()) shinyApp(app$ui, app$server) ``` -A new piece of `UI` has been added, and the buttons are clickable. The user can now add a card to the report and view it in the `Report previewer` module but the preview is still empty since we have not instructed our module what to put on the card. +A new piece of `UI` has been added, and the buttons are clickable. +The user can now add a card to the report and view it in the `Report previewer` module but the preview is still empty since we have not instructed our module what to put on the card. ### Add content to the card -To add content to a card, we will utilize the public API exposed by the `TealReportCard` class. The `teal.reporter::simple_reporter_srv` module accepts the `card_fun` argument that determines the appearance of the output from our custom module. `ReportCard` and its derivatives allow the sequential addition of content according to the order of method calls. To explore the content, we can use the `$get_content` method. For further details, refer to the documentation of `TealReportCard` and `teal.reporter::ReportCard`. +To add content to a card, we will utilize the public API exposed by the `TealReportCard` class. +The `teal.reporter::simple_reporter_srv` module accepts the `card_fun` argument that determines the appearance of the output from our custom module. +`ReportCard` and its derivatives allow the sequential addition of content according to the order of method calls. +To explore the content, we can use the `$get_content` method. +For further details, refer to the documentation of `TealReportCard` and `teal.reporter::ReportCard`. We will add simple text to the card by modifying the `card_fun` argument passed to `teal.reporter::simple_reporter_srv`. The function must return the `card` object, otherwise errors may occur in `teal`. @@ -165,24 +177,28 @@ custom_function <- function(card = teal.reporter::ReportCard$new()) { } example_module_with_reporting <- function(label = "example teal module") { - checkmate::assert_string(label) module( label, server = function(id, data, reporter) { - checkmate::assert_class(data, "tdata") moduleServer(id, function(input, output, session) { - teal.reporter::simple_reporter_srv(id = "simpleReporter", reporter = reporter, card_fun = custom_function) - output$text <- renderPrint(data[[input$dataname]]()) + teal.reporter::simple_reporter_srv( + id = "reporter", + reporter = reporter, + card_fun = custom_function + ) + ns <- session$ns + updateSelectInput(session, "dataname", choices = isolate(datanames(data()))) + output$text <- renderPrint(data()[[input$dataname]]) }) }, - ui = function(id, data) { + ui = function(id) { ns <- NS(id) teal.widgets::standard_layout( output = tagList( - teal.reporter::simple_reporter_ui(ns("simpleReporter")), + teal.reporter::simple_reporter_ui(ns("reporter")), verbatimTextOutput(ns("text")) ), - encoding = selectInput(ns("dataname"), "Choose a dataset", choices = names(data)) + encoding = selectInput(ns("dataname"), "Choose a dataset", choices = NULL) ) }, datanames = "all" @@ -203,13 +219,15 @@ Now, an application user can see the text added by `custom_function` in the `Rep ### Add non-text content to the card -`teal.reporter` supports the addition of tables, charts, and more. For more information, explore the API of `teal.reporter::ReportCard` to learn about the supported content types. +`teal.reporter` supports the addition of tables, charts, and more. +For more information, explore the API of `teal.reporter::ReportCard` to learn about the supported content types. ### `TealReportCard` -`teal` exports the `TealReportCard` class, which extends the `teal.reporter::ReportCard` class and provides several convenient methods to facilitate working with `teal` features like the filter panel or source code. For more details, refer to the documentation of `TealReportCard`. +`teal` exports the `TealReportCard` class, which extends the `teal.reporter::ReportCard` class and provides several convenient methods to facilitate working with `teal` features like the filter panel or source code. +For more details, refer to the documentation of `TealReportCard`. -To support `TealReportCard`, the function that is passed to `teal.reporter::simple_reporter_srv` must define a default value for the card, as shown below: +To support `TealReportCard`, the function that is passed to `teal.reporter::simple_reporter_srv` must define a default value for the card, as shown below: ```{r} custom_function <- function(card = TealReportCard$new()) { @@ -222,9 +240,13 @@ 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. +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. -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. 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`. +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. +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`. ```{r} library(teal) @@ -235,32 +257,30 @@ example_reporter_module <- function(label = "Example") { label, server = function(id, data, reporter, filter_panel_api) { with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelApi") - checkmate::assert_class(data, "tdata") moduleServer(id, function(input, output, session) { - dat <- reactive(data[[input$dataname]]()) - output$nrow_ui <- renderUI({ - sliderInput(session$ns("nrow"), "Number of rows:", 1, nrow(data[[input$dataname]]()), 10) + ns <- session$ns + updateSelectInput(session, "dataname", choices = isolate(datanames(data()))) + dat <- reactive(data()[[input$dataname]]) + observe({ + req(dat()) + updateSliderInput(session, "nrow", max = nrow(dat()), value = floor(nrow(dat()) / 5)) }) table_q <- reactive({ + req(input$dataname) req(input$nrow) - - teal.code::new_qenv(tdata2env(data), code = get_code(data)) %>% - teal.code::eval_code( - substitute( - result <- head(data, nrows), - list( - data = as.name(input$dataname), - nrows = input$nrow - ) - ) - ) + within( + data(), + result <- head(dataset, nrows), + dataset = as.name(input$dataname), + nrows = input$nrow + ) }) output$table <- renderTable(table_q()[["result"]]) ### REPORTER - card_fun <- function(card = ReportCard$new(), comment) { + card_fun <- function(card = teal.reporter::ReportCard$new(), comment) { card$set_name("Table Module") card$append_text(paste("Selected dataset", input$dataname), "header2") card$append_text("Selected Filters", "header3") @@ -270,27 +290,33 @@ example_reporter_module <- function(label = "Example") { card$append_text("Encoding", "header3") card$append_text( yaml::as.yaml( - stats::setNames(lapply(c("dataname", "nrow"), function(x) input[[x]]), c("dataname", "nrow")) + stats::setNames( + lapply(c("dataname", "nrow"), function(x) input[[x]]), c("dataname", "nrow") + ) ), "verbatim" ) card$append_text("Module Table", "header3") card$append_table(table_q()[["result"]]) card$append_text("Show R Code", "header3") - card$append_text(paste(teal.code::get_code(table_q()), collapse = "\n"), "verbatim") + card$append_text(teal.code::get_code(table_q()), "verbatim") if (!comment == "") { card$append_text("Comment", "header3") card$append_text(comment) } card } - teal.reporter::add_card_button_srv("addReportCard", reporter = reporter, card_fun = card_fun) + teal.reporter::add_card_button_srv( + "addReportCard", + reporter = reporter, + card_fun = card_fun + ) teal.reporter::download_report_button_srv("downloadButton", reporter = reporter) teal.reporter::reset_report_button_srv("resetButton", reporter) ### }) }, - ui = function(id, data) { + ui = function(id) { ns <- NS(id) teal.widgets::standard_layout( output = tableOutput(ns("table")), @@ -300,8 +326,8 @@ example_reporter_module <- function(label = "Example") { teal.reporter::download_report_button_ui(ns("downloadButton")), teal.reporter::reset_report_button_ui(ns("resetButton")) ), - selectInput(ns("dataname"), "Choose a dataset", choices = names(data)), - uiOutput(ns("nrow_ui")) + selectInput(ns("dataname"), "Choose a dataset", choices = NULL), + sliderInput(ns("nrow"), "Number of rows", min = 1, max = 1, value = 1, step = 1) ) ) }, @@ -310,19 +336,12 @@ example_reporter_module <- function(label = "Example") { } app <- init( - data = teal_data( - AIR = airquality, - IRI = iris, - code = "data(airquality) - AIR <- airquality - data(iris) - IRIS <- iris" - ), + data = teal_data(AIR = airquality, IRIS = iris), modules = list( example_reporter_module(label = "with Reporter"), example_module(label = "without Reporter") ), - filter = teal_slices(teal_slice(dataname = "AIR", varname = "Month", selected = c(5, 5))), + filter = teal_slices(teal_slice(dataname = "AIR", varname = "Temp", selected = c(72, 85))), header = "Example teal app with reporter" ) diff --git a/vignettes/blueprint/actors.Rmd b/vignettes/blueprint/actors.Rmd index 3f73c3aa55..35bcce157b 100644 --- a/vignettes/blueprint/actors.Rmd +++ b/vignettes/blueprint/actors.Rmd @@ -13,7 +13,7 @@ There are two main actors in the `teal` development: * `teal` App Developer * `teal` Module Developer -## `teal` App Developer +## `teal` app developer ```{r, child="_setup.Rmd"} ``` @@ -44,7 +44,7 @@ When developing a `teal` app, the developer will select the most appropriate `te 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. -## `teal` Module Developer +## `teal` module developer ```{r actors_mermaid2, echo=FALSE} shiny::pre( @@ -75,7 +75,7 @@ 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). -## Workflow in a Clinical Trial Study +## Workflow in a clinical trial study ```{r actors_mermaid3, echo=FALSE} shiny::pre( diff --git a/vignettes/blueprint/dataflow.Rmd b/vignettes/blueprint/dataflow.Rmd index 496015a742..70eead1701 100644 --- a/vignettes/blueprint/dataflow.Rmd +++ b/vignettes/blueprint/dataflow.Rmd @@ -31,12 +31,15 @@ sequenceDiagram; The sequence diagram above illustrates the different stages that data goes through within the `teal` framework, supported by the `teal.slice` R package: -1. Data is loaded into the `teal` app; - - See the [Filter panel vignette](filter_panel.html) for details; +1. Data is created and loaded into `teal` app; + - Data sets are wrapped in a `teal_data` before being passed to the app; + - The [`teal_data` class](input_data.html) facilitates reproducibility; 2. Data is passed to the filter panel; - Users _(or app developers)_ can specify filters to apply; - Filters can be specified globally, for the whole app, or for specific modules; + - Filtering code is appended to the data; + - See the [Filter panel vignette](filter_panel.html) for details; 3. Filtered data is sent to `teal` modules for analysis; - - Each module creates a [`qenv`](qenv.html) object track the code that is be applied to the data _(and used for reproducibility)_; + - Each module receives a `teal_data` object so analysis code applied to the data is tracked _(and can be used to reproduce the whole analysis)_; Whenever filters are added or removed, the data coming into modules is re-computed, providing the `teal` module with new filtered data to conduct the required analysis. diff --git a/vignettes/blueprint/ddl.Rmd b/vignettes/blueprint/ddl.Rmd deleted file mode 100644 index bcf1767a3a..0000000000 --- a/vignettes/blueprint/ddl.Rmd +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: "Delayed Data Loading (DDL)" -author: "NEST CoreDev" -output: rmarkdown::html_vignette -vignette: > - %\VignetteIndexEntry{Delayed Data Loading (DDL)} - %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} ---- - -## Introduction - -The `teal` framework introduced the Delayed Data Loading mechanism to enhance the user experience while reading and loading data in the app. - -## Background - -Usually, when running a shiny app, the dataset must be immediately available in the session for processing and reading. However, if the data is stored externally (such as in a database or other data repository), loading the shiny app may be delayed because the data needs to be fetched first. -If there is additional data processing code, this may further extend the loading process. - -To address this, we created the Delayed Data Loading mechanism. Learn how to use it [here](https://insightsengineering.github.io/teal.data/latest-tag/articles/using-delayed-data-basic.html). - -## What is Delayed Data Loading? - -Delayed Data Loading is an R object managed in `teal.data`, which stores instructions on how to obtain the data, including how to read and process it before it is used in `teal`. -By adopting this approach, `teal` apps can load more quickly, and the data can be fetched after the app is loaded. - -## Splash Screen - -`teal` provides a comprehensive solution for managing the delayed data loading mechanism, which includes the incorporation of a splash screen. -The splash screen serves as a visual cue to the user that data is being loaded and that they should wait for the app to be ready. - -Moreover, the splash screen can be modified to query the user for a password or other access credentials needed to fetch the data. -In cases where the modified splash screen is used, `teal` will display the customized version to prompt the user for the necessary information. - -After the data is fetched and loaded, `teal` removes the splash screen and replaces it with the content of the corresponding `teal` modules. -`teal` also displays a notification indicating that the data has been loaded, and the app has fully started up. - -![Notification that the data is loaded and app is ready to use](../../reference/figures/notification.jpg) - -## Additional resources - -The vignettes on `teal.data` documentation provide with basic and advanced examples for this feature. - -- [Pre-processing Delayed Data](https://insightsengineering.github.io/teal.data/latest-tag/articles/preprocessing-delayed-data.html) -- [Using Delayed Data Loading (basic)](https://insightsengineering.github.io/teal.data/latest-tag/articles/using-delayed-data-basic.html) -- [Creating Delayed Data Classes (Advanced)](https://insightsengineering.github.io/teal.data/latest-tag/articles/using-delayed-data-advanced.html) -- [Testing Delayed Data Loading](https://insightsengineering.github.io/teal.data/latest-tag/articles/testing-delayed-data.html) diff --git a/vignettes/blueprint/filter_panel.Rmd b/vignettes/blueprint/filter_panel.Rmd index 2fb0691049..47a4126ef7 100644 --- a/vignettes/blueprint/filter_panel.Rmd +++ b/vignettes/blueprint/filter_panel.Rmd @@ -24,7 +24,7 @@ Moreover, users can activate or deactivate filter states interactively, either i
-## Filter Flow +## Filter flow ```{r, child="_setup.Rmd"} ``` diff --git a/vignettes/blueprint/in_app_data.Rmd b/vignettes/blueprint/in_app_data.Rmd new file mode 100644 index 0000000000..d6f6dcfab0 --- /dev/null +++ b/vignettes/blueprint/in_app_data.Rmd @@ -0,0 +1,26 @@ +--- +title: "In-App Data" +author: "NEST CoreDev" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{In-App Data} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +## Building data in the App + +Typically the data that is passed into a `teal` application is available before the app is run. +However, this is not always true and in some cases the data will be built only after the app has started. +A good example is pulling the data from an external repository, like a database, or uploading a file. +Additional authentication may be required. + +### `teal_data_module` + +Preprocessing actions can be performed in-app using the `teal_data_module`. +Rather than passing a `teal_data` object to the app, one may pass a _`shiny` module_ that _returns_ a `teal_data` object (wrapped in a reactive expression). +This allows the app developer to include user actions data creation, fetching, and even pre-filtering modification. + +## 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) diff --git a/vignettes/blueprint/input_data.Rmd b/vignettes/blueprint/input_data.Rmd new file mode 100644 index 0000000000..cf602c4980 --- /dev/null +++ b/vignettes/blueprint/input_data.Rmd @@ -0,0 +1,65 @@ +--- +title: "Input Data" +author: "NEST CoreDev" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{Input Data} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +## Introduction + +Reproducibility is paramount in the pharmaceutical industry. +Accurate and consistent results are essential to ensure high-quality research and the safety of patients. +By prioritizing reproducibility, researchers can validate their methods, confirm their findings, and contribute to the advancement of the field. + +The `teal.code` package provides the [`qenv` class](https://insightsengineering.github.io/teal.code/latest-tag/articles/qenv.html) that facilitates code reproducibility. +Code is passed to a `qenv` object, where is evaluated in a specific environment. +`qenv` also stores the code so that it can be retrieved on request. + +The `teal_data` class, which serves as the primary data interface for `teal` applications, inherits this code tracking behavior from `qenv`. + +## 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. +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. + +```{dot teal_data_dot_diagram, echo=FALSE} +digraph G { + teal_data [label = "teal_data"]; + node [shape=box]; + teal_modules [label = "teal modules analysis R code"]; + library [label = "R library() calls"]; + filter_states [label = "filter states R code"]; + data [label = "data preprocessing R code"]; + teal_modules -> teal_data; + library -> teal_data; + edge [dir="back"]; + teal_data -> filter_states; + teal_data -> data; +} +``` + +Learn more about the use of `teal_data` in the [`teal.data` package vignettes](https://insightsengineering.github.io/teal.data/latest-tag/articles). + +## `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. +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. + +![Show R Code](../../man/figures/showrcode.jpg){width=50%} + +The Reporter feature also leverages the `teal_data` object in its operation. +Much like the `Show R Code` mechanism, the code displayed in a Reporter Card is extracted from the `teal_data` object. + +![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). + +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 fe3eea200c..dba76cf04e 100644 --- a/vignettes/blueprint/intro.Rmd +++ b/vignettes/blueprint/intro.Rmd @@ -8,30 +8,30 @@ vignette: > %\VignetteEncoding{UTF-8} --- -`teal` is an interactive data exploration framework based on Shiny, designed with emphasis on analyzing CDISC clinical trial data. +`teal` is an interactive data exploration framework based on Shiny, designed primarily to analyze CDISC clinical trial data. A Shiny Application created with `teal` offers users the ability to: - Import data from external sources; - Dynamically filter data for analysis; -- Generate reproducible code for future analysis -- Create downloadable reports of analysis results _(for analysis modules that support reporting)_. +- Generate reproducible code for future analysis; +- Create and download reports of analysis results _(for analysis modules that support reporting)_. -Moreover, the `teal` framework provides application developers with a wide range of customizable standard analysis modules to integrate into their applications, along with a logging framework that helps facilitate debugging. +Moreover, the `teal` framework provides application developers with a wide range of customizable standard analysis modules to integrate into their applications, along with a logging framework that facilitates debugging. Additionally, advanced users of the framework can develop new analysis modules and easily integrate them into any `teal` application. The `teal` framework's functionality draws heavily from the following packages: | R package | Description | |----------------------|:------------------------------------------------------------------------| -|[`teal`](https://github.com/insightsengineering/teal) | `shiny`-based interactive exploration framework for analyzing data| -|[`teal.data`](https://github.com/insightsengineering/teal.data) | creating and loading the data needed for `teal` applications| -|[`teal.widgets`](https://github.com/insightsengineering/teal.widgets) | `shiny` UI components used within `teal`| -|[`teal.slice`](https://github.com/insightsengineering/teal.slice) | provides a filtering panel to allow filtering of data| -|[`teal.code`](https://github.com/insightsengineering/teal.code) | handles reproducibility of outputs| -|[`teal.transform`](https://github.com/insightsengineering/teal.transform) | standardizes extracting and merging data| -|[`teal.logger`](https://github.com/insightsengineering/teal.logger) | standardizes logging within `teal` framework| -|[`teal.reporter`](https://github.com/insightsengineering/teal.reporter) | allows `teal` applications to generate reports| +|[`teal`](https://insightsengineering.github.io/teal) | `shiny`-based interactive exploration framework for analyzing data| +|[`teal.widgets`](https://insightsengineering.github.io/teal.widgets) | `shiny` UI components used within `teal`| +|[`teal.data`](https://insightsengineering.github.io/teal.data) | provides the data structure used in all `teal` applications| +|[`teal.slice`](https://insightsengineering.github.io/teal.slice) | provides the filter panel to allow dynamic filtering of data| +|[`teal.code`](https://insightsengineering.github.io/teal.code) | provides a mechanism for tracking code to reproduce an analysis| +|[`teal.transform`](https://insightsengineering.github.io/teal.transform) | standardizes extracting and merging data| +|[`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| Although these packages are mentioned in the material, we strongly recommend visiting their vignettes to learn more about them. diff --git a/vignettes/blueprint/module_encapsulation.Rmd b/vignettes/blueprint/module_encapsulation.Rmd index 9ce5420f21..fd15e4ca31 100644 --- a/vignettes/blueprint/module_encapsulation.Rmd +++ b/vignettes/blueprint/module_encapsulation.Rmd @@ -25,6 +25,6 @@ By implementing the modular app technique from the shiny module into the creatio `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 +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. diff --git a/vignettes/blueprint/products_map.Rmd b/vignettes/blueprint/product_map.Rmd similarity index 93% rename from vignettes/blueprint/products_map.Rmd rename to vignettes/blueprint/product_map.Rmd index 6bf18283dc..6f57ebc428 100644 --- a/vignettes/blueprint/products_map.Rmd +++ b/vignettes/blueprint/product_map.Rmd @@ -1,9 +1,9 @@ --- -title: "Products Map" +title: "Product Map" author: "NEST CoreDev" output: rmarkdown::html_vignette vignette: > - %\VignetteIndexEntry{Products Map} + %\VignetteIndexEntry{Product Map} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- @@ -65,7 +65,7 @@ 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`'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 analysing clinical trial data but it **has** a multitude of features distributed across various R 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. diff --git a/vignettes/blueprint/qenv.Rmd b/vignettes/blueprint/qenv.Rmd deleted file mode 100644 index 341238fef0..0000000000 --- a/vignettes/blueprint/qenv.Rmd +++ /dev/null @@ -1,61 +0,0 @@ ---- -title: "`qenv`" -author: "NEST CoreDev" -output: rmarkdown::html_vignette -vignette: > - %\VignetteIndexEntry{`qenv`} - %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} ---- - -## Introduction - -Reproducibility is paramount in the pharmaceutical industry. -Accurate and consistent results are essential to ensure high-quality research and the safety of patients. -By prioritizing reproducibility, researchers can validate their methods, confirm their findings, and contribute to the advancement of the field. - -[`qenv`](https://insightsengineering.github.io/teal.code/latest-tag/articles/qenv.html) is an essential feature provided by the `teal.code` package that facilitates code reproducibility. -It is a class that stores both data and code, which enables tracking of the analytical process, and thus allows for sharing and replication of R code. -Code is passed to a `qenv` object, where it is evaluated in a specific environment and stored, and can be retrieved on request. - -## `qenv` in a `teal` application - -In a `teal` application, a `qenv` object is created by the module developer in the module's server function. All data operations and their R code are appended and stored within the `qenv`'s environment, 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 `qenv` object makes it easy for users to reproduce and validate the results of their analyses. - -```{dot qenv_dot_diagram, echo=FALSE} -digraph G { - qenv [label = "qenv"]; - node [shape=box]; - teal_modules [label = "teal modules analysis R code"]; - library [label = "R library() calls"]; - filter_states [label = "filter states R code"]; - data [label = "data preprocessing R code"]; - teal_modules -> qenv; - library -> qenv; - edge [dir="back"]; - qenv -> filter_states; - qenv -> data; -} -``` - -Learn more about the use of `qenv` in [`teal` custom module](https://insightsengineering.github.io/teal/main/articles/creating-custom-modules.html) vignette. - -## `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. -This functionality is achieved by inspecting the qenv object, which contains the stored code. -With the Show R Code button, users can now easily copy and independently run the code to reproduce the same analysis presented in the teal module. - -![Show R Code](../../man/figures/showrcode.jpg){width=50%} - -The Reporter feature also leverages the qenv object in its operation. -Much like the Show R Code mechanism, the code displayed in a Reporter Card is extracted from the qenv object. - -![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). - -Overall, `qenv` from `teal.code` is a powerful tool for ensuring code reproducibility and promoting high-quality research in the R programming language. diff --git a/vignettes/blueprint/reporter.jpg b/vignettes/blueprint/reporter.jpg deleted file mode 100644 index 14eff4ec5f..0000000000 Binary files a/vignettes/blueprint/reporter.jpg and /dev/null differ diff --git a/vignettes/teal-bs-themes.Rmd b/vignettes/bootstrap-themes-in-teal.Rmd similarity index 91% rename from vignettes/teal-bs-themes.Rmd rename to vignettes/bootstrap-themes-in-teal.Rmd index 7c138def60..117677bfab 100644 --- a/vignettes/teal-bs-themes.Rmd +++ b/vignettes/bootstrap-themes-in-teal.Rmd @@ -28,7 +28,7 @@ options("teal.bs_theme" = bslib::bs_theme("Custom Options")) ####################### ``` -### Bootstrap Version and Themes +### Bootstrap version and themes The best and recommended ways to **explore** the Bootstrap themes are to use `bslib::run_with_themer(shinyApp(app$ui, app$server))` or `bslib::bs_theme_preview()`, both of which offer an interactive explore mode (not supported for Bootstrap 3). The `bslib::bs_theme_preview()` is recommended when the end user does not have any `shiny` app yet. When you already have a `shiny` app and you want to test different Bootstrap themes (and `CSS` styling) then `bslib::run_with_themer(shinyApp(app$ui, app$server))` is recommended. @@ -54,11 +54,11 @@ options("teal.bs_theme" = bslib::bs_theme(version = "5", bootswatch = "THEME NAM # or run the app inside bslib::run_with_themer ``` -### Reset the Bootstrap Theme +### Reset the Bootstrap theme Please use the `options("teal.bs_theme" = NULL)` call to return to the default `shiny` Bootstrap for `teal` apps. -### Theme Not Updated +### 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. @@ -87,7 +87,7 @@ remotes::install_github("https://github.com/dreamRs/shinyWidgets@main") If you want to update the theme in a regular `shiny::fluidPage`-like app, you do not need the `teal.bs_theme` option. Simply provide the `bslib::bs_theme` directly: `shiny::fluidPage(theme = bslib::bs_theme(...), ...)`. -### Interactive Theming Guide +### Interactive theming guide In this section we provide a step-by-step guide to customizing a `teal` application theme interactively with `bslib::run_with_themer()`. We recommend starting with a simple case and once you are satisfied, verifying with your full application. To that end we will use the `teal` application below. For this example we assume that we want to use Bootstrap 5. To start, we launch the app with `bslib::run_with_themer(app$ui, app$server)` instead of `shiny::shinyApp`. @@ -98,8 +98,8 @@ library(teal) app <- init( data = teal_data(IRIS = iris), # nolint - filter = list(IRIS = list(Sepal.Length = c(5, 7))), - modules = list(example_module(), example_module()), + filter = teal_slices(teal_slice("IRIS", "Sepal.Length", selected = c(5, 7))), + modules = modules(example_module(), example_module()), header = "My first teal application" ) @@ -108,9 +108,7 @@ bslib::run_with_themer(shinyApp(app$ui, app$server)) This gives us the following. -```{r echo = FALSE} -knitr::include_graphics("images/bs-launch.png") -``` + Note the `Theme Customizer` section on the right hand side. This was added by `bslib` and is how we customize our theme. @@ -118,9 +116,7 @@ Note the `Theme Customizer` section on the right hand side. This was added by `b Instead of starting from scratch, we want to start with a [`Bootswatch`](https://bootswatch.com/) theme. Let us select the Minty theme in the "Overall theme" drop-down. -```{r echo = FALSE} -knitr::include_graphics("images/bs-theme-set.png") -``` + `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 @@ -135,16 +131,11 @@ This is a helpful guide that provides code to update our theme. For `teal` appli 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. -```{r echo = FALSE} -knitr::include_graphics("images/bs-font-size.png") -``` + Finally, suppose we do not want borders to be rounded. In our `customizer` theme, we can go to "Options" and then uncheck the "Rounded corners" box. - -```{r echo = FALSE} -knitr::include_graphics("images/bs-corners.png") -``` + 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")`. @@ -165,11 +156,9 @@ options( library(teal) app <- init( - data = teal_data( - dataset("IRIS", iris) - ), - filter = list(IRIS = list(Sepal.Length = c(5, 7))), - modules = list(example_module(), example_module()) + data = teal_data(IRIS = iris), + filter = teal_slices(teal_slice("IRIS", "Sepal.Length", selected = c(5, 7))), + modules = modules(example_module(), example_module()) ) shinyApp(app$ui, app$server) @@ -177,9 +166,7 @@ shinyApp(app$ui, app$server) Now the application has our custom theme applied. -```{r echo = FALSE} -knitr::include_graphics("images/bs-final.png") -``` + Please note the interactive themer only contains the mostly 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 dbaa62b1e4..d9b9ad32c2 100644 --- a/vignettes/creating-custom-modules.Rmd +++ b/vignettes/creating-custom-modules.Rmd @@ -1,6 +1,6 @@ --- title: "Creating Custom Modules" -author: "Nikolas Burkoff" +author: "NEST CoreDev" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Creating Custom Modules} @@ -10,7 +10,8 @@ vignette: > ## Introduction -The `teal` framework provides a large number of analysis modules to be incorporated into `teal` applications. However, it is also possible to create your own modules using the `module` function. +The `teal` framework provides a large number of 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: @@ -21,16 +22,19 @@ example_module <- function(label = "example teal module") { module( label, server = function(id, data) { - checkmate::assert_class(data, "tdata") + checkmate::assert_class(data, "reactive") + checkmate::assert_class(isolate(data()), "teal_data") moduleServer(id, function(input, output, session) { - output$text <- renderPrint(data[[input$dataname]]()) + ns <- session$ns + updateSelectInput(session, "dataname", choices = isolate(datanames(data()))) + output$text <- renderPrint(data()[[input$dataname]]) }) }, - ui = function(id, data) { + ui = function(id) { ns <- NS(id) teal.widgets::standard_layout( output = verbatimTextOutput(ns("text")), - encoding = selectInput(ns("dataname"), "Choose a dataset", choices = names(data)) + encoding = selectInput(ns("dataname"), "Choose a dataset", choices = NULL) ) }, datanames = "all" @@ -44,31 +48,36 @@ which can be added into `teal` apps using `example_module(label = "Label for tab ### 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. See the server section below for more details. +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. +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 +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: -``` -function(id, - data, # optional, use if you want your module to access the application data - filter_panel_api, # optional, use if you want access to the filter panel from your module see teal.slice - reporter, # optional, use if your module supports reporting, see reporting vignette in teal - ...) { +```{r, eval=FALSE} +function( + id, + data, # optional; use if module needs access 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 + ...) { moduleServer(id, function(input, output, session) { # module code here }) } - ``` -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. +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. -## A More Complicated Example +## A more complicated example The `teal` framework also provides: @@ -84,18 +93,14 @@ See the package and function documentation for further details. library(teal) # ui function for the module -# histogram_var is a teal.transform::data_extract_spec object -# specifying which columns of which datasets users can choose -ui_histogram_example <- function(id, histogram_var) { +# 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( - teal.transform::data_extract_ui( - id = ns("histogram_var"), - label = "Variable", - data_extract_spec = histogram_var - ) + 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 @@ -104,45 +109,47 @@ ui_histogram_example <- function(id, histogram_var) { } # server function for the module -# histogram_var is a teal.transform::data_extract_spec object -# specifying which columns of which datasets users can choose -srv_histogram_example <- function(id, data, histogram_var) { - checkmate::assert_class(data, "tdata") - moduleServer(id, function(input, output, session) { - # get the selected dataset and column from the UI - extracted <- teal.transform::data_extract_srv( - id = "histogram_var", - datasets = data, - data_extract_spec = histogram_var, - join_keys = teal.data::join_keys(data) - ) +# presents datasets and numeric variables for selection +# displays a histogram of the selected variable +srv_histogram_example <- function(id, data) { + checkmate::assert_class(data, "reactive") + checkmate::assert_class(isolate(data()), "teal_data") - dataname <- reactive(extracted()$dataname) - selected <- reactive(extracted()$select) + 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()))) + observe({ + req(dataset()) + nums <- vapply(data()[[dataset()]], is.numeric, logical(1L)) + updateSelectInput(session, "numerics", "select numeric variable", choices = names(nums[nums])) + }) + dataset <- reactive(input$datasets) + selected <- reactive(input$numerics) - # the reactive which adds the code to plot the histogram into the qenv + # add plot code plot_code_q <- reactive({ - validate(need(length(selected) == 1, "Please select a variable")) - - # take the filtered data from the data object and add it into the qenv environment - teal.code::new_qenv(tdata2env(data), code = get_code_tdata(data)) %>% - teal.code::eval_code( - substitute( - expr = p <- hist(dataname[, selected]), - env = list( - dataname = as.name(dataname()), - selected = selected() - ) - ) - ) + validate(need(length(dataset()) == 1L, "Please select a dataset")) + validate(need(length(selected()) == 1L, "Please select a variable")) + req(selected() %in% names(data()[[dataset()]])) + + # evaluate plotting expression within data + # inject input values into plotting expression + within( + data(), + p <- hist(dataset[, selected], las = 1), + dataset = as.name(dataset()), selected = selected() + ) }) - # shiny component to view + # view plot output$plot <- renderPlot({ plot_code_q()[["p"]] }) - # Show the R code when user clicks 'Show R Code' button + # code upon clicking 'Show R Code' button teal.widgets::verbatim_popup_srv( id = "rcode", verbatim_content = reactive(teal.code::get_code(plot_code_q())), @@ -151,17 +158,12 @@ srv_histogram_example <- function(id, data, histogram_var) { }) } -# the function which creates the teal module for users -tm_histogram_example <- function(label, histogram_var) { - checkmate::assert_character(label) - checkmate::assert_class(histogram_var, "data_extract_spec") - +# function that creates module instance to use in `teal` app +tm_histogram_example <- function(label) { module( label = label, server = srv_histogram_example, ui = ui_histogram_example, - ui_args = list(histogram_var = histogram_var), - server_args = list(histogram_var = histogram_var), datanames = "all" ) } @@ -176,19 +178,8 @@ An example `teal` application using this module is shown below: library(teal) app <- init( - data = teal_data( - IRIS = iris, - code = "IRIS <- iris" - ), - modules = tm_histogram_example( - label = "Simple Module", - histogram_var = data_extract_spec( - dataname = "IRIS", - select = select_spec( - choices = c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width") - ) - ) - ), + data = teal_data(IRIS = iris, NPK = npk), + modules = tm_histogram_example(label = "Histogram Module"), header = "Simple app with custom histogram module" ) @@ -199,7 +190,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. +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. ## Adding reporting to a module Refer to `vignette("adding_support_for_reporting")` to read about adding support for reporting in your `teal` module. diff --git a/vignettes/data-as-shiny-module.Rmd b/vignettes/data-as-shiny-module.Rmd index 4a5d9a87e2..b7d08bfe69 100644 --- a/vignettes/data-as-shiny-module.Rmd +++ b/vignettes/data-as-shiny-module.Rmd @@ -25,7 +25,7 @@ The `teal_data_module` function is used to build such a module from the followin `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 +## Creating data in-app One case for postponing data operations are data sets that are dynamic, frequently updated. Such data cannot be created once and kept in the global environment. @@ -68,7 +68,7 @@ if (interactive()) { ``` -## Modification of Data In-App +## Modification of data in-app Another reason to postpone data operations is to allow the application user to act the preprocessing stage. An initial, constant form of the data can be created in the global environment and then modified once the app starts. @@ -141,11 +141,6 @@ data_mod_2 <- within( dataset1$Ratio.Sepal.Petal.Width <- round(dataset1$Sepal.Width / dataset1$Petal.Width, digits = 2L) # Create new column that converts Miles per Galon to Liter per 100 Km dataset2$lp100km <- round(dataset2$mpg * 0.42514371, digits = 2L) - dataset2 <- dplyr::relocate( - dataset2, - "lp100km", - .after = "mpg" - ) } ) diff --git a/vignettes/filter-panel.Rmd b/vignettes/filter-panel.Rmd index 55c4acd127..97261eb7f9 100644 --- a/vignettes/filter-panel.Rmd +++ b/vignettes/filter-panel.Rmd @@ -8,21 +8,21 @@ vignette: > %\VignetteEncoding{UTF-8} --- -## `teal` Apps With the Filter Panel +## `teal` apps with the filter panel -The filter panel is an integral part of all `teal` applications and is included on the right side. Based on the selections made in the filter panel, filter expressions are executed before passing data to `teal` modules. The technical details of the filter panel are extensively described in [`teal.slice` documentation](https://insightsengineering.github.io/teal.slice/latest-tag/). +The filter panel is an integral part of all `teal` applications and is included on the right side. +Based on the selections made in the filter panel, filter expressions are executed before passing data to `teal` modules. +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. In the following example four filters are specified using the `teal_slice` function and wrapped together with `teal_slices`. +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. +In the following example four filters are specified using the `teal_slice` function and wrapped together with `teal_slices`. ```r library(teal) app <- init( - data = teal_data( - IRIS = iris, CARS = mtcars, - code = "IRIS <- iris - CARS <- mtcars" - ), + data = teal_data(IRIS = iris, CARS = mtcars), modules = example_module(), filter = teal_slices( teal_slice(dataname = "IRIS", varname = "Sepal.Length"), @@ -41,17 +41,14 @@ if (interactive()) { ### Filter panel respective to `teal_module` -Each `teal_module` (see `?module`) object contains the `datanames` attribute that determines which data sets are to be sent to that module. The filter panel will display only those data sets and hide the rest when this module is active. +Each `teal_module` (see `?module`) object contains the `datanames` attribute that determines which data sets are to be sent to that module. +The filter panel will display only those data sets and hide the rest when this module is active. ```r library(teal) app <- init( - data = teal_data( - IRIS = iris, CARS = mtcars, - code = "IRIS <- iris - CARS <- mtcars" - ), + data = teal_data(IRIS = iris, CARS = mtcars), modules = modules( example_module(label = "all datasets"), example_module(label = "IRIS only", datanames = "IRIS"), @@ -66,7 +63,10 @@ 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. 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. +`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. +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. One possible scenario is depicted in the figure below: @@ -78,13 +78,15 @@ One possible scenario is depicted in the figure below: ![](./images/filters_mapping.jpg) -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. +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. ```r library(teal) app <- init( - data = list(mtcars = mtcars), + data = teal_data(mtcars = mtcars), modules = modules( example_module(label = "module 1"), example_module(label = "module 2"), diff --git a/vignettes/teal.Rmd b/vignettes/getting-started-with-teal.Rmd similarity index 70% rename from vignettes/teal.Rmd rename to vignettes/getting-started-with-teal.Rmd index 5b79234de0..50873c00c4 100644 --- a/vignettes/teal.Rmd +++ b/vignettes/getting-started-with-teal.Rmd @@ -47,11 +47,11 @@ if (interactive()) { As shown in the image above, this application consists of several distinct areas: -* Application header: the title of the application shown at the top. -* `teal` modules (bar at the top): 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 (panels on the right hand side): for filtering the data to be passed into all `teal` modules. +* 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 @@ -70,31 +70,40 @@ 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 +Example filter panel ## Creating your own applications The key function to use to create your `teal` application is `init`, which requires two arguments: `data` and `modules`. -### Application Data +### Application data -The `data` argument to the `init` function specifies the data used by your application. This can be data currently in your R session, as in the example above, but also `connectors`, which describe how to "pull" remote data when the application is run. Connectors can pull data from external sources, which may require authentication. +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. +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 the example above we call `teal_data` to convert raw datasets into `teal` specific datasets and to bind them in one `R` object. This function can also be used to specify relationships between different datasets. 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. 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. +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. -### Defining Filters +### 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. +The optional `filter` argument in `init` allows you to initialize the application with predefined filters. +See the documentation for `init` for further details. ### 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. +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. ## Where to go next diff --git a/vignettes/images/example_app.png b/vignettes/images/example_app.png index 553ecd7cd9..8c8469c7df 100644 Binary files a/vignettes/images/example_app.png and b/vignettes/images/example_app.png differ diff --git a/vignettes/images/filter_panel.png b/vignettes/images/filter_panel.png index 66b22b35e1..d7adcd60cd 100644 Binary files a/vignettes/images/filter_panel.png and b/vignettes/images/filter_panel.png differ diff --git a/vignettes/including-adam-data-in-teal.Rmd b/vignettes/including-adam-data-in-teal.Rmd deleted file mode 100644 index 428bff9f88..0000000000 --- a/vignettes/including-adam-data-in-teal.Rmd +++ /dev/null @@ -1,113 +0,0 @@ ---- -title: "Using ADaM Data in teal Applications" -author: "NEST CoreDev" -output: rmarkdown::html_vignette -vignette: > - %\VignetteIndexEntry{Using ADaM Data in teal Applications} - %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} ---- - -## Introduction - -To include `ADaM` data in a `teal` app, the `teal.data::cdisc_data` function is used. - -`cdisc_data` allows `teal` applications to include multiple datasets, identifying merge keys and providing information to produce R code for reproducibility. - -There is an advantage to passing `CDISC` datasets that adhere to `ADaM` standards to these functions in that the code is minimized. However, the dataset-related functions also include the flexibility to work with non-standard datasets provided that merge keys and the relationship between the datasets are specified. - -The examples below illustrate the usage of these different dataset functions for example `cdisc_dataset` and `dataset`. For more information, see documentation in `teal.data`. - -## Keys - -Primary keys serve as unique row identifiers in individual datasets and thus need to be specified for each dataset and dataset connector. These can be specified on the most general dataset constructor, `dataset`, as shown below. - -```{r, message=FALSE} -library(teal) - -# using cdisc_dataset, keys are automatically derived for standard datanames -# (although they can be overwritten) -adsl <- data.frame( - STUDYID = "study", - USUBJID = 1:10, - SEX = sample(c("F", "M"), 10, replace = TRUE), - AGE = rpois(10, 40) -) -dataset_adsl <- cdisc_dataset("ADSL", adsl) -class(dataset_adsl) -``` - -When passing multiple datasets to the `cdisc_data` function, dataset relationship are set using `join_keys` and `join_key` and these are used to merge datasets together within `teal` apps. - -In the example below, two standard `CDISC` datasets (`ADSL` and `ADTTE`) are passed to `cdisc_data`. In the case of `CDISC` datasets that adhere to `ADaM` standards, the merge keys do not need to be manually specified. Keys are automatically added if `dataname` matches one of the implemented standards as documented in the `cdisc_dataset` function. This minimizes the code needed to allow data merges as seen in this example: - -```{r, message=FALSE} -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), # dummy OS level - rnorm(10, mean = 400, sd = 100), # dummy EFS level - rnorm(10, mean = 450, sd = 200) # dummy PFS level -) - -cdisc_data_obj <- 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), # dummy OS level - rnorm(10, mean = 400, sd = 100), # dummy EFS level - rnorm(10, mean = 450, sd = 200) # dummy PFS level - )' -) -class(cdisc_data_obj) -``` - -which is equivalent to: -```{r, message=FALSE} -example_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) - )' -) -class(cdisc_data_obj) -``` - -The `teal.data::join_keys` function is used to specify keys: - -- [`teal.data::join_keys`](https://insightsengineering.github.io/teal.data/latest-tag/reference/join_keys.html) is a collection of multiple `teal.data::join_key` entries -- [`teal.data::join_key`](https://insightsengineering.github.io/teal.data/latest-tag/reference/join_key.html) specifies the relation between two datasets: - - `dataset_1`, `dataset_2` - name of two datasets - - `key` - optionally named vector of column names - -Note that it is assumed that join keys are symmetric, i.e. `join_key("x", "y", "x_col" = "y_col")` will enable merge from "x" to "y" and vice-versa. - - -## Further Reading - -For more information about preprocessing, reproducibility, relationships between datasets and `DDL`, please refer to the [`teal.data` package](https://insightsengineering.github.io/teal.data/). diff --git a/vignettes/including-data-in-teal-applications.Rmd b/vignettes/including-data-in-teal-applications.Rmd new file mode 100644 index 0000000000..0e317438cf --- /dev/null +++ b/vignettes/including-data-in-teal-applications.Rmd @@ -0,0 +1,246 @@ +--- +title: "Including Data in teal Applications" +author: "NEST CoreDev" +output: + rmarkdown::html_vignette: + toc: true + toc_depth: 5 + number_sections: false +vignette: > + %\VignetteIndexEntry{Including Data in teal Applications} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +``` + +## 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. + +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. +Thus, the first step of building a `teal` app is creating a `teal_data` object. + +### General data + +A `teal_data` object is created by calling the `teal_data` function and passing data objects as `name:value` pairs. + +```{r, message=FALSE} +library(teal) + +# create teal_data +data <- teal_data(iris = iris, cars = mtcars) +``` + +Note that `iris` and `cars` have been added to the `datanames` property of `data` (see [`datanames` property](#datanames)). + +This is sufficient to run a `teal` app. + +```{r, eval=FALSE} +# build app +app <- init( + data = data, + modules = example_module() +) + +# run app +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. +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() + +# run code in the object +data1 <- teal.code::eval_code(data0, code = "iris <- iris + cars <- mtcars") +# alternative +data2 <- within(data0, { + 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. + +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. +Such an object is considered _unverified_ (see [`verified` property](#verified)). + +If reproducibility is required, we recommend creating `teal_data` empty and evaluating code. + +###### code from file + +The ability to pass code as 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() +data_from_file <- eval_code(data, readLines("")) +``` + +### Creating data in-app + +The one departure from passing a `teal_data` object to `init` is when the data does not exist in the environment where the app is run, _e.g._ when it has to be pulled from a remote source. +In those cases a `teal_data_module` must be used. +See [this vignette](data-as-shiny-module.html) for a detailed description. + +
+ +## Clinical data + +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`. + +```{r} +# create cdisc_data +data_cdisc <- cdisc_data(ADSL = teal.data::rADSL, ADTTE = teal.data::rADSL) + +datanames(data_cdisc) +join_keys(data_cdisc) +``` + + +```{r, eval=FALSE} +app <- init( + data = data_cdisc, + modules = example_module() +) +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` class is described in detail [here](https://www.bioconductor.org/packages/release/bioc/vignettes/MultiAssayExperiment/inst/doc/MultiAssayExperiment.html). + +`MultiAssayExperiment` objects (MAEs) are placed in `teal_data` just like normal objects. + +```{r, eval=FALSE} +library(MultiAssayExperiment) +utils::data(miniACC) + +data_mae <- teal_data(MAE = miniACC) + +app <- init( + data = data_mae, + modules = example_module() +) +shinyApp(app$ui, app$server) +``` + +Note that 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. + +The filter panel supports MAEs out of the box. + +
+ +## `teal_data` properties + +##### `datanames` + +The `datanames` property lists the objects stored in the `teal_data` 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. + +```{r} +data_with_objects <- teal_data(iris = iris, cars = mtcars) +data_with_code <- teal_data() %>% + within({ + iris <- iris + cars <- mtcars + not_a_dataset <- "data source credits" + }) +datanames(data_with_objects) +datanames(data_with_code) +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. + +For a detailed explanation of `datanames`, see [this `teal.data` vignette](https://insightsengineering.github.io/teal.data/latest-tag/articles/teal-data.html). + +[(back to General Data)](#general-data) + +##### `join_keys` + +Using relational data requires specifying joining keys for each pair of datasets. +Primary keys are unique row identifiers in individual datasets and thus should be specified for each dataset. +Foreign keys describe mapping of variables between datasets. +Joining keys are stored in the `join_keys` property, which can be set when creating a `teal_data` object, using the `join_keys` argument, or using the `join_keys` function. + +```{r} +ds1 <- data.frame( + id = seq(1, 10), + group = rep(c("A", "B"), each = 5) +) +ds2 <- data.frame( + group = c("A", "B"), + condition = c("condition1", "condition2") +) +keys <- join_keys( + join_key("DS1", keys = "id"), + join_key("DS2", keys = "group"), + join_key("DS1", "DS2", keys = c("group" = "group")) +) +data_relational1 <- teal_data(DS1 = ds1, DS2 = ds2, join_keys = keys) +data_relational2 <- teal_data(DS1 = ds1, DS2 = ds2) +join_keys(data_relational2) <- keys +``` + +For a detailed explanation of join keys, see [this `teal.data` vignette](https://insightsengineering.github.io/teal.data/latest-tag/articles/join-keys.html). + +[(back to `ADaM` Data)](#adam-data) + +##### `verified` + +`teal_data` allows for tracking code from data creation through filtering through 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. + +```{r} +data_with_code + +data_with_objects_and_code <- teal_data(iris = iris, cars = mtcars, code = expression(iris <- iris, cars <- mtcars)) +data_with_objects_and_code + +data_with_objects_and_code_ver <- verify(data_with_objects_and_code) +data_with_objects_and_code_ver +``` + + +For a detailed explanation of verification, see [this `teal.data` vignette](https://insightsengineering.github.io/teal.data/latest-tag/articles/teal-data-reproducibility.html). + +[(back to Reproducible Data)](#reproducible-data) + +
+ +## Further reading + +For a complete guide to the `teal_data` class, please refer to the [`teal.data` package](https://insightsengineering.github.io/teal.data/latest-tag/). diff --git a/vignettes/including-general-data-in-teal.Rmd b/vignettes/including-general-data-in-teal.Rmd deleted file mode 100644 index 75c14cbd80..0000000000 --- a/vignettes/including-general-data-in-teal.Rmd +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: "Using General Data in teal Applications" -author: "NEST CoreDev" -output: rmarkdown::html_vignette -vignette: > - %\VignetteIndexEntry{Using General Data in teal Applications} - %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} ---- - -## Introduction - -`teal` applications are not restricted to `CDISC`-standard data. Although many `teal` modules included with `NEST` are designed for `CDISC` data, those in the library `teal.modules.general` have been designed to work with non-relational data. - -This example application uses the standard `iris` and `mtcars` datasets: - -```{r, message=FALSE} -library(teal) - -app <- init( - data = teal_data( - IRIS = iris, CARS = mtcars, - code = "IRIS <- iris - CARS <- mtcars" - ), - modules = example_module() -) - -if (interactive()) { - shinyApp(app$ui, app$server) -} -``` - -For more information, see documentation in `teal.data`. - -## Delayed Data Loading (`DDL`) - -`teal` provides the ability to pull remote data and use it in the app. Additional user authentication may be necessary. - -To learn more about `DDL`, visit the appropriate vignette in `teal.data`. - - -## Further Reading - -For more information about preprocessing, reproducibility, relationships between datasets and `DDL`, please refer to the [`teal.data` package](https://insightsengineering.github.io/teal.data/). diff --git a/vignettes/including-mae-data-in-teal.Rmd b/vignettes/including-mae-data-in-teal.Rmd deleted file mode 100644 index c614dc7a1f..0000000000 --- a/vignettes/including-mae-data-in-teal.Rmd +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: "Using MultiAssayExperiment data in teal Applications" -author: "NEST CoreDev" -output: rmarkdown::html_vignette -vignette: > - %\VignetteIndexEntry{Using MultiAssayExperiment data in teal Applications} - %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} ---- - -## `MultiAssayExperiment` data -`MultiAssayExperiment` 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. - -`MultiAssayExperiment` data is described in detail [here](https://www.bioconductor.org/packages/release/bioc/vignettes/MultiAssayExperiment/inst/doc/MultiAssayExperiment.html). - - -## Example Application -The example below represents an application including `MultiAssayExperiment` data. - -```{r, eval = FALSE} -library(teal) -utils::data(miniACC, package = "MultiAssayExperiment") - -app <- init( - data = teal_data(MAE = miniACC), - modules = example_module() -) - -if (interactive()) { - shinyApp(app$ui, app$server) -} -``` - -The filter panel supports `MAE` data out of the box, but `teal` itself does not guarantee that any module will work with `MAE` data the same way it works with other types of data (e.g. `ADaM`) because `MAE` has a unique structure that requires special consideration when developing a module. The package [`teal.modules.hermes`](https://insightsengineering.github.io/teal.modules.hermes/) has been specifically developed for the analysis of `MAE` data. - - -## Further Reading - -For more information about preprocessing, reproducibility, relationships between datasets and `DDL`, please refer to the [`teal.data` package](https://insightsengineering.github.io/teal.data/). diff --git a/vignettes/preprocessing-data.Rmd b/vignettes/preprocessing-data.Rmd deleted file mode 100644 index 38848a5ea5..0000000000 --- a/vignettes/preprocessing-data.Rmd +++ /dev/null @@ -1,85 +0,0 @@ ---- -title: "Preprocessing Data" -author: "NEST CoreDev" -output: rmarkdown::html_vignette -vignette: > - %\VignetteIndexEntry{Preprocessing Data} - %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} ---- - -## Usage in `teal` apps -In this vignette, we will show how to use preprocessed data in `teal` apps. The basics of data preprocessing have been discussed in `teal.data` [here](https://insightsengineering.github.io/teal.data/latest-tag/articles/preprocessing-data.html). - -In a `teal` app providing the code in a copy-paste style is cumbersome and can lead to an out-of-sync situation where the code does not represent the preprocessing code anymore. We therefore use the `teal.data::get_code` function to extract the preprocessing code from the `app.R` file. The `get_code` function requires `#` tags to indicate which lines of code in `app.R` need be included in the preprocessing code. `get_code` understands the following tags: - -- Enclosing preprocessing code between `#code>` `#` `#NOTE:** make sure to save the code in a file called `app.R`): - -```{r eval=FALSE} -# Code needs modification before it can be run: -# - save as app.R -# for the purpose of example save the file to current directory -library(teal) - -# code> -new_iris <- transform(iris, id = seq_len(nrow(iris))) -# -# data import -new_iris <- transform(iris, id = seq_len(nrow(iris))) - -excluded_obj1 <- 1:10 # nocode - -# nocode> -excluded_obj2 <- 1:10 -#